JDK8 新特性学习(lambda)

1. Lambda表达式

1.1 函数式编程思想概述

传统的面向对象编程,函数的执行更注重的是根据对象的某种方法执行某个函数,比较依赖于对象。而函数式编程,将对象与函数的这种联系给优化了,不需要我们自己来创建对象和调用函数,换句话说JVM内部会帮我推导出对象于函数之间的联系,这样可以简化我们的编码量,提高开发效率。

1.2 多线程开发中遇到的冗余代码

回顾传统线程创建的三种方式:

  1. 继承Thread,重写run()
public class TestExtends extends Thread{ 
    @Override
    public void run() { 
        System.out.println("线程1....");
    }
}

调用:

//1.继承Thread
TestExtends anExtends = new TestExtends();
anExtends.start();
  1. 实现Runnable接口,重写run()
public class TestRunnable implements Runnable{ 
    @Override
    public void run() { 
        System.out.println("线程2....");

    }
}

调用:

//2.实现Runnable
TestRunnable runnable = new TestRunnable();
new Thread(runnable).start();
  1. 匿名内部类实现
 //3. 使用匿名内部类的实现
 new Thread(new Runnable() { 
     @Override
     public void run() { 
         System.out.println("线程3....");
     }
 }).start();

可以发现,三种方式同样做了一件事,重写run方法,而且它们需要我们手都去创建Runnable的实现类,此时我们想,我们应该更注重线程体里面的内容,而不是去关注其它的细节,引出lambda表达式。

1.3 使用lambda表达式优化

使用 ()——>表达式来简化代码,只需要3行代码即可,()根据情况传参可以传参

 //4.lambda表达式
 new Thread(()->{ 
     System.out.println("线程4....");
 }).start();

1.4 几个练习

1.4.1 使用lambda表达式(无参无返回)

题目:给定一个厨子Cook接口,内含唯一的抽象方法makeFood,且无参无返回值
如下:

public interface Cook { 
    public abstract void makeFood();
}

在下面的代码中,使用lambda表达式的标准格式调用invokeCook方法,打印字符串

public class InvokeCook { 

    public static void main(String[] args) { 
        // TODO 请在此使用Lambda【标准格式】调用invokeCook方法
    }

    public static void invokeCook(Cook cook){ 
        cook.makeFood();
    }
}

解答

public class InvokeCook { 

    public static void main(String[] args) { 
        // TODO 请在此使用Lambda【标准格式】调用invokeCook方法
        InvokeCook.invokeCook(()->{ 
            System.out.println("该吃饭了....");
        });
    }
    public static void invokeCook(Cook cook){ 
        cook.makeFood();
    }
}

1.4.2 使用lambda表达式(有参无返回)

回顾对象比较大小,使用Comparator接口

public class PersonComparator { 
    public static void main(String[] args) { 
        Person[] persons = { new Person("周杰伦",45),
        new Person("林俊杰",33),new Person("成龙",56)};

        Comparator<Person> comparator = new Comparator<Person>() { 
            @Override
            public int compare(Person o1, Person o2) { 
                return o1.getAge() - o2.getAge();
            }
        };
        //排序
        Arrays.sort(persons,comparator);
        for (Person person : persons) { 
            System.out.println(person);
        }
    }
}

使用lambda表达式优化

public class LambdaComparator { 
    public static void main(String[] args) { 
        Person[] persons = { new Person("周杰伦",45),
                new Person("林俊杰",33),new Person("成龙",56)};

        Arrays.sort(persons, ((Person o1,Person o2)->{ 
            return o1.getAge() - o2.getAge();
        }) );
        //遍历
        for (Person person : persons) { 
            System.out.println(person);
        }
    }
}

1.4.3 使用lambda表达式(有参有返回)

问题:定义一个计算器接口类,实现两数之和

public interface Calculator { 
    public int sum(int a,int b);
}

在下面的代码中,请使用Lambda的标准格式调用 invokeCalc 方法,完成120和130的相加计算:

