精华内容
下载资源
问答
  • 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函数式编程的体验。 pom引入 <groupId>tk.qcsoft.angelos <artifactId>exia <version>1.0.3 Exia模块 提供类似kotlin的函数式编程语法体验。简单易用。提供多种对目标类的结构化函数操作...
  • 主要介绍了Java函数式编程(七):MapReduce,本文是系列文章的第7篇,其它文章请参阅本文底部的相关文章,需要的朋友可以参考下
  • java函数式编程入门

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

    2020-08-18 20:31:27
    主要介绍了JAVA 函数式编程的相关资料,文中讲解非常细致,代码帮助大家更好的理解和学习,感兴趣的朋友可以了解下
  • 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函数式编程

    2019-04-22 01:17:27
    NULL 博文链接:https://cywhoyi.iteye.com/blog/1943291
  • 主要介绍了Java函数式编程(五):闭包,本文是系列文章的第5篇,其它篇章请参阅相关文章,需要的朋友可以参考下
  • 主要介绍了Java函数式编程(六):Optional,本文是系列文章的第6篇,其它文章请参阅本文底部的相关文章,需要的朋友可以参考下
  • Java 8函数式编程.pdf

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

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

    背景

    JAVA版本最新的目前已经发布到11了,但目前市面上大多数公司依然在使用Java7之前版本的语法,然而这些编程模式已经渐渐的跟不上新时代的技术了。比如时下潮流前沿spring framework5中的响应式编程就是使用到了函数式编程的风格。

    当时有一段时间了解了一下大数据领域生态发现对于写spark中的程序来说如果你使用java7前的命令式编程会使得整体代码相当臃肿,又臭又长匿名类大几百行的代码其中写到的逻辑其实不过几十行,大部分的代码浪费在语法上。使得代码的可读性变得非常差。spark本身是使用Scala编写的对于本身就支持函数式编程的语言,使得代码简洁而又易于理解。当然spark也支持jdk8相对于jdk7来说8加入了函数式编程的支持使得整体优雅了许多。

    在编程历史的长河中java向来可维护性、可扩展性是一直受业界所推崇的。但由于java语法臃肿,也流失了一部分转向了python、go等。在学习大数据的时候发现大部分的教程更倾向Scala(如果大家有java8的教程麻烦连接评论哦!),而Scala本身依赖与jvm,所以若不是公司技术栈限制的话我相信大家更倾向与使用Scala来编程。OK!那么java也推出了函数式编程也通过本文来了解一下。

    常见的编程范式

    • 命令式编程:命令式编程的主要思想是关注计算机执行的步骤,即一步一步告诉计算机先做什么再做什么。这种风格我相信对于传统程序员来说都不陌生。甚至一些大牛闭着眼睛都可以敲了。
      代表语言有:C, C++, Java, Javascript, BASIC,Ruby等多为老牌语言
    • 声明式编程:声明式编程是以数据结构的形式来表达程序执行的逻辑。它的主要思想是告诉计算机应该做什么,但不指定具体要怎么做。乍一看似乎没有见过但其实日常来说大家也都在用。
      代表语言有:SQL,HTML,CSS
    • 函数式编程:函数式编程将函数作为编程中的“一等公民”,关注于流程而非具体实现。可以将函数作为参数或返回值。所有数据的操作都通过函数来实现。可以理解为数学中的函数。较新的语言基本上追求语法上的简洁基本都有支持。
      代表语言有:JAVA(8以上),js(ES6),C#,Scala,python等

    函数式编程的优劣

    优点:

    1. 代码简洁可读性强,逻辑结构清晰。
    2. 线程安全,内部API屏蔽了coder对多线程的关注。
    3. 更好的复用性,往往一个函数适用于各种计算场景。

    缺点:

    1. 由于函数内数据不变原则,导致的资源占用
    2. 调试上相对于命令式的困难

    JAVA8中为函数式编程引入的变化

    • 函数式接口,函数式接口中只能有一个抽象方法
      @FunctionInterface,这也是为了函数调用时避免带来二义性。@FunctionInterface并不是一定要标注但若是标注可以在编译时就给你提示错误。
    • 静态方法,静态方法目的完全出于编写类库,对某些行为进行抽象,但是接口中的静态方法不能被继承。
    • 默认实现,是不得已而为之,因为Java8引入了函数式接口,许多像Collection这样的基础接口中增加了方法,如果还是一个传统的抽象方法的话,那么可能很多第三方类库就会变得完全无法使用。新增一个方法所有实现类都要实现一次。被default修饰的方法–默认实现

    JAVA函数式编程可以简单概括

    lambda + 方法引用 + stream API = java函数式编程

    基本函数

    在这里插入图片描述
    以上是在函数式编程中的基本函数模型,我们大可以将其与数学函数做关联:y = x +1,我们仅仅需要关注这个函数的输入输出即可。
    以predicte函数举例:该函数输入一个表达式,输出一个布尔类型
    一元函数function:输入一个类型参数输出为另一个类型,当然这两个类型可以是相同的,当相同时也可以使用unaryOperator来代替。具体下面有给出实际场景的代码断:

    public class FunctionDemo {
    
        public static void main(String[] args) {
            //断言型
    //        predicate();
            //消费型
    //        consumer();
            //一元函数 输入输出不同
    //        function();
            //提供型
    //        supplier();
            //一元函数 输入输出类型相同
    //        unaryOperator();
            //二元函数 输入输出不同
    //        biFunction();
            //二元函数 输入输出相同
            binaryOperator();
        }
    
        /**
         *
         */
        public static void predicate(){
            Predicate<Integer> predicate = i -> i > 0;
            IntPredicate intPredicate = i -> i > 0;
            System.out.print(predicate.test(6));
            System.out.print(intPredicate.test(-1));
        }
    
        public static void consumer(){
            Consumer<String> consumer = s -> System.out.println(s);
            consumer.accept("我是一个消费者");
        }
    
        public static void function(){
            Function<Integer,String> function = x -> "数字是:"+ x;
            System.out.println(function.apply(88));
        }
    
        public static void supplier(){
            Supplier<String> supplier = () -> "我是一个提供者";
            System.out.println(supplier.get());
        }
    
        public static void unaryOperator(){
            UnaryOperator<Integer> unaryOperator = x -> ++x;
            System.out.println(unaryOperator.apply(1));
        }
    
        public static void biFunction(){
            BiFunction<Integer,Double,Double> biFunction = (x,y) -> {
                ++x;
                ++y;
                return x+y;
            };
            System.out.println(biFunction.apply(1,2.3));
        }
    
        public static void binaryOperator(){
            IntBinaryOperator intBinaryOperator = (x,y) -> x + y;
            System.out.println(intBinaryOperator.applyAsInt(2,3));
        }
    }
    

    Lambda表达式

    相信大家在写一些匿名内部类时编译器会提示你,该处可以转换为lambda,例如:

        public static void main(String[] args){
            IMethod iMethod = () -> {};
        }
    
        public static void useLambda(){
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.print("123");
                }
            });
            thread.start();
    
            //lambda
            new Thread(() -> System.out.print("123")).start();
        }
    

    定义一个线程并启动他,传统写法我们用了7行,但其实只有 System.out.print(“123”);这句话是有用的,而lambda表达式我们仅仅一行就达到了相同目的。‘

    lambda表达式主要用于实例化一些函数式接口,替换匿名类的写法,主要形式有:
    (输入) -> {输出} // 我们仅仅关注输入输出即可,若代码有多行大括号里面可以写多行,多行有返回值要显示声明return。输入可以为空即()。

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

    文末链接有附带工程demo链接可以参考哦!

    方法引用

    我们可以直接使用两个冒号::来调用方法

    • 静态方法引用
    • 非静态 实例方法引用
    • 非静态 类方法引用
    • 构造函数方法引用
    public class MethodReferenceDemo {
        public static void main(String[] args) {
            //消费者 方法引用模式
    //        consumer();
            //静态方法引用
    //        callStaticMethod();
            //非静态 实例方法引用
    //        callMethod();
            //非静态 类方法引用
    //        callMethodByClass();
            //构造函数方法引用
    //        callConstructorMethod();
            //数据不变模式
            callMethod2();
        }
    
        public static void consumer(){
            Consumer<String> consumer = System.out::println;
            consumer.accept("我是一个消费者");
        }
    
        private static void callStaticMethod() {
            Consumer<Dog> consumer = Dog::bark;
            consumer.accept(new Dog());
        }
    
        private static void callMethod() {
            Dog dog = new Dog();
            Function<Integer,Integer> function = dog::eat;
            System.out.println("还剩[" + function.apply(3) + "]斤狗粮");
        }
    
        private static void callMethodByClass() {
            BiFunction<Dog,Integer,Integer> biFunction  = Dog::eat;
            System.out.println("还剩[" + biFunction.apply(new Dog(),4) + "]斤狗粮");
        }
    
        private static void callConstructorMethod() {
            Supplier<Dog> supplier = Dog::new;
            System.out.println("new 了一个对象" + supplier.get());
        }
    
        private static void callMethod2() {
            Dog dog = new Dog();
            Function<Integer,Integer> function = dog::eat; //函数声明
            dog = null;
            System.out.println("还剩[" + function.apply(3) + "]斤狗粮");
        }
    
    }
    

    Stream流API

    Stream API是Java 8中加入的一套新的API,主要用于处理集合操作。Stream流API是函数式编程的核心所在,它以一种流式编程来对数据进行各种加工运算。形象的来说你可以把它看作工业中的流水线,将原料放入流中经过操作1、操作2…操作N输出一个产品。Stream也是如此它分为创建操作、中间操作、终止操作。业务逻辑清晰简单、代码看上去优雅不少。

    流通常是由三个部分组成:

    1. 数据源:流的获取,比如list.stream()方法;
    2. 中间处理:中间处理是对流元素的一系列处理。比如过滤filter,排序sorted,映射map;
    3. 终端处理:终端处理会生成结果,结果可以是任何不是流值。

    创建操作

    在这里插入图片描述
    在jdk8中集合数组加入了不少流的方法其中就有直接通过实例或是工具类创建流。如:list.stream(),而数据没有自身API需要借助工具类Arrays来创建。这里通过parallelStream()并行流的模式来创建就可以透明的使用到多线程了。

    注:通过阅读源码可以知Stream类与IntStream、LongStream并没有继承关系

    public class CreateStream {
    
        public static void main(String[] args) {
    //        collectionCreate();
    //        arrayCreate();
    //        numCreate();
            selfCreate();
        }
    
        /**
         * 集合创建
         */
        public static void collectionCreate(){
            List<String> list = new ArrayList<>();
            Stream<String> stream = list.stream();
            Stream<String> parallelStream = list.parallelStream();
        }
    
        /**
         * 数组创建
         */
        public static void arrayCreate(){
            Integer[] array = new Integer[5];
            Stream<Integer> stream = Arrays.stream(array);
        }
    
        /**
         * 数字创建
         */
        public static void numCreate(){
            IntStream.of(1,2,3);
            IntStream.rangeClosed(1,10);
            new Random().ints().limit(10);
        }
    
        /**
         * 自己创建
         */
        public static void selfCreate(){
            Random random = new Random();
            Stream.generate(random::nextInt).limit(20);
            Stream.iterate(2, (x) -> x*2).limit(10).forEach(System.out::println);
        }
    }
    

    中间操作

    中间操作分为有状态操作、无状态操作。无状态操作即该中间操作不依赖与另外的空间来存放临时结果。有状态即需要。这么说还是比较抽象,我们不妨来举个栗子0.0。

    比如说:你的排序操作传统我们要进行排序是否需要依赖额外空间来进行大小的比较。去重操作需要额外空间来存放未重复的值。而像是filter只是单纯返回过滤后的结果无需额外空间。

    这是一种说法,另一种说法该操作与其他操作,没有依赖关系即为无状态,反正则为有状态。这么说也没错,你看像是order操作不是就要等前面操作都执行完才可以执行吗。后面会提到一点就是Stream的操作模式实际上是每一条数据通过A操作B操作C操作来进行的,而到了中间有有状态操作是,必须停下等所有数据都操作到这一步时一起进行,否则你让他如何进行排序呢?

    public class MiddleStream {
        public static void main(String[] args) {
    //        mapOrMapToXXX();
    //        flatMap();
    //        peek();
    //        distinct();
    //        sort();
            limitSkip();
        }
    
        /**
         * map操作    A -> B
         * filter操作
         */
        public static void mapOrMapToXXX(){
            String s = "my name is 007";
            Stream.of(s.split(" ")).map(String::length).forEach(System.out::println);
            System.out.println("-------------");
            Stream.of(s.split(" ")).filter(x -> x.length() > 2).mapToDouble(x -> x.length()).forEach(System.out::println);
        }
    
        /**
         * flatMap操作 A -> B list
         * IntStream/LongStream 并不是stream的子类需要进行装箱
         */
        public static void flatMap(){
            String s = "my name is 007";
            Stream.of(s.split(" ")).flatMap(x -> x.chars().boxed()).forEach(x -> System.out.println((char)x.intValue()));
        }
    
        /**
         * peek 要类型对应
         */
        public static void peek(){
            IntStream.of(new int[]{1,2,3,4,5}).peek(System.out::println).forEach(x->{});
        }
    
        /**
         * distinct
         */
        public static void distinct(){
            IntStream.of(new int[]{1,3,3,4,5}).distinct().forEach(System.out::println);
        }
    
        /**
         * sort
         */
        public static void sort(){
            IntStream.of(new int[]{5,4,3,1,2}).sorted().forEach(System.out::println);
        }
    
        /**
         * limitSkip
         */
        public static void limitSkip(){
            IntStream.of(new int[]{1,2,3,4,5,6,7,8}).skip(2).limit(2).forEach(System.out::println);
        }
    }
    

    这里提到Stream有个特性叫做:惰性求值。什么意思呢?就是当stream没有调用到终止操作时,实际上是不会执行之前的所有过程的。这一点可以在demo工程中有相应的证明方法。 有接触过spark的同学可以将这一特性类比为Transformation和Action。

    终止从操作

    终止操作即流水线的最后一个操作,往往就是返回你所要的产品。
    这里分为短路操作和非短路操作:

    非短路操作:从流中获取所有数据进行运算返回,有可能返回一个或多个值,但必定运用到了所有数据
    短路操作:从流中截取部分数据返回。
    在这里插入图片描述

    public class FinalStream {
        public static void main(String[] args){
    //        forEachOrdered();
    //        collect();
    //        reduce();
    //        minMixCount();
            findFirst();
        }
    
        /**
         * forEachOrdered
         */
        public static void forEachOrdered(){
            IntStream.of(new int[]{1,2,3,4,5,6,7}).parallel().forEach(System.out::println);
    //        IntStream.of(new int[]{1,2,3,4,5,6,7}).parallel().forEachOrdered(System.out::println);
        }
    
        /**
         * collect、toArray
         */
        public static void collect(){
            String s = "hello world!";
            List<String> collect = Stream.of(s.split(" ")).collect(Collectors.toList());
            System.out.println(collect);
        }
    
        /**
         * reduce
         */
        public static void reduce(){
            Integer[] intArr = new Integer[]{1,2,3,4,5,6,7,8,9,10};
            Optional<Integer> optional = Stream.of(intArr).reduce((x, y) -> x + y);
            System.out.println(optional.get());
        }
    
        /**
         * minMixCount
         */
        public static void minMixCount(){
            Integer[] intArr = new Integer[]{1,2,3,4,5,6,7,8,9,10};
            Optional<Integer> optional = Stream.of(intArr).max(Comparator.comparingInt(x -> x));
            System.out.println(optional.get());
        }
    
        //短路操作--------------------------------
        /**
         * findFirst
         */
        public static void findFirst(){
            Optional<Integer> first = Stream.generate(() -> new Random().nextInt()).findFirst();
            System.out.println(first.get());
        }
    }
    
    

    并行流

    这里注释已经很全了就不做冗余说明。

    public class ParallelStream {
    
        public static void main(String[] args) {
    //        createParallelStream();
    //        feature2();
    //        feature3();
            feature4();
    
        }
    
        /**
         * 特性一 并行流线程数
         * 并行流线程数默认为cpu个数
         * 默认线程池
         */
        public static void createParallelStream(){
            IntStream.range(1, 100).parallel().forEach(ParallelStream::printDebug);
        }
    
        /**
         * 特性二 并行再串行 以最后一个流为准
         */
        private static void feature2(){
            IntStream.range(1, 100).parallel().peek(ParallelStream::printDebug).sequential().peek(ParallelStream::printDebug2).count();
        }
    
        /**
         * 特性三 默认线程池与设置默认线程数
         */
        private static void feature3(){
            System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism","3");
            IntStream.range(1, 100).parallel().forEach(ParallelStream::printDebug);
        }
    
        /**
         * 特性四 自定义线程池 防止线程被阻塞
         */
        private static void feature4(){
            ForkJoinPool forkJoinPool = new ForkJoinPool();
            forkJoinPool.submit(() -> IntStream.range(1, 100).parallel().forEach(ParallelStream::printDebug));
            forkJoinPool.shutdown();
    
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
    
        private static void printDebug(int i){
    //        System.out.println(i);
            System.out.println(Thread.currentThread().getName() + "debug:" + i);
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        private static void printDebug2(int i){
            System.err.println(i);
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    

    级联表达式与柯里化

    简单来说就是将一个复杂表达式拆解为多个简单表达式,比如数学中的:
    y=5! 可以等价为 y = 1 * 2 * 3 * 4 * 5

    注意:这里涉及一个基础概念数据不变性,说白了就是匿名类中运用到外部变量时,外部变量需要是常量。细心的你会发现在级联表达式中外部变量均为常量。

    /**
     * 级联表达式和柯里化
     *
     * @author 旭旭
     * @create 2018-08-12 1:09
     **/
    public class CurryDemo {
    
        public static void main(String[] args) {
            //级联表达式
            Function<Integer,Function<Integer,Integer>> fun = x -> y -> x + y;
            //柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数
            //柯里化的意义:函数标准化
            //高阶函数:返回函数的函数
            System.out.println(fun.apply(2).apply(3));
    
            Function<Integer,Function<Integer,Function<Integer,Integer>>> fun2 = x -> y -> z -> x + y + z;
    
        }
    }
    

    收集器(终止操作因为内容较多提出来说明)

    终止操作中将数据以集合方式回收,可以对数据进行分类统计等。

    /**
     * 收集器
     *
     * @author 旭旭
     * @create 2018-08-18 23:43
     **/
    public class CollectorsStream {
    
        public static void main(String[] args) {
            List<Student> students = new ArrayList<>();
            students.add(new Student("一号",7,true,"一年级"));
            students.add(new Student("二号",8,true,"二年级"));
            students.add(new Student("三号",8,false,"二年级"));
            students.add(new Student("四号",9,true,"三年级"));
            students.add(new Student("五号",7,false,"一年级"));
            students.add(new Student("六号",8,true,"二年级"));
            students.add(new Student("七号",10,true,"四年级"));
    
    //        dataToList(students);
    //        summary(students);
    //        partitioning(students);
            group(students);
        }
    
        /**
         * 获取某一数据的集合
         */
        public static void dataToList(List<Student> students){
            List<Integer> list = students.stream().map(Student::getAge).collect(Collectors.toList());
            System.out.println(list);
        }
    
        /**
         * 获取某一数据的汇总值
         */
        public static void summary(List<Student> students){
            IntSummaryStatistics collect = students.stream().collect(Collectors.summarizingInt(Student::getAge));
            System.out.println(collect);
        }
    
        /**
         * 根据某一数据分类
         */
        public static void partitioning(List<Student> students){
            Map<Boolean, List<Student>> collect = students.stream().collect(Collectors.partitioningBy(x -> x.isGender()));
            System.out.println(collect);
        }
    
        /**
         * 根据某一数据分组
         */
        public static void group(List<Student> students){
            Map<String, Long> collect = students.stream().collect(Collectors.groupingBy(Student::getGrade, Collectors.counting()));
            System.out.println(collect);
        }
    }
    

    Stream特性

    /**
     * 流运行机制,基本特性
     *
     * @author 旭旭
     * @create 2018-08-19 22:51
     **/
    public class FeatureStream {
        public static void main(String[] args){
    //        feature123();
            feature46();
    //        feature5();
        }
    
        /**
         * 特性一 所有操作都是链式调用一个操作只迭代一次
         * 特性二 每一个中间流返回一个新的流,里面的sourceStage都指向同一个地方就是Head
         * 特性三 Head -> NextStage -> NextStage -> null
         */
        public static void feature123(){
            Random random = new Random();
            Stream<Integer> integerStream = Stream.generate(random::nextInt)
                    .limit(500)
                    .peek(x -> System.out.println("peek -> " + x))
                    .filter(x -> {System.out.println("filter -> " + x);return x > 100000;});
            integerStream.count();
        }
    
        /**
         * 特性四 有状态操作(多个参数操作),会把无状态操作阶段分隔,单独处理。
         * parallel /  sequetial 这个2个操作也是中间操作,但是他们不创建新的流,而是修改
         * Head的并行状态,所以多次调用时只会生效最后一个。
         */
        public static void feature46(){
            Random random = new Random();
            Stream<Integer> integerStream = Stream.generate(random::nextInt)
                    .limit(500)
                    .peek(x -> System.out.println("peek -> " + x))
                    .filter(x -> {System.out.println("filter -> " + x);return x > 100000;})
                    .sorted((x,y) -> {System.out.println("sorted -> " + x);return x - y;})
                    .filter(x -> {System.out.println("filter -> " + x);return x > 100000;})
    //                .parallel()
                    ;
            integerStream.count();
        }
    
    
        /**
         * 特性五 有状态操作并行环境下不一定能并行操作
         */
        public static void feature5(){
            Random random = new Random();
            Stream<Integer> integerStream = Stream.generate(random::nextInt)
                    .limit(500)
                    .peek(x -> print("peek -> " + x))
                    .filter(x -> {print("filter -> " + x);return x > 100000;})
                    .sorted((x,y) -> {print("sorted -> " + x);return x - y;})
                    .filter(x -> {print("filter -> " + x);return x > 100000;})
                    .parallel();
            integerStream.count();
        }
    
        private static void print(String x){
            System.out.println(Thread.currentThread().getName() + " " + x);
        }
    }
    
    

    工程地址

    俗话说的好:光说不练假把式,这里附上工程连接拉,有需要的童靴可以拉下来玩玩。
    https://gitee.com/softcx/functional_programming

    展开全文
  • Java函数式编程》并不是一本关于Java的书,而是一本关于函数式编程的书。作者由浅入深地介绍了函数式编程的思维方式,并引导读者通过易于掌握的例子、练习和图表来学习和巩固函数式编程的基本原则和最佳实践。读者...
  • Java函数式编程》_高清华
  • 主要介绍了Java函数式编程(九):Comparator,本文是系列文章的第9篇,其它文章请参阅本文底部的相关文章,需要的朋友可以参考下
  • 优秀的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")));
     

    展开全文
  • 0202年了,还没有用上Java函数式编程!!!——Lambda表达式函数式编程是什么命令式编程(Imperative)声明式编程(Declarative)函数式编程(Functional)总结函数式编程的好处Lambda表达式函数式接口五种形式1.无参数2....
  • 主要介绍了Java函数式编程(十一):遍历目录,本文是系列文章的第11篇,其它文章请参阅本文底部的相关文章,需要的朋友可以参考下
  • 主要介绍了Java函数式编程(二):列表的转化,lambda表达式不仅能帮助我们遍历集合,并且可以进行集合的转化,需要的朋友可以参考下
  • java函数式编程的好处

    千次阅读 2020-05-05 11:28:59
    Java引入了函数式编程,这表示Java从此不在是一个单纯的面向对象语言,现在他同时混合了函数式编程。这是巨大的改变,需要我们调整面对对象的编程习惯,以适应这些变化。 但是为什么我们需要去适应这些改变?为什么...
  • 函数式编程工作坊 ## 目标我们将通过一系列练习一起发现函数式编程。 我们将看到基本模式和相关的约束。 ## 约束 任何变量都必须是final 。 因此,禁止进行循环,因为它需要一个可更改的计数器。 禁止类继承,只...
  • 主要介绍了Java函数式编程(八):字符串及方法引用,本文是系列文章的第8篇,其它文章请参阅本文底部的相关文章,需要的朋友可以参考下
  • Java函数式编程 课程大纲 ******* 课程大纲 ******* 了解函数式编程的基础知识-Lambda表达式,方法引用,流和函数接口。 for file in * ; do mv " ${file} " " ${file // - / } " ; done for file in * ; do mv " ...
  • 主要介绍了Java函数式编程(一):你好,Lambda表达式,本文讲解了新老函数式编程的一些变化,需要的朋友可以参考下
  • 当日志等级不为1时,不会调用builderMessage()方法,也就不会进行字符串拼接 */ } } 案例2:函数式接口作为返回值 import java.util.Arrays; import java.util.Comparator; public class ComparatorDemo { public ...
  • 主要介绍了Java函数式编程(十):收集器,本文是系列文章的第10篇,其它文章请参阅本文底部的相关文章,需要的朋友可以参考下
  • 主要介绍了Java函数式编程(四):在集合中查找元素,本文是系列文章的第4篇,其它篇章请参阅相关文章,需要的朋友可以参考下
  • 职能 Java函数式编程 它包含用于函数式编程的大量实用程序方法。 查看我的其他项目以进行递归关闭: : 博客条目: :

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 216,526
精华内容 86,610
关键字:

java 函数式编程

java 订阅