精华内容
下载资源
问答
  • Java函数编程
    千次阅读
    2022-01-26 15:03:02


    函数式接口

    有且只有一个抽象方法的接口被称为函数式接口,该接口中可以包含其他的方法(默认,静态,私有)。函数式接口用@FunctionalInterface注解。

    引入函数式编程后Java接口的定义发生了一些变化:

    1. 函数式接口:函数式接口中只能有一个抽象方法,这也是为了函数调用时避免带来二义性。使用@FunctionInterface标注接口为函数式接口(@FunctionInterface并不是一定要标注但若是标注可以在编译时就给你提示错误)

    2. 支持静态方法:支持静态方法目的完全出于编写类库,对某些行为进行抽象,但是接口中的静态方法不能被继承。

    3. 支持默认实现:这是不得已而为之,因为Java8引入了函数式接口,许多像Collection这样的基础接口中增加了方法,如果还是一个传统的抽象方法的话,那么可能很多第三方类库就会变得完全无法使用。新增一个方法所有实现类都要实现一次。被default修饰的方法–默认实现。其主要思想就是如果子类中没有实现,那么采用父类提供的默认实现。

    定义了这种类型的接口,使得以其为参数的方法,可以在调用时,使用一个lambda表达式作为参数。从另一个方面说,一旦我们调用某方法,可以传入lambda表达式作为参数,则这个方法的参数类型,必定是一个函数式的接口。

    关于函数式接口只有一个抽象方法
    关于函数式接口只有一个抽象方法的理解:

    1. 一个函数式接口有且只有一个抽象方法。

    2. 默认方法不是抽象方法,因为它们已经实现了。

    3. 重写了超类Object类中任意一个public方法的方法并不算接口中的抽象方法。

      如果一个接口中声明的抽象方法是重写了超类Object类中任意一个public方法,那么这些抽象方法并不会算入接口的抽象方法数量中。因为任何接口的实现都会从其父类Object或其它地方获得这些方法的实现。

    所以比如Comparator接口,有两个抽象方法compare和equals,但equals并不算入接口中的抽象方法,所以Comparator接口还是满足函数式接口的要求,Comparator接口是一个函数式接口

    Lambda表达式

    Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。

    Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。使用 Lambda 表达式可以使代码变的更加简洁紧凑。Lambda 表达式免去了使用匿名方法的麻烦,并且给予Java简单但是强大的函数化的编程能力。

    在Lambda表达式中无需像传统写法那样声明参数和返回值类型,它会根据你的上下文通过类型推导你实现的是哪一个接口,从而跟具这个接口的定义知道你变量的类型。这也是为什么函数式接口只能声明一个方法的原因。

    增加Lambda的目的,其实就是为了支持函数式编程,而为了支持Lambda表达式,才有了函数式接口。

    另外,为了在面对大型数据集合时,为了能够更加高效的开发,编写的代码更加易于维护,更加容易运行在多核CPU上,java在语言层面增加了Lambda表达式。

    语法特征

    (parameters) -> expression
    
    (parameters) -> { statements; }
    

    是lambda表达式的重要特征:

    • 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
    • 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
    • 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
    • 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定表达式返回了一个数值。

    方法引用

    方法的引用让你可以重复使用现有的方法定义,并像lambda一样传递他们,在一些情况下,比起使用lambda表达式,它们似乎更易读,感觉也更自然。

    语法:

    • 类::静态方法名

      指向静态方法的方法引用,例如Integer的compare方法 ,可以写成Integer::compare

      Comparator<Integer> c = Integer::compare;
      
    • 类::实例方法名

      指向任意类型实例方法的方法引用,例如String的equals方法,写成String::equals

      BiPredicate<String, String> b = String::equals;
      
    • 对象::实例方法名

      指向现有对象的实例方法的方法引用,例如System.out的println方法,写成System.out::println

      Consumer<String> con1 = System.out::println;
      User user = new User();
      Consumer<String> con2 = user::setUserName;
      

    构造器引用

    • 构造器的引用:ClassName::new

      对于一个现有构造函数,你可以利用它的名称和关键字new来创建它的一个引用,例如:String::new。

      Supplier<String> supplier = String::new;
      

    Java8之前的函数式接口

    Java8之前已经存在的函数式接口

    函数式接口输入参数输出参数描述
    Runnable--多线程自定义执行逻辑,无返回值
    Callabler<V>-V多线程自定义执行逻辑,有返回值
    PrivilegedAction<T>-T
    Comparator<T>T、Tint比较器,常用语比较和排序
    FileFilterFileboolean文件过滤器
    PathMatcherPathboolean路径匹配器
    InvocationHandlerObject、Method、Object[]Object动态代理,生产道理对象
    PropertyChangeListenerPropertyChangeEvent-属性改变监听器
    ActionListenerActionEvent-动作监听器
    ChangeListenerChangeEvent-变动监听器

    Java8新增的函数式接口

    Java8新增的java.util.function包定义的函数式接口

    函数式接口输入参数输出参数描述
    Supplier<T>-T生产接口

    指定接口的泛型是什么类型,那么接口的get方法就会生产什么类型的数据
    IntSupplier-int
    LongSupplier-long
    DoubleSupplier-double
    BooleanSupplier-boolean
    Consumer<T>T-消费接口

    指定接口的泛型是什么类型,那么接口的accept方法就会消费什么类型的数据
    IntConsumerint-
    LongConsumerlong-
    DoubleConsumerdouble-
    BiConsumer<T,U>T、U-
    ObjIntConsumer<T>T、int-
    ObjLongConsumer<T>T、long-
    ObjDoubleConsumer<T>T、double-
    Predicate<T>Tboolean判断接口

    对接口的泛型类型数据进行判断,接口的test方法就是用来得到判断结果
    IntPredicateintboolean
    LongPredicatelongboolean
    DoublePredicatedoubleboolean
    BiPredicate<T, U>T、Uboolean
    Function<T, R>TR转换接口

    接口的apply方法把T数据类型的数据转换为R数据类型的数据
    UnaryOperator<T>TT
    ToIntFunction<T>Tint
    ToLongFunction<T>Tlong
    ToDoubleFunction<T>Tdouble
    IntFunction<R>intR
    IntToLongFunctionintlong
    IntToDoubleFunctionintdouble
    IntUnaryOperatorintint
    LongFunction<R>longR
    LongToIntFunctionlongint
    LongToDoubleFunctionlongdouble
    LongUnaryOperatorlonglong
    DoubleFunction<R>doubleR
    DoubleToIntFunctiondoubleint
    DoubleToLongFunctiondoublelong
    DoubleUnaryOperatordoubledouble
    BiFunction<T, U, R>T、UR
    ToIntBiFunction<T, U>T、Uint
    ToLongBiFunction<T, U>T、Ulong
    ToDoubleBiFunction<T, U>T、Udouble
    BinaryOperator<T>T、TT
    IntBinaryOperatorint、intint
    LongBinaryOperatorlong、longlong
    DoubleBinaryOperatordouble、doubledouble

    Supplier(生产)

    Supplier<T>
    抽象方法

    public T get():用来获取一个泛型参数指定类型的对象数据
    

    Supplier< T > 接口被称之为生产型接口,指定接口的泛型是什么类型,那么接口中的get方法就会生产什么类型的数据。

    举例:
    求数组的最大值最小值

    public static void main(String[] args) {
        Integer[] arr = {2, 7, 3, 4, 5};
        //求最大值
        Integer max = get(() -> {
            Integer a = arr[0];
            for (Integer integer : arr) {
                a = integer >= a ? integer : a;
            }
            return a;
        });
        System.out.println("max:"+max);
        // 求最小值
        Integer min = get(() -> {
            Integer a = arr[0];
            for (Integer integer : arr) {
                a = integer <= a ? integer : a;
            }
            return a;
        });
        System.out.println("min:"+min);
    }
    
    public  static <T> T get(Supplier<T> supplier) {
        return supplier.get();
    }
    

    日志

    max:7
    min:2
    

    Consumer(消费)

    Consumer<T>

    抽象方法

    public void accept(T t):对给定的参数执行此操作。   
    

    默认方法

    public default Consumer<T> andThen(Consumer<? super T> after):返回一个组合的 Consumer ,按顺序执行该操作,然后执行 after操作。 
    

    Consumer<T> 接口被称之为消费型接口,指定接口的泛型是什么类型,那么就可以使用接口中的accept方法消费什么类型的数据,具体怎么消费就需要自定义。

    而其默认方法则是可以把两个Consumer接口组合在一起对数据进行消费,谁写在前边就先消费谁,其调用格式为con1.andThen(con2).accept(s)。

    举例:

    public static void main(String[] args) {
        User user = new User();
        user.setId("1");
        // 打印用户信息
        consumer(user, (u) -> System.out.println(JSON.toJSONString(u)));
        // 修改用户信息
        consumer(user, (u) -> u.setId("2"));
        System.out.println(JSON.toJSONString(user));
    }
    
    public static void consumer(User user, Consumer<User> consumer) {
        consumer.accept(user);
    }
    

    日志

    {"id":"1"}
    {"id":"2"}
    

    BiConsumer<T, U>
    和Consumer<T>类似

    举例:

    public static void main(String[] args) {
        User user = new User();
        user.setId("1");
        // 修改用户ID为指定值
        biConsumer(user,"2", (u,id) ->u.setId(id));
        System.out.println(JSON.toJSONString(user));
    }
    
    public static void biConsumer(User user, String id, BiConsumer<User, String> biConsumer) {
        biConsumer.accept(user, id);
    }
    

    日志

    {"id":"2"}
    

    Predicate(判断)

    Predicate<T>

    抽象方法

    public boolean test(T t):在给定的参数上评估这个谓词。
    

    默认方法

    public default Predicate<T> and(Predicate<? super T> other):返回一个组合的谓词,表示该谓词与另一个谓词的短路逻辑AND。
    public default Predicate<T> or(Predicate<? super T> other):返回一个组合的谓词,表示该谓词与另一个谓词的短路逻辑或。  
    public default Predicate<T> negate():返回表示此谓词的逻辑否定的谓词。
    

    Predicate< T > 接口就是用来对某种数据类型的数据进行判断,接口中的test方法就是用来制定判断规则并把结果返回

    其三个默认方法其实就是相对三个逻辑——与、或、非。

    举例:

    public static void main(String[] args) {
        Integer integer = 2;
        Predicate<Integer> predicate1 = (i) -> i > 1;
        Predicate<Integer> predicate2 = (i) -> i < 2;
        Predicate<Integer> predicate3 = (i) -> i < 0;
    
        System.out.println(predicate1.test(integer));
        System.out.println(predicate1.and(predicate2).test(integer));
        System.out.println(predicate1.negate().or(predicate3).test(integer));
    }
    

    日志:

    true
    false
    false
    

    BiPredicate<T, U>
    和Predicate<T>类似
    举例:

    public static void main(String[] args) {
        Integer integer1 = 2;
        Integer integer2 = 2;
        BiPredicate<Integer, Integer> predicate1 = (i1, i2) -> i1.compareTo(i2) > 0;
        BiPredicate<Integer, Integer> predicate2 = (i1, i2) -> i1.compareTo(i2) == 0;
    
        System.out.println(predicate1.test(integer1, integer2));
        System.out.println(predicate1.or(predicate2).test(integer1, integer2));
        System.out.println(predicate2.negate().test(integer1, integer2));
    }
    

    日志:

    false
    true
    false
    

    Function(转换)

    Function<T,R>
    抽象方法

    public R apply(T t):将此函数应用于给定的参数。 
    

    默认方法

    public default <V> Function<T,V> andThen(Function<? super R,? extends V> after):返回一个组合函数,首先将该函数应用于其输入,然后将 after函数应用于结果。  
    

    Function< T , R > 接口被用作转换数据类型的,可调用其apply方法把T数据类型的数据转换为R数据类型的数据,但转换规则需要自定义

    另外,其默认方法也是把两个Function接口连接起来,第二个Function接口会把第一个Function接口的输出结果作为输入,也可认为这是链式组合(类似方法的链式调用)

    举例:

    public static void main(String[] args) {
        Integer integer = 2;
        Function<Integer, Integer> function1 =  (i) -> i+1;
        Function<Integer, Integer> function2 =  (i) -> i*10;
        System.out.println(function1.apply(integer));
        System.out.println(function2.apply(integer));
        System.out.println(function1.andThen(function2).apply(integer));
    }
    

    日志:

    3
    20
    30
    

    BiFunction<T, U, R>
    和Function<T,R>类似
    举例:

    public static void main(String[] args) {
        Integer integer1 = 2;
        Integer integer2 = 5;
        BiFunction<Integer, Integer, Integer> biFunction = (i1,i2) -> i1+i2;
        Function<Integer, Integer> function = (i) -> i*10;
        System.out.println(biFunction.apply(integer1, integer2));
        System.out.println(biFunction.andThen(function).apply(integer1, integer2));
    }
    

    日志:

    7
    70
    



    参考文章:常用函数式接口的介绍与使用

    更多相关内容
  • java函数编程

    2021-02-12 03:45:54
    java函数编程
  • 那么,为什么函数编程Java中很危险呢?也许这个疑问普遍存在于很多程序员的脑中,作者Elliotte对此发表了一些见解,我们一起来看看他是怎么说的。  在我的日常工作中,我身边的开发者大多是毕业于CS编程院校...
  • Java 8函数编程.pdf

    2021-08-12 18:31:19
    Java 8函数编程
  • Java 函数编程

    千次阅读 2022-04-25 17:18:16
    Java 函数编程 一、Lambda表达式 1.1 函数编程思想概述 在数学中,函数就是有输入量、输出量的一套计算方案,也就是“拿数据做操作” 面向对象思想强调“必须通过对象的形式来做事情” 函数式思想强调则金量忽略...

    Java 函数式编程

    一、Lambda表达式

    1.1 函数式编程思想概述

    在数学中,函数就是有输入量、输出量的一套计算方案,也就是“拿数据做操作”

    面向对象思想强调“必须通过对象的形式来做事情”

    函数式思想强调则金量忽略面向对象的复杂语句:“强调做什么,而不是以什么形式去做”

    而我们要学习的Lambda表达式就是函数式思想的体现

    1.2 体验Lambda表达式

    需求:启动一个线程,在控制台输出一句话:多线程程序启动了

    方式1:

    • 定义一个类MyRunnable接口,重写run方法
    • 创建MyRunnable类的对象
    • 创建Thread类对象,把MyRunnable的对象作为构造参数传递
    • 启动线程
    public class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("多线程程序启动了");
        }
    }
    
            MyRunnable myRunnable = new MyRunnable();
            Thread thread = new Thread(myRunnable);
            thread.start();
    

    方式2:

    • 在方式1的基础上进行改进,使用匿名内部类的方式
          new Thread(new Runnable() {
               @Override
                public void run() {
                    System.out.println("多线程程序启动了");
                }
            }).start();
    

    方式3:

    • Lambda表达式的方式改进
            new Thread(() -> {
                System.out.println("多线程程序启动了");
            }).start();
    

    1.3 Lambda表达式的标准格式

    匿名内部类中重写run()方法的代码分析:

    • 方法形式参数为空,说明调用方法时不需要传递参数
    • 方法返回值类型为void,说明方法执行没有结果返回
    • 方法体中的内容,是我们具体要做的事情
          new Thread(new Runnable() {
               @Override
                public void run() {
                    System.out.println("多线程程序启动了");
                }
            }).start();
    

    Lambda表达式的代码分析:

    • ():里面没有内容,可以看成是方法形式参数为空
    • ->:用箭头指向后面要做的事情
    • {}:包含一段代码,我们称之为代码块,可以看成是方法体中的内容
            new Thread(() -> {
                System.out.println("多线程程序启动了");
            }).start();
    

    组成Lambda表达式的三要素:形式参数、箭头、代码块

    Lambda表达式的格式:

    • 格式:(形式参数)->{代码块}
    • 形式参数:如果有多个参数,参数之间用逗号隔开;如果没有参数,留空即可
    • ->:由英文中画线和大于符号组成,固定写法。代表指向动作
    • 代码块:是我们具体要做的事情,也就是以前我们写的方法体内容

    1.4 Lambda表达式的练习

    Lambda表达式的使用前提

    • 有一个接口
    • 接口中有且仅有一个抽象方法

    练习1:

    • 定义一个接口(Eatable),里面定义一个抽象方法:void eat();
    • 定义一个测试类(EatableDemo),在测试类中提供两个方法:
      • 一个方法是:useEatable(Eatable e)
      • 一个方法是主方法,在主方法中调用useEatable方法

    定义一个接口:

    public interface Eatable {
        void eat();
    }
    
    • 方式一:传统接口实现类
    public class EatableImpl implements Eatable{
        @Override
        public void eat() {
            System.out.println("一日三餐,必不可少");
        }
    }
    
    public class EatableDemo{
        public static void main(String[] args) {
            Eatable eatable = new EatableImpl();
            eatable.eat();
        }
    
        private static void useEatable(Eatable eatable){
            eatable.eat();
        }
    }
    
    • 方式2:匿名内部类
    public class EatableDemo{
        public static void main(String[] args) {
            useEatable(new Eatable() {
                @Override
                public void eat() {
                    System.out.println("一日三餐,必不可少");
                }
            });
        }
    
        private static void useEatable(Eatable eatable){
            eatable.eat();
        }
    }
    
    • 方式3:Lambda表达式
    public class EatableDemo{
        public static void main(String[] args) {
            useEatable(()->{
                System.out.println("一日三餐,必不可少");
            });
        }
    
        private static void useEatable(Eatable eatable){
            eatable.eat();
        }
    }
    

    运行结果均相同

    练习2:

    • 定义一个接口(Flyable),里面定义一个抽象方法:void fiy(String s);
    • 定义一个测试类(FlyableDemo),在测试类中提供两个方法
      • 一个方法是:useFlyable(Flyable f)
      • 一个方法是主方法,在主方法中调用useFlayable方法
    public interface Flyable {
        void fly(String s);
    }
    
    public class FlyableDemo {
        public static void main(String[] args) {
    
            useFlyable(new Flyable() {
                @Override
                public void fly(String s) {
                    System.out.println(s);
                    System.out.println("飞机可以起飞");
                }
            });
            System.out.println("--------------------");
            useFlyable((String s)->{
                System.out.println(s);
                System.out.println("飞机可以起飞");
            });
        }
        private static void useFlyable(Flyable flyable){
            flyable.fly("风和日丽,晴空万里");
        }
    }
    

    练习3:

    • 定义一个接口(Addable),里面定义一个抽象方法:int add(int x,int y);
    • 定义一个测试类(AddableDemo),在测试类中提供两个方法
      • 一个方法是:useAddable(Addable a)
      • 一个方法是主方法,在主方法中调用useAddable方法
    public interface Addable {
        int add(int x,int y);
    }
    
    public class AddableDemo {
        public static void main(String[] args) {
            useAddable(new Addable() {
                @Override
                public int add(int x, int y) {
                    return x + y;
                }
            });
    
            useAddable((int x,int y)->{
                return x + y;
            });
        }
    
        private static void useAddable(Addable addable) {
            int sum = addable.add(10, 20);
            System.out.println(sum);
        }
    }
    

    1.5 Lambda表达式的省略模式

    省略规则:

    • 参数类型可以省略。如果有多个参数的情况下,不能只省略一个
    • 如果参数有且仅有一个,那么小括号可以省略
    • 如果代码块的语句只有一条,可以省略大括号和分号,甚至时return
    public class LambdaDemo5 {
        public static void main(String[] args) {
            //参数类型可以省略
            useAddable((x, y) -> {
                return x + y;
            });
            System.out.println("------------------------");
            //如果只有一个参数,小括号也可以省略
            useFlyable(s -> {
                System.out.println(s);
            });
            System.out.println("------------------------");
            //如果代码块的语句只有一条,可以省略大括号和分号(有return时要把return也去掉)
            useFlyable(s ->
                System.out.println(s)
            );
    
            useAddable((x,y)->x+y);
        }
    
        private static void useFlyable(Flyable flyable) {
            flyable.fly("风和日丽,晴空万里");
        }
    
        private static void useAddable(Addable addable) {
            int sum = addable.add(10, 20);
            System.out.println(sum);
        }
    }
    

    接口类参考1.4

    1.6 Lambda表达式的注意事项

    注意事项:

    • 使用Lambda必须要有接口,并且要求接口中有且仅有一个抽象的方法
    • 必须有上下文环境,才能推导出Lambda对应的接口
      • 根据局部变量的赋值得知Lambda对应的接口:Runnable r =() ->System.out.println(“Lambda表达式”);
      • 根据调用方法的参数得知Lambda对应的接口:new Thread(()->System.out.println(“Lambda表达式”)).start();
    public interface Inter {
        void show();
    }
    
    public class LambdaDemo6 {
        public static void main(String[] args) {
    
            useInter(()->
                    System.out.println("Lambda表达式")
            );
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("匿名内部类");
                }
            }).start();
    
            Runnable r = () -> System.out.println("Lambda表达式");
            new Thread(r).start();
    
            new Thread(()->
                System.out.println("Lambda表达式")
            ).start();
        }
    
        private static void useInter(Inter inter){
            inter.show();
        }
    }
    

    1.7 Lambda表达式和匿名内部类的区别

    所需类型不同:

    • 匿名内部类:可以是接口,也可以是抽象类,还可以是具体类
    • Lambda表达式:只能是接口

    使用限制不同:

    • 如果接口中有且仅有一个抽象方法,可以使用Lambda表达式,也可以使用匿名内部类
    • 如果接口中多于一个抽象方法,只能使用匿名内部类,而不能使用Lambda表达式

    实现原理不同:

    • 匿名内部类:编译之后,产生一个单独的.class字节码文件
    • Lambda表达式:编译之后,没有一个单独的.class字节码文件,对应的字节码会在运行的时候动态生成

    二、接口组成更新

    1.1 接口组成更新概述

    接口的组成

    • 常量:public static final
    • 抽象方法:public abstract
    • 默认方法(Java 8)
    • 静态方法(Java 8)
    • 私有方法 (Java 8)

    1.2 接口中默认方法

    接口中默认方法得定义格式:

    • 格式:public default 返回值类型 方法名(参数列表){}
    • 范例:public default void show3(){}

    接口中默认方法的注意事项:

    • 默认方法不是抽象方法,所以不强制被重写。但是可以被重写,重写的时候去掉default关键字
    • public可以省略,default不能重写
    public interface MyInterface {
        void show1();
    
        void show2();
    
        default void show3(){
            System.out.println("show3");
        }
    }
    
    public class MyInterfaceImplOne implements MyInterface{
        @Override
        public void show1() {
            System.out.println("One show1");
        }
    
        @Override
        public void show2() {
            System.out.println("One show2");
        }
    }
    
    public class MyInterfaceImplTwo implements MyInterface{
        @Override
        public void show1() {
            System.out.println("Two show1");
        }
    
        @Override
        public void show2() {
            System.out.println("Two show2");
        }
    }
    
    public class InterfaceDemo {
        public static void main(String[] args) {
            MyInterface myInterface = new MyInterfaceImplOne();
            myInterface.show1();
            myInterface.show2();
            myInterface.show3();
            System.out.println("------------------");
            MyInterface myInterface2 = new MyInterfaceImplTwo();
            myInterface2.show1();
            myInterface2.show2();
            myInterface2.show3();
        }
    }
    

    运行结果:

    One show1
    One show2
    show3
    ------------------
    Two show1
    Two show2
    show3
    

    1.3 接口中静态方法

    接口中静态方法的定义格式:

    • 格式:public static 返回值类型 方法名(参数列表){ }
    • 范例:public static void show(){ }

    接口中静态方法的注意事项:

    • 静态方法只能通过接口名调用,不能通过实现类名或者对象名调用
    • public可以省略,static不能省略
    public interface Inter {
    
        void show();
    
        default void method() {
            System.out.println("Inter 中的默认方法执行了");
        }
    
        public static void test(){
            System.out.println("Inter 中的静态方法执行了");
        }
    }
    
    public class InterImpl implements Inter{
        @Override
        public void show() {
            System.out.println("show方法执行了");
        }
    }
    
    public class InterDemo {                                           
        public static void main(String[] args) {                       
            Inter inter = new InterImpl();                             
            inter.show();                                             
            inter.method();                                           
            Inter.test();                                             
        }                                                             
    }                                                                                                        
    

    1.4 接口中私有方法

    Java 9中新增了带方法体的私有方法,这其实在Java 8中就埋下了伏笔:Java 8允许在接口中定义带方法体的默认方法和静态方法。这样可能就会引发一个问题:当两个默认方法或者静态方法中包含一段相同的代码实现时,程序必然考虑将这段实现代码抽取成一个共性方法,而这个共性方法时不需要让别人使用的,因此用私有给隐藏起来,这就是Java 9增加私有方法的必然性。

    接口中私有方法的定义格式:

    • 格式1:private 返回值类型方法名(参数列表){ }

    • 范例1:private void show(){ }

    • 格式2:private static 返回值类型 方法名(参数列表){ }

    • 范例2:private static void method(){ }

    接口中私有方法的注意事项:

    • 默认方法可以调用私有的静态方法和非静态方法
    • 静态方法只能调用私有的静态方法
    public interface Inter {
    
        default void show1(){
            System.out.println("show1开始执行");
            method();
            System.out.println("show1结束执行");
        }
    
        default void show2(){
            System.out.println("show2开始执行");
            method();
            System.out.println("show2结束执行");
        }
    
        static void method1(){
            System.out.println("method1开始执行");
            method();
            System.out.println("method1结束执行");
        }
    
        static void method2(){
            System.out.println("method2开始执行");
            method();
            System.out.println("method2结束执行");
        }
    
        static void method(){
            System.out.println("初级工程师");
            System.out.println("中级工程师");
            System.out.println("高级工程师");
        }
    }
    
    public class InterImpl implements Inter{
    }
    
    public class InterDemo {
        public static void main(String[] args) {
            Inter inter = new InterImpl();
            inter.show1();
            System.out.println("------------------------");
            inter.show2();
            System.out.println("------------------------");
            Inter.method1();
            System.out.println("------------------------");
            Inter.method2();
        }
    }
    

    三、方法引用

    1.1 体验方法引用

    通过方法引用来使用已经存在的方案

    public interface Printable {
        void printString(String s);
    }
    
    public class PrintableDemo {
        public static void main(String[] args) {
            usePrintable(s->
                System.out.println(s)
            );
    
            usePrintable(System.out::println);
        }
        private static void usePrintable(Printable p){
            p.printString("hello world");
        }
    }
    

    1.2 方法引用符

    • ::该符号为引用运算符,而它所在表达式被称为方法引用符

    • Lambda表达式:usePrintable(s->System.out.println(s));

      分析:拿到参数s之后通过Lambda表达式,传递给System.out.println方法去处理

    • 方法引用:usePrintable(System.out::println);

    ​ 分析:直接使用System.out中的println方法来取代Lambda,代码更加的简洁

    推导与省略:

    • 如果使用Lambda,那么根据“可推导就是可省略”的原则,无需指定参数类型,也无需指定的重载形式,它们都将被自动推导
    • 如果使用方法引用,也同要可以根据上下文进行推导
    • 方法引用是Lambda的孪生兄弟
    public interface Printable {
        void printInt(int i);
    }
    
    public class PrintableDemo2 {
        public static void main(String[] args) {
            usePrintable(i -> System.out.println(i));
    
            usePrintable(System.out::println);
        }
    
        private static void usePrintable(Printable printable){
            printable.printInt(1);
        }
    }
    

    1.3 Lambda表达式支持的方法引用

    常见的引用方式:

    • 引用类方法
    • 引用对象的实例方法
    • 引用类的实例方法
    • 引用构造器

    1.4 引用类方法

    引用类方法,其实就是引用类的静态方法

    • 格式:类名::静态方法
    • 范例:Integer::parseInt
      • Integer类的方法:public static int parsenInt(String s),将此String转换为int类型数据
    • Lambda表达式被类方法替代的时候,它的形式参数全部传递给静态方法作为参数
    public interface Converter {
        int convert(String s);
    }
    
    public class ConverterDemo {
        public static void main(String[] args) {
            useConverter(s -> Integer.parseInt(s));
    
            useConverter(Integer::parseInt);
        }
    
        private static void useConverter(Converter c) {
            int number = c.convert("666");
            System.out.println(number);
        }
    
    }
    

    1.5 引用对象的实例方法

    引用对象的实例方法,其实就是引用类中的成员方法

    • 格式:对象::成员方法

    • 范例:“HelloWorld"::toUpperCase

    • String 类中的方法:public String toUpperCase()将此String所有字符转为大写

    • Lambda表达式被对象的实例方法替代的时候,它的形式参数全部传递给该方法作为参数

    public interface Printer {
        void printUpperCase(String s);
    }
    
    public class PrintString {
        public void printUpper(String s){
            String result = s.toUpperCase();
            System.out.println(result);
        }
    }
    
    public class PrinterDemo {
        public static void main(String[] args) {
            usePrinter(s -> System.out.println(s.toUpperCase()));
    
            PrintString printString = new PrintString();
            usePrinter(printString::printUpper);
        }
    
        private static void usePrinter(Printer printer) {
            printer.printUpperCase("HelloWorld");
        }
    }
    

    1.6 引用类的实例方法

    引用类的实例方法,其实就是引用类中的成员方法

    • 格式:类名::成员方法

    • 范例:String::substring

    • String类中的方法:public String subString(int beginIndex,int endIndex)从beginIndex开始到endIndex结束,截取字符串。返回一个子串,字串的长度为endIndex-beginIndex

    • Lambda表达式被类的实例方法替代的时候,第一个参数作为调用者,后面的参数全部传递给该方法作为参数

    public interface MyString {
        String mySubString(String s, int x, int y);
    }
    
    public class MyStringDemo {
        public static void main(String[] args) {
            useMyString((s, x, y) -> s.substring(x, y));
    
            useMyString(String::substring);
        }
    
        private static void useMyString(MyString myString){
            String s = myString.mySubString("HelloWorld", 5, 10);
            System.out.println(s);
        }
    }
    

    1.7 引用构造器

    引用构造器,其实就是引用构造方法

    • 格式:类名::new
    • 范例:Student::new
    • Lambda表达式被构造器代替的时候,它的形式参数全部传递给构造器作为参数
    public class Student {
        private String name;
        private int age;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public Student() {
        }
    
        public Student(String name, int age) {
            this.name = name;
            this.age = age;
        }
    }
    
    public interface StudentBuilder {
        Student build(String name,int age);
    }
    
    public class StudentDemo {
        public static void main(String[] args) {
            useStudentBuilder((name, age) -> new Student(name,age));
    
            useStudentBuilder(Student::new);
        }
    
        private static void useStudentBuilder(StudentBuilder studentBuilder){
            Student student = studentBuilder.build("xuanxuan", 22);
            System.out.println(student.getName()+","+student.getAge());
        }
    }
    

    四、函数式接口

    1.1 函数接口概述

    函数式接口:有且仅有一个抽象方法的接口

    Java中的函数式编程体现就是Lambda表达式,所以函数式接口就是可以使用于Lambda使用的接口

    只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导

    如何检测一个接口是不是函数式接口呢?

    • @FunctionalInterface
    • 放在接口定义的上方:如果接口是函数接口,编译通过;如果不是,编译失败

    注意:

    • 我们自己定义函数式接口的时候,@FunctionalInterface是可选的,就算我们不写这个注解,只要保证满足函数式接口定义的条件,也照样是函数式接口。但是,建议加上注解。
    @FunctionalInterface
    public interface MyInterface {
        void show();
    }
    
    public class MyInterfaceDemo {
        public static void main(String[] args) {
            MyInterface myInterface = ()-> System.out.println("函数式接口");
            myInterface.show();
        }
    }
    

    1.2 函数式接口作为方法的参数

    如果方法的参数是一个函数式接口,我们可以使用Lambda表达式作为参数传递

    startThread(() -> System.out.println(Thread.currentThread().getName() + "线程启动了"));
    
    
    public class RunnableDemo {
        public static void main(String[] args) {
            startThread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "线程启动了");
                }
            });
    
            startThread(() -> System.out.println(Thread.currentThread().getName() + "线程启动了"));
    
            startThread(()->{
                System.out.println(Thread.currentThread().getName() + "线程启动了");
            });
        }
    
        private static void startThread(Runnable runnable) {
            new Thread(runnable).start();
        }
    }
    
    

    1.3 函数式接口作为方法的返回值

    如果方法的返回值是一个函数式接口,我们可以使用Lambda表达式作为结果返回

    private static Comparator<String> getComparator() {
            return (s1,s2) -> s1.length() - s2.length();
        }
    
    public class ComparatorDemo {
        public static void main(String[] args) {
            ArrayList<String> arrayList = new ArrayList<String>();
    
            arrayList.add("ccc");
            arrayList.add("aa");
            arrayList.add("dddd");
            arrayList.add("b");
            System.out.println("排序前" + arrayList);
            Collections.sort(arrayList);
            System.out.println("排序后" + arrayList);
            Collections.sort(arrayList, getComparator());
            System.out.println("使用定义比较器排序方法后:" + arrayList);
        }
    
        private static Comparator<String> getComparator() {
    //        return new Comparator<String>() {
    //            @Override
    //            public int compare(String s1, String s2) {
    //                return s1.length() - s2.length();
    //            }
    //        };
            return (s1,s2) -> s1.length() - s2.length();
        }
    }
    

    1.4 常用的函数式接口

    Java 8 在java.util.function包下预定了大量的函数式接口供我们使用,常用如下:

    • Supplier接口
    • Consumer接口
    • Predicate接口
    • Function接口

    1.5 Supplier接口

    Supplier接口

    • T get():获得结果
    • 该方法不需要参数,它会按照某种实现逻辑(由Lambda表达式实现)返回一个数据
    • Supplier 接口也被称为生产型接口,如果我们指定了接口的泛型是什么类型,那么接口中的get方法就会生产什么类型的数据供我们使用
    public class SupplierDemo {
        public static void main(String[] args) {
            String s = getString(() -> "xuanxuan");
            System.out.println(s);
    
            Integer i = getInteger(() -> 666);
            System.out.println(i);
        }
    
        public static String getString(Supplier<String> supplier) {
            return supplier.get();
        }
    
        public static Integer getInteger(Supplier<Integer> supplier) {
            return supplier.get();
        }
    }
    

    练习:获取最大值

    public class SupplierDemo {
        public static void main(String[] args) {
            int[] arr = new int[]{17, 28, 49, 21, 32, 66};
            int maxNumber = getMax(() -> {
                int max = arr[0];
                for (int i = 1; i < arr.length; i++) {
                    if (max < arr[i]) {
                        max = arr[i];
                    }
                }
                return max;
            });
            System.out.println("数组中的最大值是:" + maxNumber);
        }
    
        private static int getMax(Supplier<Integer> supplier) {
            return supplier.get();
        }
    }
    

    1.6 Consumer接口

    Consumer:包含两个方法

    • void accept(T t):对给定的参数执行此操作
    • default Consumer andThen(Consumer after):返回一个组合的Consumer,依次执行此操作,然后执行after操作
    • Consumer 接口也被称为消费型接口,它消费的数据类型由泛型指定
    public class ConsumerDemo {
        public static void main(String[] args) {
            operatorString("abc", s -> System.out.println(s));
            operatorString("abc", System.out::println);
            operatorString("abc", s -> System.out.println(new StringBuilder(s).reverse().toString()));
            System.out.println("----------------------------------");
            operatorString("abc", s -> System.out.println(s), s -> System.out.println(new StringBuilder(s).reverse().toString()));
        }
    
        private static void operatorString(String name, Consumer<String> consumer) {
            consumer.accept(name);
        }
    
        private static void operatorString(String name, Consumer<String> consumer1, Consumer<String> consumer2) {
    //        consumer1.accept(name);
    //        consumer2.accept(name);
            consumer1.andThen(consumer2).accept(name);
        }
    }
    

    练习:

    字符串数组中又多条信息,按照:“姓名:name,年龄:age"的格式将信息打印出来

    public class ConsumerDemo {
        public static void main(String[] args) {
            String[] arr = new String[]{"abc,30", "cbd,35", "dna,33"};
            printInfo(arr, s -> System.out.print("姓名:" + s.split(",")[0] + ","), s -> System.out.println("年龄:" + Integer.parseInt(s.split(",")[1])));
        }
    
        private static void printInfo(String[] arr, Consumer<String> consumer1, Consumer<String> consumer2) {
            for (String s : arr) {
                consumer1.andThen(consumer2).accept(s);
            }
        }
    }
    

    1.7 Predicate接口

    常用方法:

    方法说明
    boolean test(T t)对给定的参数进行判断(判断逻辑由Lambda表达式实现),返回一个布尔值
    default Predicate negate()返回一个逻辑的否定,对应逻辑非
    default Predicate and (Predicate other)返回一个组合判断,对应短路与
    default Predicate or (Predicate other)返回一个组合判断,对应短路或

    练习:判断给定的字符串是否满足要求

    public class PredicateDemo {
        public static void main(String[] args) {
            boolean b1 = checkString("hello", s -> s.length() > 5);
            System.out.println(b1);
    
            boolean b2 = checkString("helloworld", s -> s.length() > 8);
            System.out.println(b2);
    
            boolean b3 = checkString("hello", s -> s.length() > 5, s -> s.length() > 8);
            System.out.println(b3);
    
            boolean b4 = checkString("helloworld", s -> s.length() > 5, s -> s.length() > 8);
            System.out.println(b4);
        }
    
        private static boolean checkString(String s, Predicate<String> predicate) {
            return predicate.test(s);
        }
    
        private static boolean checkString(String s, Predicate<String> predicate, Predicate<String> predicate2) {
    //        return predicate.and(predicate2).test(s);
            return predicate.or(predicate2).test(s);
        }
    }
    

    练习2:

    • String[] strArray ={“孙悟空,30”,“唐僧,36”,“沙僧,34”,“猪八戒,32”,“白骨精,5000”}
    • 字符串数组中有多条信息,请通过Predicate接口的拼装将符合要求的字符串筛选到集合ArrayLitst中,并遍历ArrayLitst集合
    • 同时满足如下要求:name长度大于2,age大于33
    public class PredicateDemo3 {
        public static void main(String[] args) {
            String[] strArray = new String[]{"孙悟空,30", "唐僧,36", "沙僧,34", "猪八戒,32", "白骨精,5000"};
            ArrayList<String> arrayList = myFilter(strArray, s -> s.split(",")[0].length() > 2, s -> Integer.parseInt(s.split(",")[1]) > 33);
            System.out.println("name长度大于2,age大于33有:");
            for (String s : arrayList) {
                System.out.print("name:" + s.split(",")[0] + ",");
                System.out.println("age:" + Integer.parseInt(s.split(",")[1]));
            }
        }
    
        private static ArrayList<String> myFilter(String[] strArray, Predicate<String> predicate1, Predicate<String> predicate2) {
            ArrayList<String> arrayList = new ArrayList<String>();
            for (String s : strArray) {
                if (predicate1.and(predicate2).test(s)) {
                    arrayList.add(s);
                }
            }
            return arrayList;
        }
    }
    

    1.8 Function接口

    Function<T,R>两个常用方法:

    方法说明
    R apply(T t)将此函数应用于给定的参数。
    default Function<T,V> andThen(Function after)返回一个组合函数,首先将该函数应用于其输入,然后将 after函数应用于结果。
    • Function<T,R>接口通常用于对参数进行处理,转换(处理逻辑由Lambda表达式实现)然后返回一个新的值

    练习:

    public class FunctionDemo {
        public static void main(String[] args) {
            convert("100", s -> Integer.parseInt(s));
            convert("100", Integer::parseInt);
    
            convert(100, i -> String.valueOf(100 + i));
    
            convert("100", s -> Integer.parseInt(s), i -> String.valueOf(i + 566));
    
        }
    
        //定义一个方法,把一个int类型的数据加上一个整数之后,转为字符串在控制台输出
        private static void convert(String s, Function<String, Integer> function) {
            Integer i = function.apply(s);
            System.out.println(i);
        }
    
        //定义一个方法,把一个int类型的数据加上一个整数之后,转为字符串在控制台输出
        private static void convert(int i, Function<Integer, String> function) {
            String s = function.apply(i);
            System.out.println(s);
        }
    
        //定义一个方法,把一个字符串转换为int类型,把int类型的数据加上一个整数之后,转为字符串在控制台输出
        private static void convert(String s, Function<String, Integer> function1, Function<Integer, String> function2) {
            String ss = function2.apply(function1.apply(s));
            System.out.println(ss);
        }
    }
    

    练习2:提取String中的年龄加70岁,并以int型输出

    public class FunctionDemo {
        public static void main(String[] args) {
            String s = "孙悟空,30";
            convert(s, s1 -> s1.split(",")[1], s1 -> Integer.parseInt(s1) + 70);
        }
    
        private static void convert(String s, Function<String, String> function1, Function<String, Integer> function2) {
            Integer i = function2.apply(function1.apply(s));
            System.out.println(i);
        }
    }
    

    五、Stream流

    1.1 体验Stream流

    需求:按照下面的要求完成集合的创建和遍历

    • 创建一个集合,存储多个字符串元素
    • 把集合中所有以“张”开头的元素存储到一个新的集合
    • 再把长度为3的元素存储到一个新集合
    • 最后遍历上一步得到的集合

    使用Stream流的方式完成过滤操作:

    • 直接阅读代码的字面意思即可完美展示无关逻辑方式的语义:生成流、过滤姓氏、过滤长度为3、逐一打印
    • Stream流把真正的函数式编程风格引入到java中
    list.stream().filter(s -> s.startsWith("张")).filter(s -> s.length() == 3).forEach(s -> System.out.println(s));
    
    public class StreamDemo {
        public static void main(String[] args) {
            ArrayList<String> list = new ArrayList<String>();
            list.add("张飞");
            list.add("张三丰");
            list.add("张三");
            list.add("李四");
            list.add("孙悟空");
            list.add("张一飞");
    
            ArrayList<String> zhangList = new ArrayList<String>();
            for (String s : list) {
                if (s.startsWith("张")) {
                    zhangList.add(s);
                }
            }
            ArrayList<String> treeList = new ArrayList<String>();
            for (String s : zhangList) {
                if (s.length() == 3) {
                    treeList.add(s);
                }
            }
            for (String s : treeList) {
                System.out.println(s);
            }
            System.out.println("-------------------------------");
            //Stream流改进
            list.stream().filter(s -> s.startsWith("张")).filter(s -> s.length() == 3).forEach(s -> System.out.println(s));
        }
    }
    

    1.2 Stream流的生成方式

    Stream流的使用

    • 生成流:通过数据源(集合、数组等)生成流
    list.stream();
    
    • 中间操作:一个流后面可以跟随零个或者多个中间操作,其目的主要是打开流,做出某种程度的数据过滤/映射,然后返回一个新的流,交给下一个操作使用
    filter()
    
    • 终结操作:一个流只能有一个终结操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作
    forEach()
    

    Stream流的常见生成方式

    • Collection体系的集合可以使用默认方法stream()生成流
    default Stream<E> stream()
    
    • Map体系的集合间接的生成流
    • 数组可以通过Stream接口的静态方法of(T…values)生成流
    public class StreamDemo {
        public static void main(String[] args) {
            List<String> list = new ArrayList<String>();
            Stream<String> listStream = list.stream();
    
            Set<String> set = new HashSet<String>();
            Stream<String> setStream = set.stream();
    
            Map<String, Integer> map = new HashMap<String, Integer>();
            Stream<String> keyStream = map.keySet().stream();
            Stream<Integer> valueStream = map.values().stream();
            Stream<Map.Entry<String, Integer>> entryStream = map.entrySet().stream();
    
            String[] strArray = {"hello", "world", "java"};
            Stream<String> strArrayStream = Stream.of(strArray);
            Stream<String> strArrayStream2 = Stream.of("hello", "world", "java");
            Stream<Integer> strArrayStream3 = Stream.of(10, 20, 30);
        }
    }
    

    1.3 Stream流的常见中间操作方法

    • Stream filter(Predicate predicate):用于对流中的数据进行过滤
    • Predicate接口中的方法:boolean test(T t):对给定的参数进行判断,返回一个布尔值
    public class StreamDemo {
        public static void main(String[] args) {
            ArrayList<String> list = new ArrayList<String>();
            list.add("张飞");
            list.add("张三丰");
            list.add("张三");
            list.add("李四");
            list.add("孙悟空");
            list.add("张一飞");
            list.stream().filter(s -> s.startsWith("张")).forEach(System.out::println);
            System.out.println("----------------------");
            list.stream().filter(s -> s.length() == 3).forEach(System.out::println);
            System.out.println("----------------------");
            list.stream().filter(s -> s.startsWith("张")).filter(s -> s.length() == 3).forEach(System.out::println);
        }
    }
    
    • Stream limit(long maxSize):返回此流中的元素组成的流,截取前指定参数个数的数据
    • Stream skip(long n):跳过指定参数个数的数据,返回由该流的剩余元素组成的流
    public class StreamDemo {
        public static void main(String[] args) {
            ArrayList<String> list = new ArrayList<String>();
            list.add("张飞");
            list.add("张三丰");
            list.add("张三");
            list.add("李四");
            list.add("孙悟空");
            list.add("张一飞");
            //取前三个数据在控制台输出
            list.stream().limit(3).forEach(System.out::println);
            System.out.println("-----------------------------");
            //跳过2个元素,把剩下的元素在控制台上输出
            list.stream().skip(2).forEach(System.out::println);
            System.out.println("-----------------------------");
            //跳过2个元素并将剩下元素的前两个元素在控制台上输出
            list.stream().skip(2).limit(2).forEach(System.out::println);
        }
    }
    
    • Stream Stream concat(Stream a,Stream b):合并a和b两个流为一个流
    • Stream distinct:返回由该流的不同元素(根据Objectequals(Object))组成的流
    public class StreamDemo {
        public static void main(String[] args) {
            ArrayList<String> list = new ArrayList<String>();
            list.add("张飞");
            list.add("张三丰");
            list.add("张三");
            list.add("李四");
            list.add("孙悟空");
            list.add("张一飞");
            //需求1:取前4个数据组成一个流
            Stream<String> limitStream = list.stream().limit(4);
            //需求2:跳过2个数据组成一个流
            Stream<String> skipStream = list.stream().skip(2);
            //需求3:合并需求1和需求2得到的流,并把结果在控制台输出
    //        Stream.concat(limitStream,skipStream).forEach(System.out::println);
            //需求4:合并需求1和需求2得到的流,并把结果在控制台输出,要求字符串元素不能重复
            Stream.concat(limitStream,skipStream).distinct().forEach(System.out::println);
        }
    }
    
    • Stream sorted():返回由此流的元素组成的流,根据自然顺序排序
    • Stream sorted(Comparator comparator):返回由该流的元素组成的流,根据提供的Comparator进行排序
    public class StreamDemo {
        public static void main(String[] args) {
            ArrayList<String> list = new ArrayList<String>();
            list.add("zhangfei");
            list.add("zhangsanfeng");
            list.add("zhangsan");
            list.add("lisi");
            list.add("sunwukong");
            list.add("zhangyifei");
    
            //需求1:按照字母顺序把数据在控制台输出
            list.stream().sorted().forEach(System.out::println);
            //需求2:按照字符串长度把数据在控制台输出
            list.stream().sorted((s1, s2) -> {
                int num = s1.length() - s2.length();
                int num2 = num == 0 ? s1.compareTo(s2) : num;
                return num2;
            }).forEach(System.out::println);
        }
    }
    
    
    • Stream map(Function mapper):返回由给定函数应用于此流的元素的结果组成的流(Function接口中的方法 R apply(T t))
    • IntStream mapToInt(ToIntFunction mapper):返回一个IntStream其中包含将给定函数应用于此流的元素的结果
    public class StreamDemo {
        public static void main(String[] args) {
            ArrayList<String> list = new ArrayList<String>();
            list.add("10");
            list.add("20");
            list.add("30");
            list.add("40");
            list.add("50");
    
    //        list.stream().map(s -> Integer.parseInt(s)).forEach(System.out::println);
            list.stream().map(Integer::parseInt).forEach(System.out::println);
    
            list.stream().mapToInt(Integer::parseInt).forEach(System.out::println);
    
            int result = list.stream().mapToInt(Integer::parseInt).sum();
            System.out.println(result);
        }
    }
    

    1.4 Stream流的常见终结操作方法

    • void forEach(Consumer action):对此流的每个元素执行操作(Consumer接口中的方法 void accept(T t):对给定的参数执行此操作)
    • long count():返回此流中的元素数
    public class StreamDemo {
        public static void main(String[] args) {
            ArrayList<String> list = new ArrayList<String>();
            list.add("张飞");
            list.add("张三丰");
            list.add("张三");
            list.add("李四");
            list.add("孙悟空");
            list.add("张一飞");
    
            //需求1:把集合中的元素在控制台输出
            list.stream().forEach(System.out::println);
    
            //需求2:统计集合中有几个姓张的元素并在控制台输出
            list.stream().filter(s -> s.startsWith("张")).forEach(System.out::println);
        }
    }
    

    1.5 Stream流的练习

    现在又两个ArrayList集合,分别存储6名男演员和6名女演员名称,要求完成如下操作

    • 男演员只要名字为3个字的前三人
    • 女演员只要姓林的,并且不要第一个
    • 把过滤后的男演员姓名和女演员姓名合并到一起
    • 把上一步操作后的元素作为构造方法的参数创建演员对象,遍历数据(演员类Actor已经提供,里面有一个成员变量,一个带参构造方法,以及成员变量对应的get/set方法)
    public class StreamDemo {
        public static void main(String[] args) {
            ArrayList<String> manList = new ArrayList<String>();
            manList.add("周润发");
            manList.add("成龙");
            manList.add("刘德华");
            manList.add("吴京");
            manList.add("周星驰");
            manList.add("李连杰");
    
            ArrayList<String> womanList = new ArrayList<String>();
            womanList.add("林心如");
            womanList.add("张曼玉");
            womanList.add("林青霞");
            womanList.add("柳岩");
            womanList.add("林志玲");
            womanList.add("王祖贤");
    
            //男演员只要名字为3个字的前三人
            Stream<String> manStream = manList.stream().filter(s -> s.length() == 3).limit(3);
    
            //女演员只要姓林的,并且不要第一个
            Stream<String> womanStream = womanList.stream().filter(s -> s.startsWith("林")).skip(1);
    
            //把过滤后的男演员姓名和女演员姓名合并到一起
            Stream<String> stream = Stream.concat(manStream, womanStream);
    
            //把上一步操作后的元素作为构造方法的参数创建演员对象,遍历数据
            stream.map(Actor::new).forEach(p -> System.out.println(p.getName()));
            System.out.println("------------------------------------");
            //改进
            Stream.concat(manList.stream().filter(s -> s.length() == 3).limit(3), womanList.stream().filter(s -> s.startsWith("林")).skip(1)).map(Actor::new).forEach(p -> System.out.println(p.getName()));
        }
    }
    
    

    1.6 Stream流的收集操作

    对数据使用Stream流的方式操作完毕后,如何把流中的数据收集到集合中?

    Stream流的手机方法

    • R collect(Collector collector)
    • 但是这个收集方法的参数是一个Collector接口

    工具类Collectors提供了具体的收集方式

    • public static Collector toList():把元素收到List集合中
    • public static Collector toSet():把元素收集到Set集合中
    • public static Collector toMap(Function keyMapper,Function valueMapper):把元素收集到Map集合中
    public class StreamDemo {
        public static void main(String[] args) {
            ArrayList<String> list = new ArrayList<String>();
            list.add("张飞");
            list.add("张三丰");
            list.add("张三");
            list.add("李四");
            list.add("孙悟空");
            list.add("张一飞");
    
            //需求1:得到名字为3个字的流
            Stream<String> listStream = list.stream().filter(s -> s.length() == 3);
            //需求2:把使用Stream流操作完毕的数据收集到List集合中并遍历
            List<String> collect = listStream.collect(Collectors.toList());
            for (String s : collect) {
                System.out.println(s);
            }
    
            Set<Integer> set = new HashSet<Integer>();
            set.add(10);
            set.add(20);
            set.add(30);
            set.add(33);
            set.add(35);
    
            //需求3:得到年龄大于25的流
            Stream<Integer> integerStream = set.stream().filter(age -> age > 25);
    
            //需求4:把使用Stream流操作完毕的数据收集到Set集合中并遍历
            Set<Integer> collect2 = integerStream.collect(Collectors.toSet());
            for (Integer i : collect2) {
                System.out.println(i);
            }
    
            String[] strArray = {"张飞,28", "张三丰,33", "张三,26", "李四,44"};
    
            //需求5:得到字符串年龄中数据大于28的流
            Stream<String> stringStream = Stream.of(strArray).filter(s -> Integer.parseInt(s.split(",")[1]) > 28);
    
            //需求6:把使用Stream流操作完毕的数据收集到Map集合中并遍历,字符串的姓名作为键,年龄作为值
            Map<String, Integer> map = stringStream.collect(Collectors.toMap(s -> s.split(",")[0], s -> Integer.parseInt(s.split(",")[1])));
            Set<String> keySet = map.keySet();
            for (String key : keySet) {
                Integer value = map.get(key);
                System.out.println(key + "," + value);
            }
        }
    }
    

    “山高水长,怕什么来不及,慌什么到不了,天顺其然,地顺其性,人顺其变,一切都是刚刚好。”

    展开全文
  • java函数编程入门

    2020-12-21 01:43:18
    以前写过一篇java8的流操作,人们都说流操作是函数编程,但函数编程是什么呢? 什么是函数编程 什么是函数编程?它是一种编程范式,即一切都是数学函数函数编程语言里也可以有对象,但通常这些对象都是...
  • Java函数编程详解

    万次阅读 多人点赞 2019-05-05 21:46:49
    1.Java函数编程的语法: 使用Consumer作为示例,它是一个函数式接口,包含一个抽象方法accept,这个方法只有输入而无输出也就是说这个方法无返回值。 现在我们要定义一个Consumer接口的实例化对象,传统的方式是....

    Java从1.8以后引入了函数式编程,这是很大的一个改进。函数式编程的优点在提高编码的效率,增强代码的可读性。本文历时两个多月一点点写出来,即作为心得,亦作为交流。

    1.Java函数式编程的语法:

    使用Consumer作为示例,它是一个函数式接口,包含一个抽象方法accept,这个方法只有输入而无输出也就是说这个方法无返回值。 
    现在我们要定义一个Consumer接口的实例化对象,传统的方式是这样定义的:

    public static void main(String[] args) {
            //JDK1.8版本之前的做法
            Consumer<Integer> con = new Consumer<Integer>() {
                @Override
                public void accept(Integer t) {
                    System.out.println(t);
                }
            };
            //调用方法
            con.accept(3);
        }

    这里可以打印出3.

    上面是JDK1.8以前的做法,在1.8以后引入函数式的编程可以这样写:

    public static void main(String[] args) {
            //第一种写法:
            Consumer<Integer> con = (param) -> {System.out.println(param);}; 
            //第二种写法:
            Consumer<Integer> con1 = (param) -> System.out.println(param);
            //第三种写法:
            Consumer<Integer> con2 = System.out::println;
            
            con2.accept(3);
        }

    上面的con、con1、con2这个Consumer对象的引用照样可以打印出3.

    在上面的第二种写法是第一种写法的精简,但是只有在函数题中只有一条语句时才可以这么使用,做进一步的简化。

    在上面的第三种写法是对第一、二种写法的进一步精简,由于第三种写法只是进行打印,调用了System.out中的println静态方法对输入参数直接进行打印。它表示的意思就是针对输入的参数将其调用System.out中的静态方法println进行打印。

    上面已说明,函数式编程接口都只有一个抽象方法,因此在采用这种写法时,编译器会将这段函数编译后当作该抽象方法的实现。 
    如果接口有多个抽象方法,编译器就不知道这段函数应该是实现哪个方法的了。 
    因此,=  后面的函数体我们就可以看成是accept函数的实现。

    输入:-> 前面的部分,即被()包围的部分。此处只有一个输入参数,实际上输入是可以有多个的,如两个参数时写法:(a, b);当然也可以没有输入,此时直接就可以是()。
    函数体:->后面的部分,即被{}包围的部分;可以是一段代码。
    输出:函数式编程可以没有返回值,也可以有返回值。如果有返回值时,需要代码段的最后一句通过return的方式返回对应的值。但是Consumer这个函数式接口不能有具体的返回值。

    Java 8 中我们可以通过 `::` 关键字来访问类的构造方法,对象方法,静态方法。

    好了,到这一步就可以感受到函数式编程的强大能力。 
    通过最后一段代码,我们可以简单的理解函数式编程,Consumer接口直接就可以当成一个函数了,这个函数接收一个输入参数,然后针对这个输入进行处理;当然其本质上仍旧是一个对象,但我们已经省去了诸如老方式中的对象定义过程,直接使用一段代码来给函数式接口对象赋值。 
    而且最为关键的是,这个函数式对象因为本质上仍旧是一个对象,因此可以做为其它方法的参数或者返回值,可以与原有的代码实现无缝集成!


    2.Java函数式接口

    java.util.function.Consumer;
    java.util.function.Function;
    java.util.function.Predicate;

    2.1 Consumer是一个函数式编程接口; 顾名思义,Consumer的意思就是消费,即针对某个东西我们来使用它,因此它包含有一个有输入而无输出(无返回值)的accept接口方法; 
    除accept方法,它还包含有andThen这个方法; 

    JDK源码定义如下:

    default Consumer<T> andThen(Consumer<? super T> after) {
            Objects.requireNonNull(after);
            return (T t) -> { accept(t); after.accept(t); };

     }

    andThen这个方法是作用是:指定在调用当前Consumer后是否还要调用其它的Consumer; 

    public static void main(String[] args) {
            //定义第一个Consumer
            Consumer<Integer> consumer1 = (param) -> {System.out.println(param);};
            
            //定义第二个Consumer
            Consumer<Integer> consumer2 = (param) -> {System.out.println(param * param);};
            
            //consumer1可以连续的调用自己
            //consumer1.andThen(consumer1).andThen(consumer1).accept(3);
            //打印出 3 3 3
            
            //consumer1可以调用自己后调用consumer2
            consumer1.andThen(consumer1).andThen(consumer2).accept(3);
            //打印出3 3 9
        }

    注意:当一个Consumer接口调用另外一个Consumer对象时两个Counsumer对象的泛型必须一致。

    2.2  Function也是一个函数式编程接口;它代表的含义是“函数”,它是个接受一个参数并生成结果的函数,而函数经常是有输入输出的,因此它含有一个apply方法,包含一个输入与一个输出; 
    除apply方法外,它还有compose与andThen及indentity三个方法,其使用见下述示例;

    apply的用法:

    public static void main(String[] args) {
            Function<Integer, Integer> fun = res -> res + 1;
            Integer  i = fun.apply(2);
            System.out.println(i);
    }

    上面打印出2。

    Function编程接口有两个泛型Function<T, R> T表示:函数的输入类型,R表示:函数的输出类型。

    compose的用法:

    JDK源码定义:

    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
            Objects.requireNonNull(before);
            return (V v) -> apply(before.apply(v));

     }

    public static void main(String[] args) {
            Function<Integer, Integer> fun = res -> res + 1;
            Function<Integer, Integer> fun1 = res -> res * 10;
            Integer i = (Integer) fun.compose(fun1).apply(2);
            System.out.println(i);
    }

    上面打印出21;

    上面表示了fun2接收到2以后先计算,然后将fun2计算的结果再交给fun再计算。

    andThen方法的用法:

    JDK源码定义:

    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
            Objects.requireNonNull(after);
            return (T t) -> after.apply(apply(t));

     }

    public static void main(String[] args) {
            Function<Integer, Integer> fun = res -> res + 1;
            Function<Integer, Integer> fun1 = res -> res * 10;
            Integer i = (Integer) fun.andThen(fun1).apply(3);
            System.out.println(i);
     }

    上面打印出40

    上面表示了fun先计算得到4,然后将计算结果4再给fun1计算得到40;

    indentity方法的用法:

    JDK源码定义:

    static <T> Function<T, T> identity() {
            return t -> t;
     }

    public static void main(String[] args) {
            System.out.println(Function.identity().apply("总分"));

     }

    上面打印出“总分“;就是说传入什么参数输出什么参数。

    2.3 Predicate为函数式接口,predicate的中文意思是“断定”,即判断的意思,判断某个东西是否满足某种条件; 因此它包含test方法,根据输入值来做逻辑判断,其结果为True或者False。 

    test方法的用法:

    JDK源码定义: boolean test(T t);

    public static void main(String[] args) {
            Predicate<String> pre = res -> res.equals("1234");
            boolean rest = pre.test("1234");
            System.out.println(rest);
     }

    上面打印出true。

    and方法的用法:

    JDK源码定义:

    default Predicate<T> and(Predicate<? super T> other) {
            Objects.requireNonNull(other);
            return (t) -> test(t) && other.test(t);
     }

    public static void main(String[] args) {
            Predicate<String> pre = res -> res.equals("1234");
            Predicate<String> pre1 = res -> res.equals("1234");
            Predicate<String> pre3 = res -> res.equals("1234");
            boolean rest = pre.and(pre1).test("1234");
            boolean rest1 = pre.and(pre1).test("12341");
            System.out.println(rest);
            System.out.println(rest1); 

    }
    上面先打印出true,后打印出false

    根据源码,"1234"先和pre比较,如果为true,再和pre1比较。不为true直接返回false,不再和pre1比较。当“1234”和pre比较后为true,再和pre1比较结果为true,所以最终返回true。

    negate()的用法:

    JDK源码定义:

    default Predicate<T> negate() {
            return (t) -> !test(t);
     }

    public static void main(String[] args) {
            Predicate<String> pre = res -> res.equals("1234");
            boolean rest = pre.negate().test("1234");
            System.out.println(rest);
    }

    上面打印出false,上面的意思是如果比较的结果是true,那么返回false,如果为false,就返回true。

    or的方法的用法:

    JDK源码定义:

    default Predicate<T> or(Predicate<? super T> other) {
            Objects.requireNonNull(other);
            return (t) -> test(t) || other.test(t);
    }

    public static void main(String[] args) {
            Predicate<String> pre = res -> res.equals("1234");
            Predicate<String> pre1 = res -> res.equals("12341");
            boolean rest = pre.or(pre1).test("12341");
            System.out.println(rest);
     }

    上面打印出true;

    or方法的意思是:只要一个为true就返回true。

    isEqual方法用法:

    JDK源码定义:

    static <T> Predicate<T> isEqual(Object targetRef) {
            return (null == targetRef)
                    ? Objects::isNull
                    : object -> targetRef.equals(object);
     }

    public static void main(String[] args) {
            System.out.println(Predicate.isEqual("12345").test("12345"));

    }

    上面打印出true;isEqual里的参数是要比较的目标对象。

    3.函数式编程接口的使用

    通过Stream以及Optional两个类,可以进一步利用函数式接口来简化代码。

    3.1 Stream

    Stream可以对多个元素进行一系列的操作,也可以支持对某些操作进行并发处理。

    3.1.1 Stream对象的创建

    Stream对象的创建途径有以下几种:

    a. 创建空的Stream对象

    Stream str = Stream.empty();

    b. 通过集合类中的stream或者parallelStream方法创建;

    List<String> list = Arrays.asList("a", "b", "c", "d");
    Stream listStream = list.stream(); //获取串行的Stream对象
    Stream parallelListStream = list.parallelStream(); //获取并行的Stream对象  

    c. 通过Stream中的of方法创建:

    Stream s = Stream.of("test");

    Stream s1 = Stream.of("a", "b", "c", "d");

    d. 通过Stream中的iterate方法创建: 
        iterate方法有两个不同参数的方法:

    public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f);  
    public static<T> Stream<T> iterate(T seed, Predicate<? super T> hasNext, UnaryOperator<T> next)
    其中第一个方法将会返回一个无限有序值的Stream对象:它的第一个元素是seed,第二个元素是f.apply(seed); 第N个元素是f.apply(n-1个元素的值);生成无限值的方法实际上与Stream的中间方法类似,在遇到中止方法前一般是不真正的执行的。因此无限值的这个方法一般与limit等方法一起使用,来获取前多少个元素。 
    当然获取前多少个元素也可以使用第二个方法。 
    第二个方法与第一个方法生成元素的方式类似,不同的是它返回的是一个有限值的Stream;中止条件是由hasNext来断定的。
    第二种方法的使用示例如下:

    /**
     * 本示例表示从1开始组装一个序列,第一个是1,第二个是1+1即2,第三个是2+1即3..,直接10时中止;
     * 也可简化成以下形式:
     *        Stream.iterate(1,
     *        n -> n <= 10,
     *        n -> n+1).forEach(System.out::println);
     * 写成以下方式是为简化理解
     */
    Stream.iterate(1,
            new Predicate<Integer>() {
                @Override
                public boolean test(Integer integer) {
                    return integer <= 10;
                }
            },
        new UnaryOperator<Integer>() {
            @Override
            public Integer apply(Integer integer) {
                return integer+1;
            }
    }).forEach(System.out::println);

    e. 通过Stream中的generate方法创建 
    与iterate中创建无限元素的Stream类似,不过它的每个元素与前一元素无关,且生成的是一个无序的队列。也就是说每一个元素都可以随机生成。因此一般用来创建常量的Stream以及随机的Stream等。 
    示例如下:

    /**
     * 随机生成10个Double元素的Stream并将其打印
     */
    Stream.generate(new Supplier<Double>() {
        @Override
        public Double get() {
            return Math.random();
        }
    }).limit(10).forEach(System.out::println);

    //上述写法可以简化成以下写法:
    Stream.generate(() -> Math.random()).limit(10).forEach(System.out::println);


    f. 通过Stream中的concat方法连接两个Stream对象生成新的Stream对象 
    这个比较好理解不再赘述。

    3.1.2 Stream对象的使用
    Stream对象提供多个非常有用的方法,这些方法可以分成两类: 
    中间操作:将原始的Stream转换成另外一个Stream;如filter返回的是过滤后的Stream。 
    终端操作:产生的是一个结果或者其它的复合操作;如count或者forEach操作。

    其清单如下所示,方法的具体说明及使用示例见后文。 
    所有中间操作:

    所有的终端操作:

    下面就几个比较常用的方法举例说明其用法:

    3.1.2.1 filter
    用于对Stream中的元素进行过滤,返回一个过滤后的Stream 
    其方法定义如下:

    Stream<T> filter(Predicate<? super T> predicate);

    使用示例:

    Stream<String> s = Stream.of("test", "t1", "t2", "teeeee", "aaaa");
    //查找所有包含t的元素并进行打印
    s.filter(n -> n.contains("t")).forEach(System.out::println);

    3.1.2.2 map
    元素一对一转换。 
    它接收一个Funcation参数,用其对Stream中的所有元素进行处理,返回的Stream对象中的元素为Function对原元素处理后的结果 
    其方法定义如下:

    <R> Stream<R> map(Function<? super T, ? extends R> mapper);

    示例,假设我们要将一个String类型的Stream对象中的每个元素添加相同的后缀.txt,如a变成a.txt,其写法如下:

    Stream<String> s = Stream.of("test", "t1", "t2", "teeeee", "aaaa");
    s.map(n -> n.concat(".txt")).forEach(System.out::println);

    3.1.2.3 flatMap
    元素一对多转换:对原Stream中的所有元素使用传入的Function进行处理,每个元素经过处理后生成一个多个元素的Stream对象,然后将返回的所有Stream对象中的所有元素组合成一个统一的Stream并返回; 
    方法定义如下:

    <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);

    示例,假设要对一个String类型的Stream进行处理,将每一个元素的拆分成单个字母,并打印:

    Stream<String> s = Stream.of("test", "t1", "t2", "teeeee", "aaaa");
    s.flatMap(n -> Stream.of(n.split(""))).forEach(System.out::println);

    3.1.2.4 takeWhile
    方法定义如下:

    default Stream<T> takeWhile(Predicate<? super T> predicate)

    如果Stream是有序的(Ordered),那么返回最长命中序列(符合传入的Predicate的最长命中序列)组成的Stream;如果是无序的,那么返回的是所有符合传入的Predicate的元素序列组成的Stream。 
    与Filter有点类似,不同的地方就在当Stream是有序时,返回的只是最长命中序列。 
    如以下示例,通过takeWhile查找”test”, “t1”, “t2”, “teeeee”, “aaaa”, “taaa”这几个元素中包含t的最长命中序列:

    Stream<String> s = Stream.of("test", "t1", "t2", "teeeee", "aaaa", "taaa");
    //以下结果将打印: "test", "t1", "t2", "teeeee",最后的那个taaa不会进行打印 
    s.takeWhile(n -> n.contains("t")).forEach(System.out::println);

    3.1.2.5 dropWhile
    与takeWhile相反,如果是有序的,返回除最长命中序列外的所有元素组成的Stream;如果是无序的,返回所有未命中的元素组成的Stream;其定义如下:

    default Stream<T> dropWhile(Predicate<? super T> predicate)

    如以下示例,通过dropWhile删除”test”, “t1”, “t2”, “teeeee”, “aaaa”, “taaa”这几个元素中包含t的最长命中序列:

    Stream<String> s = Stream.of("test", "t1", "t2", "teeeee", "aaaa", "taaa");
    //以下结果将打印:"aaaa", "taaa"  
    s.dropWhile(n -> n.contains("t")).forEach(System.out::println);

    3.1.2.6 reduce与collect
    关于reduce与collect由于功能较为复杂,在后续将进行单独分析与学习,此处暂不涉及。

    3.2 Optional用于简化Java中对空值的判断处理,以防止出现各种空指针异常。 
    Optional实际上是对一个变量进行封装,它包含有一个属性value,实际上就是这个变量的值。

    3.2.1 Optional对象创建
    它的构造函数都是private类型的,因此要初始化一个Optional的对象无法通过其构造函数进行创建。它提供了一系列的静态方法用于构建Optional对象:

    3.2.1.1 empty
    用于创建一个空的Optional对象;其value属性为Null。 
    如:

    Optional o = Optional.empty();

    3.2.1.2 of
    根据传入的值构建一个Optional对象; 
    传入的值必须是非空值,否则如果传入的值为空值,则会抛出空指针异常。 
    使用:

    o = Optional.of("test"); 
    1
    3.2.1.3 ofNullable
    根据传入值构建一个Optional对象 
    传入的值可以是空值,如果传入的值是空值,则与empty返回的结果是一样的。

    3.2.2 方法
    Optional包含以下方法:

    3.2.3 使用场景
    常用的使用场景如下:

    3.2.3.1 判断结果不为空后使用
    如某个函数可能会返回空值,以往的做法:

    String s = test();
    if (null != s) {
        System.out.println(s);
    }

    现在的写法就可以是:

    Optional<String> s = Optional.ofNullable(test());
    s.ifPresent(System.out::println);

    乍一看代码复杂度上差不多甚至是略有提升;那为什么要这么做呢? 
    一般情况下,我们在使用某一个函数返回值时,要做的第一步就是去分析这个函数是否会返回空值;如果没有进行分析或者分析的结果出现偏差,导致函数会抛出空值而没有做检测,那么就会相应的抛出空指针异常! 
    而有了Optional后,在我们不确定时就可以不用去做这个检测了,所有的检测Optional对象都帮忙我们完成,我们要做的就是按上述方式去处理。

    3.2.3.2 变量为空时提供默认值
    如要判断某个变量为空时使用提供的值,然后再针对这个变量做某种运算; 
    以往做法:

    if (null == s) {
        s = "test";
    }
    System.out.println(s);

    现在的做法:

    Optional<String> o = Optional.ofNullable(s);
    System.out.println(o.orElse("test"));

    3.2.3.3 变量为空时抛出异常,否则使用
    以往写法:

    if (null == s) {
        throw new Exception("test");
    }
    System.out.println(s);


    现在写法:

    Optional<String> o = Optional.ofNullable(s);
    System.out.println(o.orElseThrow(()->new Exception("test")));
     

    展开全文
  • 主要介绍了Java函数编程(七):MapReduce,本文是系列文章的第7篇,其它文章请参阅本文底部的相关文章,需要的朋友可以参考下
  • 详解JAVA 函数编程

    2020-08-18 20:31:27
    主要介绍了JAVA 函数编程的相关资料,文中讲解非常细致,代码帮助大家更好的理解和学习,感兴趣的朋友可以了解下
  • 目前主要涉及提升java函数编程的体验。 pom引入 <groupId>tk.qcsoft.angelos <artifactId>exia <version>1.0.3 Exia模块 提供类似kotlin的函数式编程语法体验。简单易用。提供多种对目标类的结构化函数操作...
  • 主要介绍了Java函数编程(五):闭包,本文是系列文章的第5篇,其它篇章请参阅相关文章,需要的朋友可以参考下
  • 主要介绍了Java函数编程(六):Optional,本文是系列文章的第6篇,其它文章请参阅本文底部的相关文章,需要的朋友可以参考下
  • Java8函数编程

    万次阅读 多人点赞 2019-06-13 15:18:48
    于是决心花点时间深入地去研究一下java8的函数式。 (想自学习编程的小伙伴请搜索圈T社区,更多行业相关资讯更有行业相关免费视频教程。完全免费哦!) 一、lambda表达式 先po一个最经典的例子——线程 public static...

    最近使用lambda表达式,感觉使用起来非常舒服,箭头函数极大增强了代码的表达能力。于是决心花点时间深入地去研究一下java8的函数式。

    (想自学习编程的小伙伴请搜索圈T社区,更多行业相关资讯更有行业相关免费视频教程。完全免费哦!)

    一、lambda表达式

    先po一个最经典的例子——线程

    public static void main(String[] args) {
      // Java7
      new Thread(new Runnable() {
        @Override
        public void run() {
          for (int i = 0; i < 100; i++) {
            System.out.println(i);
          }
        }
      }).start();
    
      // Java8
      new Thread(() -> {
        for (int i = 0; i < 100; i++) {
          System.out.println(i);
        }
      }).start();
    }
    

    第一次接触lambda表达式是在创建线程时,比较直观的感受就是lambda表达式相当于匿名类的语法糖,emm~,真甜。不过事实上,lambda表达式并不是匿名类的语法糖,而且经过一段时间的使用,感觉恰恰相反,在使用上匿名类更像是Java中lambda表达式的载体。

    使用场景

    下面的一些使用场景均为个人的一些体会,可能存在不当或遗漏之处。

    1. 简化匿名类的编码

    上面的创建线程就是一个很好简化编码的例子,此处就不再重复。

    2. 减少不必要的方法创建

    在Java中,我们经常会遇到这样一种场景,某个方法只会在某处使用且内部逻辑也很简单,在Java8之前我们通常都会创建一个方法,但是事实上我们经常会发现这样写着写着,一个类中的方法可能会变得非常庞杂,严重影响阅读体验,进而影响编码效率。但是如果使用lambda表达式,那么这个问题就可以很容易就解决掉了。

    一个简单的例子,如果我们需要在一个函数中多次打印时间。(这个例子可能有些牵强,但是实际上还是挺常遇见的)

    public class FunctionMain {
        
        public static void main(String[] args) {
            TimeDemo timeDemo = new TimeDemo();
            timeDemo.createTime = System.currentTimeMillis();
            timeDemo.updateTime = System.currentTimeMillis() + 10000;
            outputTimeDemo(timeDemo);
        }
    
        private static void outputTimeDemo(TimeDemo timeDemo) {
            Function timestampToDate = timestamp -> {
                DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                return df.format(new Date(timestamp));
            };
    
            System.out.println(timestampToDate.apply(timeDemo.createTime));
            System.out.println(timestampToDate.apply(timeDemo.updateTime));
        }
    
    
        interface Function {
            String apply(long timestamp);
        }
    }
    
    class TimeDemo {
        long createTime;
        long updateTime;
    }
    

    在这段代码的outputTimeDemo中我们可以看到,对于时间戳转换的内容,我们并没有额外创建一个方法,而是类似于创建了一个变量来表达。不过,这个时候出现了另一个问题,虽然我们少创建了一个方法,但是我们却多创建了一个接口Function,总有种因小失大的感觉, 不过这个问题,我们在后面的java.util.function包部分可以找到答案。

    3. 事件处理

    一个比较常见的例子就是回调。

    public static void main(String[] args) {
        execute("hello world", () -> System.out.println("callback"));
    }
    
    private static void execute(String s, Callback callback) {
        System.out.println(s);
        callback.callback();
    }
    
    @FunctionalInterface
    interface Callback {
        void callback();
    }
    

    在这里,可以发现一点小不同,就是Callback多了一个注解@FunctionalInterface,这个注解主要用于编译期检查,如果我们的接口不符合函数式接口的要求,那编译的时候就会报错。不加也是可以正常执行的。

    4. stream中使用

    这个在后面的stream中详解。

    java.util.function包

    在之前的例子中,我们发现使用lambda表达式的时候,经常需要定义一些接口用来辅助我们的编码,这样就会使得本应轻量级的lambda表达式又变得重量级。那是否存在解决方案呢?其实Java8本身已经为我们提供了一些常见的函数式接口,就在java.util.function包下面。

    接口描述
    Function<T,R>接受一个输入参数,返回一个结果
    Supplier无参数,返回一个结果
    Consumer接受一个输入参数,并且不返回任何结果
    BiFunction<T,U,R>接受两个输入参数的方法,并且返回一个结果
    BiConsumer<T,U>接受两个输入参数的操作,并且不返回任何结果

    此处列出最基本的几个,其他的都是在这些的基础上做了一些简单的封装,例如IntFunction就是对Function<T,R>的封装。上面的这些函数式接口已经可以帮助我们处理绝大多数场景了,如果有更复杂的情况,那就得我们自己定义接口了。不过遗憾的是在java.util.function下没找到无参数无返回结果的接口,目前我找到的方案就是自己定义一个接口或者直接使用Runnable接口。

    使用示例

    public static void main(String[] args) {
        Function<Integer, Integer> f = x -> x + 1;
        System.out.println(f.apply(1));
    
        BiFunction<Integer, Integer, Integer> g = (x, y) -> x + y;
        System.out.println(g.apply(1, 2));
    }
    

    lambda表达式和匿名类的区别

    lambda表达式虽然使用时和匿名类很相似,但是还是存在那么一些区别。

    1. this指向不同

    lambda表达式中使用this指向的是外部的类,而匿名类中使用this则指向的是匿名类本身。

    public class FunctionMain {
    
        private String test = "test-main";
    
        public static void main(String[] args) {
            new FunctionMain().output();
        }
    
        private void output() {
            Function f = () -> {
                System.out.println("1:-----------------");
                System.out.println(this);
                System.out.println(this.test);
            };
            f.outputThis();
    
            new Function() {
                @Override
                public void outputThis() {
                    System.out.println("2:-----------------");
                    System.out.println(this);
                    System.out.println(this.test);
                }
            }.outputThis();
        }
    
        interface Function {
            String test = "test-function";
    
            void outputThis();
        }
    }
    

    如上面这段代码,输出结果如下
    在这里插入图片描述
    所以如果想使用lambda表达式的同时去访问原类中的变量、方法的是做不到的。

    2. 底层实现不同

    编译

    从编译结果来看,两者的编译结果完全不同。

    首先是匿名类的方式,代码如下:

    import java.util.function.Function;
    
    public class ClassMain {
    
        public static void main(String[] args) {
            Function<Integer, Integer> f = new Function<Integer, Integer>() {
                @Override
                public Integer apply(Integer integer) {
                    return integer + 1;
                }
            };
            System.out.println(f.apply(1));
        }
    }
    

    编译后的结果如下:
    在这里插入图片描述
    可以看到ClassMain在编译后生成了两个class,其中ClassMain$1.class就是匿名类生成的class。

    那么接下来,我们再来编译一下lambda版本的。代码和编译结果如下:

    import java.util.function.Function;
    
    public class FunctionMain {
    
        public static void main(String[] args) {
            Function<Integer, Integer> f = x -> x + 1;
            System.out.println(f.apply(1));
        }
    }
    

    在这里插入图片描述
    在这里我们可以看到FunctionMain并没有生成第二个class文件。

    字节码

    更进一步,我们打开他们的字节码来寻找更多的细节。首先依然是匿名类的方式
    在这里插入图片描述
    在Code-0这一行,我们可以看到匿名类的方式是通过new一个类来实现的。

    接下来是lambda表达式生成的字节码,
    在这里插入图片描述
    在lambda表达式的字节码中,我们可以看到我们的lambda表达式被编译成了一个叫做lambda$main$0的静态方法,接着通过invokedynamic的方式进行了调用。

    3. lambda表达式只能替代部分匿名类

    lambda表达式想要替代匿名类是有条件的,即这个匿名类实现的接口必须是函数式接口,即只能有一个抽象方法的接口。

    性能

    由于没有实际测试过lambda表达式的性能,且我使用lambda更多是基于编码简洁度的考虑,因此本文就不探讨性能相关问题。
    关于lambda表达式和匿名类的性能对比可以参考官方ppt https://www.oracle.com/technetwork/java/jvmls2013kuksen-2014088.pdf

    二、Stream API

    Stream API是Java8对集合类的补充与增强。它主要用来对集合进行各种便利的聚合操作或者批量数据操作。

    1. 创建流

    在进行流操作的第一步是创建一个流,下面介绍几种常见的流的创建方式

    从集合类创建流

    如果已经我们已经有一个集合对象,那么我们可以直接通过调用其stream()方法得到对应的流。如下

    List<String> list = Arrays.asList("hello", "world", "la");
    list.stream();
    

    利用数组创建流

    String[] strArray = new String[]{"hello", "world", "la"};
    Stream.of(strArray);
    

    利用可变参数创建流

    Stream.of("hello", "world", "la");
    

    根据范围创建数值流

    IntStream.range(0, 100);         // 不包含最后一个数
    IntStream.rangeClosed(0, 99);    // 包含最后一个数
    

    BufferReader.lines()

    对于BufferReader而言,它的lines方法也同样可以创建一个流

    File file = new File("/Users/cayun/.m2/settings.xml");
    BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
    br.lines().forEach(System.out::println);
    br.close();
    

    2. 流操作

    在Stream API中,流的操作有两种:Intermediate和Terminal

    Intermediate:一个流可以后面跟随零个或多个 intermediate 操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。
    Terminal:一个流只能有一个 terminal 操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。Terminal 操作的执行,才会真正开始流的遍历,并且会生成一个结果,或者一个 side effect。

    除此以外,还有一种叫做short-circuiting的操作

    对于一个 intermediate 操作,如果它接受的是一个无限大(infinite/unbounded)的 Stream,但返回一个有限的新 Stream。
    对于一个 terminal 操作,如果它接受的是一个无限大的 Stream,但能在有限的时间计算出结果。

    常见的流操作可以如下归类:
    Intermediate
    map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered

    Terminal
    forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator

    Short-circuiting
    anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit

    常见的流操作详解

    1. forEach

    forEach可以说是最常见的操作了,甚至对于List等实现了Collection接口的类可以不创建stream而直接使用forEach。简单地说,forEach就是遍历并执行某个操作。

    Stream.of("hello", "world", "a", "b").forEach(System.out::println);
    

    2. map

    map也同样是一个非常高频的流操作,用来将一个集合映射为另一个集合。下面代码展示了将[1,2,3,4]映射为[1,4,9,16]

    IntStream.rangeClosed(1, 4).map(x -> x * x).forEach(System.out::println);
    

    除此之外,还有一个叫做flatMap的操作,这个操作在映射的基础上又做了一层扁平化处理。这个概念可能比较难理解,那举个例子,我们需要将[“hello”, “world”]转换成[h,e,l,l,o,w,o,r,l,d],可以尝试一下使用map,那你会惊讶地发现,可能结果不是你想象中的那样。如果不信可以执行下面这段代码,就会发现map与flatMap之间的区别了,

    Stream.of("hello", "world").map(s -> s.split("")).forEach(System.out::println);
    System.out.println("--------------");
    Stream.of("hello", "world").flatMap(s -> Stream.of(s.split(""))).forEach(System.out::println);
    

    3. filter

    filter则实现了过滤的功能,如果只需要[1,2,3,4,5]中的奇数,可以如下,

    IntStream.rangeClosed(1, 5).filter(x -> x % 2 == 1).forEach(System.out::println);
    

    4. sorted和distinct

    其中sorted表示排序,distinct表示去重,简单的示例如下:

    Integer[] arr = new Integer[]{5, 1, 2, 1, 3, 1, 2, 4};    // 千万不要用int
    Stream.of(arr).sorted().forEach(System.out::println);
    Stream.of(arr).distinct().forEach(System.out::println);
    Stream.of(arr).distinct().sorted().forEach(System.out::println);
    

    5. collect

    在流操作中,我们往往需求是从一个List得到另一个List,而不是直接通过forEach来打印。那么这个时候就需要使用到collect了。依然是之前的例子,将[1,2,3,4]转换成[1,4,9,16]。

    List<Integer> list1= Stream.of(1, 2, 3, 4).map(x -> x * x).collect(Collectors.toList());
            
    // 对于IntStream生成的流需要使用mapToObj而不是map
    List<Integer> list2 = IntStream.rangeClosed(1, 4).mapToObj(x -> x * x).collect(Collectors.toList());
    

    3. 补充

    并行流

    除了普通的stream之外还有parallelStream,区别比较直观,就是stream是单线程执行,parallelStream为多线程执行。parallelStream的创建及使用基本与stream类似,

    List<Integer> list = Arrays.asList(1, 2, 3, 4);
    // 直接创建一个并行流
    list.parallelStream().map(x -> x * x).forEach(System.out::println);
    // 或者将一个普通流转换成并行流
    list.stream().parallel().map(x -> x * x).forEach(System.out::println);
    

    不过由于是并行执行,parallelStream并不保证结果顺序,同样由于这个特性,如果能使用findAny就尽量不要使用findFirst。

    使用parallelStream时需要注意的一点是,多个parallelStream之间默认使用的是同一个线程池,所以IO操作尽量不要放进parallelStream中,否则会阻塞其他parallelStream。

    三、Optional

    Optional的引入是为了解决空指针异常的问题,事实上在Java8之前,Optional在很多地方已经较为广泛使用了,例如scala、谷歌的Guava库等。

    在实际生产中我们经常会遇到如下这种情况,

    public class FunctionMain {
    
        public static void main(String[] args) {
            Person person = new Person();
            String result = null;
            if (person != null) {
                Address address = person.address;
                if (address != null) {
                    Country country = address.country;
                    if (country != null) {
                        result = country.name;
                    }
                }
            }
            System.out.println(result);
        }
    }
    
    class Person {
        Address address;
    }
    
    class Address {
        Country country;
    }
    
    class Country {
        String name;
    }
    

    每每写到这样的代码,作为编码者一定都会头皮发麻,满心地不想写,但是却不得不写。这个问题如果使用Optional,或许你就能找到你想要的答案了。

    Optional的基本操作

    1. 创建Optional

    Optional.empty();          // 创建一个空Optional
    Optional.of(T value);      // 不接受null,会报NullPointerException异常
    Optional.ofNullable(T value);     // 可以接受null
    

    2. 获取结果

    get();                                   // 返回里面的值,如果值为null,则抛异常
    orElse(T other);                         // 有值则返回值,null则返回other
    orElseGet(Supplier other);               // 有值则返回值,null则由提供的lambda表达式生成值
    orElseThrow(Supplier exceptionSupplier); // 有值则返回值,null则抛出异常
    

    3. 判断是否为空

    isPresent();       // 判断是否为空
    

    到这里,我们可能会开始考虑怎么用Optional解决引言中的问题了,于是思考半天,写出了这样一段代码,

    public static void main(String[] args) {
        Person person = new Person();
        String result = null;
        Optional<Person> per = Optional.ofNullable(person);
        if (per.isPresent()) {
            Optional<Address> address = Optional.ofNullable(per.get().address);
            if (address.isPresent()) {
                Optional<Country> country = Optional.ofNullable(address.get().country);
                if (country.isPresent()) {
                    result = Optional.ofNullable(country.get().name).orElse(null);
                }
            }
         }
         System.out.println(result);
    }
    

    啊嘞嘞,感觉不仅没有使得代码变得简单,反而变得更加复杂了。那么很显然这并不是Optional的正确使用方法。接下来的部分才是Optional的正确使用方式。

    4. 链式方法

    在Optional中也有类似于Stream API中的链式方法map、flatMap、filter、ifPresent。这些方法才是Optional的精髓。此处以最典型的map作为例子,可以看看map的源码

    public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent())
            return empty();
        else {
            return Optional.ofNullable(mapper.apply(value));
        }
    }
    

    源码很简单,可以看到对于null情况仍然返回null,否则返回处理结果。那么此再来思考一下引言的问题,那就可以很简单地改写成如下的写法,

    public static void main(String[] args) {
        Person person = new Person();
        String result = Optional.ofNullable(person)
                .map(per -> per.address)
                .map(address -> address.country)
                .map(country -> country.name).orElse(null);
        System.out.println(result);
    }
    

    哇哇哇,相比原先的null写法真真是舒服太多了。

    map与flatMap的区别

    这两者的区别,同样使用一个简单的例子来解释一下吧,

    public class FunctionMain {
    
        public static void main(String[] args) {
            Person person = new Person();
            String name = Optional.ofNullable(person).flatMap(p -> p.name).orElse(null);
            System.out.println(name);
        }
    }
    
    class Person {
        Optional<String> name;
    }
    

    在这里使用的不是map而是flatMap,稍微观察一下,可以发现Person中的name不再是String类型,而是Optional类型了,如果使用map的话,那map的结果就是Optional<Optional>了,很显然不是我们想要的,flatMap就是用来将最终的结果扁平化(简单地描述,就是消除嵌套)的。
    至于filter和ifPresent用法类似,就不再叙述了。

    四、其他一些函数式概念在Java中的实现

    由于个人目前为止也只是初探函数式阶段,很多地方了解也不多,此处只列举两个。(注意:下面的部分应用函数与柯里化对应的是scala中的概念,其他语言中可能略有偏差)

    部分应用函数(偏应用函数)

    部分应用函数指的是对于一个有n个参数的函数f,但是我们只提供m个参数给它(m < n),那么我们就可以得到一个部分应用函数,简单地描述一下,如下
    在这里插入图片描述
    在这里插入图片描述
    在这里g就是f的一个部分应用函数。

    BiFunction<Integer, Integer, Integer> f = (x, y) -> x + y;
    Function<Integer, Integer> g = x -> f.apply(1, x);
    System.out.println(g.apply(2));
    

    柯里化

    柯里化就是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。换个描述,如下
    在这里插入图片描述
    Java中对柯里化的实现如下,

    Function<Integer, Function<Integer, Integer>> f = x -> y -> x + y;
    System.out.println(f.apply(1).apply(2));
    

    因为Java限制,我们不得不写成f.apply(1).apply(2)的形式,不过视觉上的体验与直接写成f(1)(2)相差就很大了。
    柯里化与部分应用函数感觉很相像,不过因为个人几乎未使用过这两者,因此此处就不发表更多见解。

    展开全文
  • java代码-java函数编程
  • java函数编程的好处

    千次阅读 2020-05-05 11:28:59
    Java引入了函数编程,这表示Java从此不在是一个单纯的面向对象语言,现在他同时混合了函数编程。这是巨大的改变,需要我们调整面对对象的编程习惯,以适应这些变化。 但是为什么我们需要去适应这些改变?为什么...
  • java8函数编程源码

    2019-04-22 20:47:11
    java8函数编程源码。前面讲解Lambda表达式的基本规则,后面介绍如何使用Lambda表达式优化现有代码,提交并发等等。
  • Java函数编程》_高清华
  • Java函数编程》并不是一本关于Java的书,而是一本关于函数式编程的书。作者由浅入深地介绍了函数式编程的思维方式,并引导读者通过易于掌握的例子、练习和图表来学习和巩固函数式编程的基本原则和最佳实践。读者...
  • 主要介绍了Java函数编程(十一):遍历目录,本文是系列文章的第11篇,其它文章请参阅本文底部的相关文章,需要的朋友可以参考下
  • JAVA函数编程

    千次阅读 2019-02-17 22:40:55
    JAVA函数编程背景常见的编程范式函数式编程的优劣JAVA8中为函数式编程引入的变化JAVA函数编程可以简单概括基本函数Lambda表达式方法引用Stream流API创建操作中间操作终止从操作并行流级联表达式与柯里化收集器...
  • Java8函数编程(一)

    千次阅读 2017-08-03 22:53:28
    函数编程详解: 前言: 现在有很多公司都用了jdk8,但是函数编程也许没有用上,jdk8也提供了很多API,比喻Stream API,等等。流式编程是它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的...
  • Java8函数编程快速入门Java8函数编程快速入门Java8函数编程快速入门Java8函数编程快速入门Java8函数编程快速入门Java8函数编程快速入门Java8函数编程快速入门Java8函数编程快速入门Java8函数编程...
  • 主要介绍了Java函数编程(九):Comparator,本文是系列文章的第9篇,其它文章请参阅本文底部的相关文章,需要的朋友可以参考下
  • java函数编程之Consumer

    万次阅读 2016-12-21 23:51:40
    描述:Consumer接口接受一个T类型参数,没有返回值。 源码如下: ...public interface Consumer<T> { ... * Performs this operation on the given argument. ...两相对比,使用函数式确实是要优雅一点。
  • Java8新引入函数式编程方式,大大的提高了编码效率。本文将对涉及的对象等进行统一的学习及记录。 首先需要清楚一个概念:函数式接口;它指的是有且只有一个未实现的方法的接口,一般...2 Java函数式接口 2.1 Consu...
  • 主要介绍了Java函数编程(一):你好,Lambda表达式,本文讲解了新老函数式编程的一些变化,需要的朋友可以参考下
  • 0202年了,还没有用上Java函数编程!!!——Lambda表达式函数式编程是什么命令式编程(Imperative)声明式编程(Declarative)函数式编程(Functional)总结函数式编程的好处Lambda表达式函数式接口五种形式1.无参数2....
  • Java 8函数编程.rar

    2021-04-03 17:56:43
    Java 8函数编程PDF版本,给感兴趣的朋友一个很好参考。如何编写出简单、干净、易读的代码——尤其是对于集合的操作?
  • 函数编程,这个词语由两个名词构成,函数编程...函数,其实单独抽离出来这个词语,也并不陌生,那二者组合后的到底是什么呢,下面这篇文章主要给大家介绍了关于Java8函数编程的相关资料,需要的朋友可以参考下。
  • java lambda函数编程完成实例代码,看完代码,基本上就会用lambda 了

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 611,693
精华内容 244,677
关键字:

java函数编程

java 订阅
友情链接: Chomework.rar