public class InvokeCalculator { 
    public static void main(String[] args) { 
    // TODO 请在此使用Lambda【标准格式】调用invokeCalc方法来计算120+130的结果ß
    }
    public static void calculator(int a,int b,Calculator calculator){ 
        int result = calculator.sum(a, b);
        System.out.println(result);
    }
}

解答

public class InvokeCalculator { 
    public static void main(String[] args) { 
    // TODO 请在此使用Lambda【标准格式】调用invokeCalc方法来计算120+130的结果ß
        InvokeCalculator.calculator(120,130,(int a,int b)->{ 
            return a+b;
        });

    }
    public static void calculator(int a,int b,Calculator calculator){ 
        int result = calculator.sum(a, b);
        System.out.println(result);
    }
}

1.5 继续优化lambda表达式

依据可推导即可省略
如省略了{}和;

 InvokeCook.invokeCook(()->
     System.out.println("该吃饭了....")
 );

省略规则

  1. 小括号内的参数类型可以省略
  2. 如果小括号内只有一个参数,那么小括号也可以省略
  3. 如果大括号内只有一行,无论是否有返回值,均可以省略{},return关键字和尾部分号。

上面代码的一些省略:

 InvokeCalculator.calculator(120,130,( a, b)->
      a+b);
}
Arrays.sort(persons, (( o1, o2)->
     o1.getAge() - o2.getAge()) );

1.6 lambda表达式的使用前提

lambda表达式的语法非常简洁,完全没有面向对象复杂的束缚,但是使用时有几个问题需要注意:

  1. 使用lambda表达式必须具有接口,要求接口中有且只有一个抽象方法。无论是JDK内置的Runnable,Comparator还是自定义的接口,当接口中的抽象方法唯一时,才可以使用lambda表达式。
  2. 使用lambda表达式必须具有上下文推断,也就是方法的参数或局部变量必须为lambda对应的接口类型,才可以使用lambda作为该接口的实例。

备注:有且只有一个抽象方法的接口,称为函数式接口

1.7 函数式接口

1.7.1 概念

有且只有一个抽象方法的接口,称为函数式接口。

函数式接口,即适用于函数式编程场景的接口,而Java函数式编程体现就是lambda,所以函数式接口可以适应于lambda使用的接口,只有确保接口中只有一个抽象方法,lambda才能顺序进行推导。

1.7.2 语法糖

语法糖是指使用更加方便,但原理不变的代码语法,如我们在遍历集合的时候可以使用迭代器,也可以使用for-each遍历,但是迭代器的语法相对复杂,for-each的遍历底层也是使用迭代器,所以可以成for-each是迭代器的语法糖。从应用层面上讲,lambda表达式是匿名内部类的语法糖,但是<mark>两者在原理上又有点不同</mark>。

1.7.3 格式

修饰符 interface 接口名{ 
	pubic abstract 返回值 方法名(参数列表);
	//其它非非抽象函数
}

其中pubic abstract也可以省略。

1.7.3 注解@FunctionalInterface

作用是被FuncationalInterface标注的接口是一个函数式接口,即接口内只能包含一个抽象的方法,否则编译失败。

包含两个:

2. 函数式编程

2.1 Lambda的延迟执行

由一个日志日志案例来引出:

public class TestLogger { 

    public static void logger(int leave,String msg){ 
        System.out.println(msg);   //liuzeyuduyanting
        if( leave == 1){ 
            System.out.println(msg);
        }
    }

    public static void main(String[] args) { 
        String msg1 = "liuzeyu";
        String msg2 = "duyanting";

        logger(2,msg1 + msg2);
    }
}

上面是一个打印日志信息的案例,目的是有且只有leave = 1的时候,才打印输出拼接字符串。但是我们输入leave = 2的时候,也进行了字符串的拼接,性能浪费了。
使用lambda表达式优化

@FunctionalInterface
public interface LoggerInterface { 
    public String logger();
}
public class TestLogger { 

    public static void logger(int leave,LoggerInterface myInterface){ 
        if(leave == 1){ 
            System.out.println(myInterface.logger());
        }
    }

    public static void main(String[] args) { 
        String msg1 = "liuzeyu";
        String msg2 = "duyanting";
        logger(2,()->{ 
            System.out.println("lambda延迟了...");
            return msg1 + msg2;
        });
    }
}

此时字符串拼接的地方在lambda表达式的内部,可以做到延迟拼接的作用,提高了性能。

2.2 常用的函数式接口

JDK提供了大量常用的函数式接口,主要在java.util.function包中被提供,下面式几个常用的接口。

2.2.1 Supplier接口

java.util.function.Supplier接口中只包含了一个无参的抽象方法,T get(),泛型传什么,get()就返回什么,常适用于生产环境下。

public class SupplierDemo1 { 

    public static String fun1(Supplier<String> supplier){ 
        return  supplier.get();
    }

    public static void main(String[] args) { 
// String value = fun1(() -> { 
// return "liuzeyu" + "duyanting";
// });
        //简化代码:
        String value = fun1(() ->
           "liuzeyu" + "duyanting");

        System.out.println(value);  //liuzeyuduyanting
    }
}

练习:求数组中最大值

public class SupplierDemo2 { 

    public static  Integer getMax(Supplier<Integer> supplier){ 
        return supplier.get();
    }

    public static void main(String[] args) { 
        int[] arr = { 11,343,42,547,12};

        int maxValue = getMax(()->{ 
            int max = arr[0];
            for (int i:
                    arr) { 
                if( i > max){ 
                    max = i;
                }
            }
            return max;
        });
        System.out.println(maxValue); //547
    }
}

2.2.2 Consumer接口

  1. 接口概述
@FunctionalInterface
public interface Consumer<T> { 

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

Consumer接口的作用刚好了Supplier相反,Supplier的作用是泛型接收一个参数类型,就可以返回数据,主要用于生产环境下,因为它不会对数据进行修改,而是直接返回。而Consumer接口可以修改数据,使用数据,常用于消费环境。

  1. 抽象方法accept

accept方法作用是可以消费一个接收泛型的数据

public class ConsumerDemo1 { 

    public static void fun1(String str,Consumer<String> con){ 
        con.accept(str);
    }

    public static void main(String[] args) { 

        fun1("Hello",(str)->{ 
            String s = new StringBuilder(str).reverse().toString();
            System.out.println(s);  //olleH
        });
    }
}

  1. 默认方法andThen

如果一个方法的参数和返回值都是Consumer类型,那么就可以实现效果,首先做一个动作,然后再做一个动作,实现两个动作的组合,这个时候就需要用到Consumer接口中的andThen方法。
实现组合需要用到多个lambda表达式,andThen方法的语义是进行一步一步操作,例如:

public class ConsumerDemo2 { 

    public static void convert(String string, Consumer<String> con1, Consumer<String> con2){ 
        con1.andThen(con2).accept(string);  //先执行动作con1再执行动作con2
    }

    public static void main(String[] args) { 
        convert("Hello",(string)->{ 
            String lowerCase = string.toLowerCase();
            System.out.println(lowerCase); //hello
        },(string)->{ 
            String upperCase = string.toUpperCase();
            System.out.println(upperCase);  //HELLO
        });
    }
}
  1. 实现格式化数据

需求:将数据String[] info = {"刘泽煜,22","杜丫头,20"};数组中的每个信息,格式化为姓名:年龄,例如 刘泽煜:22

public class ConsumerDemo3 { 

    public static  void form(String[] strs, Consumer<String>con1,Consumer<String> con2){ 
        //将数组中的每一个元素进行
        for (String str : strs) { 
            con1.andThen(con2).accept(str);
        }
    }

    public static void main(String[] args) { 
        String[] info = { "刘泽煜,22","杜丫头,20"};
        form(info,(string)->{ 
            String name = string.split(",")[0];
            System.out.print(name+":");
        },(string)->{ 
            String age = string.split(",")[1];
            System.out.println(age);
        });
    }
}

2.2.3 Predicate接口

我们需要对某个数据进行判断,使用抽象方法test(),然后得到布尔值,这个时候可以使用Predicate接口,Predicate也是java.util.function报下的类

  1. 抽象方法test()
public class PredicateDemo1 { 
    public static void testLong(String str, Predicate<String> predicate){ 
        boolean test = predicate.test(str);
        System.out.println(test);  //true
    }

    public static void main(String[] args) { 
        testLong("ILoveJava",(String str)->{ 
            return str.length() > 5;
        });
    }
}

  1. 默认方法and
    and函数表示并且,如果两个条件都满足就返回true,否则false

举例:判断字符串的长度是否大于5,第一个字符是不是为L

public class PredicateDemo2 { 
    public static void testAnd(String str, Predicate<String> p1,Predicate<String> p2){ 
        boolean test = p1.and(p2).test(str);
        System.out.println(test);
    }

    public static void main(String[] args) { 
        testAnd("Liuzeyu",(String str)->{ 
            return str.length() > 5;
        },(String str)->{ 
            return  str.charAt(0) == 'L';
        });
    }
}

  1. 默认方法or
    或者的意思,满足条件之一即可返回true
  2. 默认方法negate
    取反。

练习
数组中有多条姓名+性别信息,请通过Predicate接口实习信息的筛选,筛选到ArrayList集合中,需要同时满足以下条件:

  • 必须为女生
  • 名字为4个字
public class PredicateDemo3 { 

    public static void main(String[] args) { 
        String[] infos = {  "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男", "赵丽颖,女" };
        ArrayList<String> list = getArrayList(infos, (String str) -> { 
            return str.split(",")[0].length() == 4;
        }, (String str) -> { 
            return str.split(",")[1].equals("女");
        });

        System.out.println(list);
    }

    public static ArrayList<String> getArrayList(String[] strs, Predicate<String> p1,Predicate<String> p2){ 
        ArrayList<String> info = new ArrayList<>();
        for (String str : strs) { 
            boolean test = p1.and(p2).test(str);
            if(test){ 
                info.add(str);
            }
        }
        return info;
    }
}

2.2.4 Function接口

Function接口实现的功能是将一种类型转换成另一种类型,使用的是内部的抽象方法R apply(T t); t是要转换的对象类型,R是转换后的对象类型

  1. 抽象方法apply
    举例:使用Function接口将String类型转成Integer类型
public class FunctionDemo1 { 

    public static  Integer convert(String str, Function<String,Integer> function){ 
        Integer apply = function.apply(str);
        return apply;
    }

    public static void main(String[] args) { 
        Integer integer = convert("123213", (String str) -> { 
            return  Integer.parseInt(str);
        });
        System.out.println(integer+10);
    }
}
  1. 默认方法andThen

使用andThen进行组合操作
举例:将String类型的123转换成Integer类型后,将结果加10,结果再转换成String类型

public class FunctionDemo2 { 

    public static String convert(String str,
                               Function<String,Integer> fun1,Function<Integer,String> fun2){ 
        return  fun1.andThen(fun2).apply(str);
    }

    public static void main(String[] args) { 
        String convert = convert("123", (String str) -> { 
            return Integer.parseInt(str) + 10;
        }, (Integer integer) -> { 
            return String.valueOf(integer);
        });

        System.out.println(convert);
    }
}

  1. 练习
    请使用 Function 进行函数模型的拼接,按照顺序需要执行的多个函数操作为:

    String str = “赵丽颖,20”;

    1. 将字符串截取数字年龄部分,得到字符串;
    2. 将上一步的字符串转换成为int类型的数字;
    3. 将上一步的int数字累加100,得到结果int数字。
public class FunctionDemo3 { 
    public static String convert(String str,
                               Function<String,String> fun1,
                               Function<String,Integer> fun2,
                               Function<Integer,String> fun3){ 
        return fun1.andThen(fun2).andThen(fun3).apply(str);
    }

    public static void main(String[] args) { 
        String str = "赵丽颖,20";
        String convert = convert(str, (String s) -> { 
            return s.split(",")[1];
        }, (String s) -> { 
            return Integer.parseInt(s) + 100;
        }, (Integer i) -> { 
            return String.valueOf(i);
        });

        System.out.println(convert); //120
    }
}

3. Stream流

谈到流,可能第一时间想到的是我们之前学过的IO流,但实际上Stream流和IO流并不是一个东西,Stream流是JDK 1.8引入的,得益于lambda表达式所带来的函数式编程,目的是解决集合类存在的弊端。

3.1 传统集合类存在的弊端

需求:如果要完成:

  1. 首先筛选所有姓张的人;
  2. 然后筛选名字有三个字的人;
  3. 最后进行对结果进行打印输出。

使用传统的集合类操作的话:

public class StreamDemo1 { 
    public static void main(String[] args) { 
        List<String> list = new ArrayList<>();
        list.add("张无忌");
        list.add("周芷若");
        list.add("赵敏");
        list.add("张强");
        list.add("张三丰");
        //首先筛选所有姓张的人;
        List<String> list2 = new ArrayList<>();
        for (String s : list) { 
            if(s.startsWith("张")){ 
                list2.add(s);
            }
        }
        //然后筛选名字有三个字的人;
        List<String> list3 = new ArrayList<>();
        for (String s : list2) { 
            if(s.length() == 3){ 
                list3.add(s);
            }
        }
        //最后进行对结果进行打印输出。
        for (String s : list3) { 
            System.out.println(s);
        }
    }
}

可见,代码量繁重,而且冗余,用到了多个循环,很影响性能。

3.2 Stream流可以解决的弊端

public class StreamDemo2 { 
    public static void main(String[] args) { 
        List<String> list = new ArrayList<>();
        list.add("张无忌");
        list.add("周芷若");
        list.add("赵敏");
        list.add("张强");
        list.add("张三丰");

        list.stream().filter((s)->s.startsWith("张"))
                .filter((s)->s.length() == 3)
                .forEach(s -> System.out.println(s));
    }
}

从代码量来看,确实少了很多,变得更加简洁了。

3.3 Stream流思想的简单概述

切勿将此处的流与IO流混为一谈,这是两个不同的东西,Stream流类似于车间的流水线,每一个物品经历流水线可能会经过一些步骤:如加工步骤,包装,拆分…等等步骤。集合元素在Stream上也会同样尽力一些处理步骤,这个过程集合元素本身没有发生改变,类似于copy一个集合的副本放在流水线上进行一些操作,在Stream中不会存储任何元素,它也类似于来自于数据源额元素队列,数据源可以是集合,数组等。
与之前的Collection操作不同,Stream流还有两个基础的特征:

  • Pipelining:中间操作每次都会返回流本身,这样多个操作可以串联成一个管道,如同流式风格。
  • 内部迭代:以前对集合的遍历一般都是采用迭代器或增强for循环,采用的的外部迭代方式,而Stream流提供的是内部迭代的方式,流可以直接调用遍历方法。

当你使用一个流的时候,包括3个步骤:获取数据源->数据转换->执行操作获取想要的结果,每次转换原有的Stream对象不变,返回一个新的Stream对象。可以有多次转换,这就允许对其操作可以像链条一样排列,变成一个管道。

3.4 根据不同集合类型获取流

public class StreamDemo1 { 
    public static void main(String[] args) { 
        //根据Collectioin获取的流
        List<String> list = new ArrayList<String>();
        Stream<String> stream1 = list.stream();
        Set<String> set = new HashSet<>();
        Stream<String> stream2 = set.stream();
        //根据Map获取流
        Map<String,String> map = new HashMap<>();
        Set<String> keySet = map.keySet();
        Stream<String> stream3 = keySet.stream();
        Set<Map.Entry<String, String>> entrySet = map.entrySet();
        Stream<Map.Entry<String, String>> stream4 = entrySet.stream();
        Collection<String> values = map.values();
        Stream<String> stream5 = values.stream();
        //根据数组获取流
        String[] names = new String[3];
        names[0]="xxx";
        names[1]="yyy";
        Stream<String> stream6 = Arrays.stream(names);

    }
}

运行结果:

3.5 流的常用方法

流模型的操作很丰富,主要的操作方法可以分为两类:

  • 延迟方法:返回值类型仍然是Stream类型,因此支持链式调用,除了终结方法外,其它的方法都属于延迟方法。
  • 终结方法:返回值不再是Stream类型的方法,不支持链式调用,终结方法有foreach和count。

3.5.1 forEach:逐一处理

public class StreamDemo2 { 
    public static void main(String[] args) { 
        String[] names = { "liuzeyu","duyanting","jay"};
        Stream<String> stream = Stream.of(names);

// stream.forEach((String name)->{ 
// System.out.println(name);
// });
        stream.forEach(name-> System.out.println(name));
    }
}

运行结果:

3.5.2 count:统计个数

public class StreamDemo3 { 
    public static void main(String[] args) { 
        Integer[] ids = { 1,2,3,5,6};
        Stream<Integer> stream = Stream.of(ids);
        long count = stream.count();
        System.out.println(count);  //5
    }
}

3.5.3 filter:过滤

用到Predicate<T>接口函数

public class StreamDemo4 { 
    public static void main(String[] args) { 
        List<String> list = new ArrayList<>();
        list.add("张无忌");
        list.add("周芷若");
        list.add("赵敏");
        list.add("张强");
        list.add("张三丰");

        Stream<String> stream = list.stream();
        stream.filter((String name)->{ return name.startsWith("张");})
                .filter((String name)->{ return name.length() == 3;})
                .forEach((String name)->{ 
                    System.out.println(name);
                });
    }
}

filter每个操作都会返回一个新的Stream对象。
运行结果:

3.5.4 map:映射

用到Function<T,R>函数式接口

public class StreamDemo5 { 
    public static void main(String[] args) { 
        Integer[] ids = { 1,2,3,5,6};
        //将Integer类型转换成String
        Stream<Integer> stream = Stream.of(ids);
        Stream<String> stringStream = stream.map((Integer id) -> { 
            return String.valueOf(id);
        });
        stringStream.forEach(name-> System.out.println(name));
    }
}

运行结果:

3.5.5 limit:提取前几个

public class StreamDemo6 { 

    public static void main(String[] args) { 
        List<String> list = new ArrayList<>();
        list.add("张无忌");
        list.add("周芷若");
        list.add("赵敏");
        list.add("张强");
        list.add("张三丰");

        Stream<String> stream = list.stream();
        Stream<String> limit = stream.limit(3);
        limit.forEach(name-> System.out.println(name));
    }
}

运行结果:

3.5.6 skip:跳过前几个

public class StreamDemo7 { 
    public static void main(String[] args) { 
        List<String> list = new ArrayList<>();
        list.add("张无忌");
        list.add("周芷若");
        list.add("赵敏");
        list.add("张强");
        list.add("张三丰");

        Stream<String> stream = list.stream();
        Stream<String> skip = stream.skip(2);
        skip.forEach(name-> System.out.println(name));
    }
}

运行结果:

3.5.7 concat:组合

public class StreamDemo8 { 
    public static void main(String[] args) { 
        List<String> list = new ArrayList<>();
        list.add("张无忌");
        list.add("周芷若");
        list.add("赵敏");
        list.add("张强");
        list.add("张三丰");

        Integer[] ids = { 1,2,3,5,6};

        Stream<String> stream1 = list.stream();
        Stream<Integer> stream2 = Stream.of(ids);
        Stream<? extends Serializable> concat = Stream.concat(stream1, stream2);
        concat.forEach(name-> System.out.println(name));
    }
}

运行结果:

3.6 练习

需求:

  1. 第一个队伍只要名字为3个字的成员姓名;存储到一个新集合中。
  2. 第一个队伍筛选之后只要前3个人;存储到一个新集合中。
  3. 第二个队伍只要姓张的成员姓名;存储到一个新集合中。
  4. 第二个队伍筛选之后不要前2个人;存储到一个新集合中。
  5. 将两个队伍合并为一个队伍;存储到一个新集合中。
  6. 根据姓名创建 Person 对象;存储到一个新集合中。
  7. 打印整个队伍的Person对象信息。
public class Person { 
    private String name;
    public Person() { 
    }
    public Person(String name) { 
        this.name = name;
    }
    @Override
    public String toString() { 
        return "Person{" +
                "name='" + name + '\'' +
                '}';
    }
    ....代码省略
public class ListDemo { 
    public static void main(String[] args) { 
        //第一支队伍
        ArrayList<String> one = new ArrayList<>();
        one.add("迪丽热巴");
        one.add("宋远桥");
        one.add("苏星河");
        one.add("石破天");
        one.add("石中玉");
        one.add("老子");
        one.add("庄子");
        one.add("洪七公");
        //第二支队伍
        ArrayList<String> two = new ArrayList<>();
        two.add("古力娜扎");
        two.add("张无忌");
        two.add("赵丽颖");
        two.add("张三丰");
        two.add("尼古拉斯赵四");
        two.add("张天爱");
        two.add("张二狗");

        Stream<String> stream1 = one.stream();
        Stream<String> stream2 = two.stream();

        //利用流思想
        //队伍1
        Stream<String> limit = stream1.filter(name -> name.length() == 3).limit(3);
        //队伍2
        Stream<String> skip = stream2.filter(name -> name.startsWith("张")).skip(2);
        //合为一只队伍
        Stream<String> concat = Stream.concat(limit, skip);
        //存储到Person对象中
        List<Person> persons = new ArrayList<>();
        concat.forEach(name->persons.add(new Person(name)));
        for (Person person : persons) { 
            System.out.println(person);
        }
    }
}

运行结果:

4. 方法引用(不好理解)

在使用lambda表达式的时候,我们实际传递进去的代码就是一种解决方案,拿什么参数就做什么操作,那么考虑另外一种情况,如果lambda中所指定的操作方案,已经有地方存在相同的方案,那是否有必要重复再写一份呢?

4.1 方法引用

public class MethodRef { 
    public static void printString(Printable printable){ 
        printable.printMsg("Hello Java");
    }

    public static void main(String[] args) { 
// printString((String str)->{ 
// System.out.println(str);
// });
        //使用方法引用
        printString(System.out::print);
    }
}
interface Printable{ 
    void printMsg(String msg);
}

printString(System.out::print);传递的参数System.out::print,很奇怪是吧,为什么传递这样一个参数,就可以替代(String str)->{ System.out.println(str); }

  • 使用lambda表达式获取到传递的参数后,再将它传递给System.out.println(str);
  • 而使用方法引用,是直接让System.out中的println来取代lambda表达式,两种写法执行效果完全一致,而第二种方法引用的写法复用了已有的方案,更加简洁。

注意:lambda表达式中传递的参数<mark>一定是方法引用中那个方法可以接收的参数类型</mark>,否则会抛出异常。

再看一个例子:

public class MethodRef { 
    public static void printString(Printable printable){ 
        printable.printMsg(3434);
    }

    public static void main(String[] args) { 
// printString((Integer i)->{ 
// System.out.println(i);
// });
        //使用方法引用
        printString(System.out::print);
    }
}
interface Printable{ 
    void printMsg(Integer msg);
}

可见无论使用lambda表达式还是使用方法引用,都是根据上下文推导出,接收的相应参数类型,来输出相应的参数值。

4.2 对象名引用成员方法

这也是一种常见的用法,准备好MethodRefObject类

public class MethodRefObject { 
    public void printUpperCase(String msg){ 
        System.out.println(msg);
    }
}

函数式接口

interface Printable2{ 
    void print(String msg);
}

那么当需要使用printUpperCase方法来替代Printable2 接口的lambda表达式的时候,就已经具有了MethodRefObject类的对象实例,所以可以通过对象类引用方法名:

public class MethodRef2 { 
    public static void pringString(Printable2 printable2){ 
        printable2.print("Hello");
    }

    public static void main(String[] args) { 
        //1.使用lambda表达式
// pringString((String msg)->{ 
// MethodRefObject mfo = new MethodRefObject();
// mfo.printUpperCase(msg);
// });
        //2.使用对象的方法引用
        MethodRefObject mfo = new MethodRefObject();
        pringString(mfo::printUpperCase);

    }
}

4.3 通过类型引用静态方法

同样的方法,当需要使用abs来替代Calcable 接口lambda表达式的时候,由于abs是静态方法,于是可以使用类名调用

public class MethodRef3 { 
    public static void  method(int num,Calcable cal){ 
        System.out.println(cal.calc(num));
    }
    public static void main(String[] args) { 
        //1.使用lambda表达式
// method(10,(int num)->{ 
// return Math.abs(num);
// });
        //method(-10, num-> Math.abs(num));

        //2. 使用方法引用
        method(-1,Math::abs);
    }

}
@FunctionalInterface
interface Calcable{ 
    int calc(int sum);
}

4.4 super引用父类的方法

super引用父类的方法:super:父类方法
准备父类:

public class Human { 
    void sayHello(){ 
        System.out.println("Hello....");
    }
}

准备函数式接口:

@FunctionalInterface
public interface Greetable { 
    void greet();
}

准备子类:

public class Man extends Human { 
    @Override
    void sayHello() { 
        super.sayHello();
    }

    //定义method,传递参数
    public void method(Greetable g){ 
        g.greet();
    }

    public void show(){ 
        //调用method方法,使用lambda表达式
        method(()->{ 
            //创建父类Human对象,调用sayHello方法
            new Human().sayHello();
        });
        //使用方法引用
        method(super::sayHello);
    }


    public static void main(String[] args) { 
        new Man().show();
    }

}

super::sayHello的作用和使用lambda表达式一样。

4.5 this引用本类的成员方法

this引用本类的成员方法采用: this:方法名

函数式接口:

@FunctionalInterface
public interface Richeable { 
    void buy();
}

Husband 类:

package methodreference.thisRef;

public class Husband { 
    private  void marry(Richeable r){ 
        r.buy();
    }

    private void byHouse(){ 
        System.out.println("买套新房...");
    }

    public void beHappy(){ 
        marry(()->{ 
            byHouse();          //买套新房...
        });
        marry(()->{ 
            this.byHouse();     //买套新房...
        });
        //使用方法引用
        marry(this::byHouse);  //买套新房...
    }


    public static void main(String[] args) { 
        new Husband().beHappy();
    }
}

4.6 类的构造器的引用

构造器的引用采用的是:类名:new
准备Person 实体类:

public class Person { 
    private String name;
    public Person(String name) { 
        this.name = name;
    }
    public Person() { 
    }
    public String getName() { 
        return name;
    }
    public void setName(String name) { 
        this.name = name;
    }
}

准备函数式接口:

@FunctionalInterface
public interface PersonBuilder { 
    public Person buildPerson(String name);
}

测试类:

public class Test { 

    public static void printName(String name,PersonBuilder pb){ 
        System.out.println(pb.buildPerson(name).getName());
    }

    public static void main(String[] args) { 
        printName("张三丰",(String name)->{ 
            return  new Person(name);
        });
        //使用方法引用
        printName("张无忌",Person::new);
    }
}

4.7 数组构造器的引用

数组也是Object的子类,所以同样具有构造器,只是引用的方法有所不同,采用的是:数组类型[]:new

函数式接口:

@FunctionalInterface
public interface ArrayBuilder { 
    //根据数组长度创建整形数组
    int[] buidArray(int len);
}

测试类:

public class Test { 
    private static int[] initArray(int len,ArrayBuilder ab){ 
        return ab.buidArray(len);
    }

    public static void main(String[] args) { 
        int[] array1 = initArray(10, (len) -> { 
            return new int[len];
        });

        int[] array2 = initArray(12, int[]::new);
        System.out.println(array1.length);
        System.out.println(array2.length);
    }
}

参考:黑马视频+笔记