Java8 新特性 (四)方法引用与构造器引用 一、方法引用(Method Reference) 二、构造器引用 三、数组引用 四、为什么要引入方法引用? 五、方法引用符 六、方法引用案例 七、构造器引用案例 八、数组引用案例

  1、使用场景当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!

  2、方法引用可以看做是Lambda表达式深层次的表达。换句话说,方法引用就是Lambda表达式,也就是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是Lambda表达式的一个语法糖。

  3、要求: 实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致!

  4、语法格式:

    格式:使用操作符 “::” 将类(或对象)与方法名分隔开来。

    如下三种主要使用情况:

对象::实例方法名

类::静态方法名

类::实例方法名

二、构造器引用

  格式:

ClassName::new

  

  与函数式接口相结合,自动与函数式接口中方法兼容。
  可以把构造器引用赋值给定义的方法,要求构造器参数列表要与接口中抽象方法的参数列表一致!且方法的返回值即为构造器对应类的对象。

三、数组引用

  格式:

type[] :: new

  

四、为什么要引入方法引用?

  1、冗余的 Lambda 场景

    来看一个简单的函数式接口以应用Lambda表达式:

1 @FunctionalInterface
2 public interface Printable {
3     void print(String str);
4 }

    在 Printable 接口当中唯一的抽象方法 print 接收一个字符串参数,目的就是为了打印显示它。那么通过Lambda来使用它的代码很简单:

1 public class Demo01PrintSimple {
2   private static void printString(Printable data) {
3     data.print("Hello, World!");
4   } 
5   public static void main(String[] args) {
6     printString(s ‐> System.out.println(s));
7   }
8 }

     其中 printString 方法只管调用 Printable 接口的 print 方法,而并不管 print 方法的具体实现逻辑会将字符串打印到什么地方去。

     而 main 方法通过Lambda表达式指定了函数式接口 Printable 的具体操作方案为:拿到String(类型可推导,所以可省略)数据后,在控制台中输出它

  2、问题分析

    这段代码的问题在于,对字符串进行控制台打印输出的操作方案,明明已经有了现成的实现,那就是 System.out对象中的 println(String) 方法。

      既然Lambda希望做的事情就是调用 println(String) 方法,那何必自己手动调用呢?

  3、用方法引用改进代码

  能否省去Lambda的语法格式(尽管它已经相当简洁)呢?只要引用过去就好了: 

1    public class DemoPrintRef {
2         private static void printString(Printable data) {
3             data.print("Hello, World!");
4         } 
5         public static void main(String[] args) {
6             printString(System.out::println);
7         }
8     }

     请注意其中的双冒号 :: 写法,这被称为方法引用,双冒号是一种新的语法。

  4、

五、方法引用符

    双冒号 :: 为引用运算符,而它所在的表达式被称为方法引用。如果Lambda要表达的函数方案已经存在于某个方法的实现中,那么则可以通过双冒号来引用该方法作为Lambda的替代者。

    1、语义分析

      例如上例中, System.out 对象中有一个重载的 println(String) 方法恰好就是我们所需要的。那么对于printString 方法的函数式接口参数,对比下面两种写法,完全等效:

      ① Lambda表达式写法: -> System.out.println(s);

         ② 方法引用写法: System.out::println

       第一种语义是指:拿到参数之后经Lambda之手,继而传递给 System.out.println 方法去处理。

       第二种等效写法的语义是指:直接让 System.out 中的 println 方法来取代Lambda。两种写法的执行效果完全样,而第二种方法引用的写法复用了已有方案,更加简洁

      注意Lambda 中 传递的参数 一定是方法引用中 的那个方法可以接收的类型,否则会抛出异常。

    2、推导与省略

      如果使用Lambda,那么根据可推导就是可省略的原则,无需指定参数类型,也无需指定的重载形式——它们都将被自动推导。而如果使用方法引用,也是同样可以根据上下文进行推导。

      函数式接口是Lambda的基础,而方法引用是Lambda的孪生兄弟。

     下面这段代码将会调用 println 方法的不同重载形式,将函数式接口改为int类型的参数:

1 @FunctionalInterface
2 public interface PrintableInteger {
3     void print(int str);
4 }

     由于上下文变了之后可以自动推导出唯一对应的匹配重载,所以方法引用没有任何变化:

1    public class DemoPrintOverload {
2         private static void printInteger(PrintableInteger data) {
3             data.print(1024);
4         } 
5         public static void main(String[] args) {
6             printInteger(System.out::println);
7         }
8     }

  

    这次方法引用将会自动匹配到 println(int) 的重载形式 

六、方法引用案例

  1、情况一:对象::实例方法

    Demo1:

 1     //Consumer中的void accept(T t)
 2     //PrintStream中的void println(T t)
 3     @Test  
 4     public void test1() {
 5         Consumer<String> con1 = str -> System.out.println(str);
 6         con1.accept("北京");
 7 
 8         System.out.println("*******************");
 9         PrintStream ps = System.out;
10         Consumer<String> con2 = ps::println;
11         con2.accept("beijing");
12     }

    Demo2

 1     //Supplier中的T get()
 2     //Employee中的String getName()
 3     @Test
 4     public void test2() {
 5         Employee emp = new Employee(1001,"Tom",23,5600);
 6 
 7         Supplier<String> sup1 = () -> emp.getName();
 8         System.out.println(sup1.get());
 9 
10         System.out.println("*******************");
11         Supplier<String> sup2 = emp::getName;
12         System.out.println(sup2.get());
13 
14     }

  2、情况二:类::静态方法

    Demo1:

 1     //Comparator中的int compare(T t1,T t2)
 2     //Integer中的int compare(T t1,T t2)
 3     @Test
 4     public void test3() {
 5         Comparator<Integer> com1 = (t1,t2) -> Integer.compare(t1,t2);
 6         System.out.println(com1.compare(12,21));
 7 
 8         System.out.println("*******************");
 9 
10         Comparator<Integer> com2 = Integer::compare;
11         System.out.println(com2.compare(12,3));
12 
13     }

    Demo2:

 1     //Function中的R apply(T t)
 2     //Math中的Long round(Double d)
 3     @Test
 4     public void test4() {
 5         Function<Double,Long> func = new Function<Double, Long>() {
 6             @Override
 7             public Long apply(Double d) {
 8                 return Math.round(d);
 9             }
10         };
11 
12         System.out.println("*******************");
13 
14         Function<Double,Long> func1 = d -> Math.round(d);
15         System.out.println(func1.apply(12.3));
16 
17         System.out.println("*******************");
18 
19         Function<Double,Long> func2 = Math::round;
20         System.out.println(func2.apply(12.6));
21     }

  3、情况三:类::非静态方法(类::实例方法)

    Demo1:

 1     // Comparator中的int comapre(T t1,T t2)
 2     // String中的int t1.compareTo(t2)
 3     @Test
 4     public void test5() {
 5         Comparator<String> com1 = (s1,s2) -> s1.compareTo(s2);
 6         System.out.println(com1.compare("abc","abd"));
 7 
 8         System.out.println("*******************");
 9 
10         Comparator<String> com2 = String :: compareTo;
11         System.out.println(com2.compare("abd","abm"));
12     }

    Demo2:

 1     //BiPredicate中的boolean test(T t1, T t2);
 2     //String中的boolean t1.equals(t2)
 3     @Test
 4     public void test6() {
 5         BiPredicate<String,String> pre1 = (s1,s2) -> s1.equals(s2);
 6         System.out.println(pre1.test("abc","abc"));
 7 
 8         System.out.println("*******************");
 9         BiPredicate<String,String> pre2 = String :: equals;
10         System.out.println(pre2.test("abc","abd"));
11     }

    Demo3:

 1     // Function中的R apply(T t)
 2     // Employee中的String getName();
 3     @Test
 4     public void test7() {
 5         Employee employee = new Employee(1001, "Jerry", 23, 6000);
 6 
 7 
 8         Function<Employee,String> func1 = e -> e.getName();
 9         System.out.println(func1.apply(employee));
10 
11         System.out.println("*******************");
12 
13 
14         Function<Employee,String> func2 = Employee::getName;
15         System.out.println(func2.apply(employee));
16 
17 
18     }

  4、总结

    针对于情况1和情况2:方法引用使用的要求:要求接口中的抽象方法的形参列表和返回值类型与方法引用的方法的形参列表和返回值类型相同!

    

七、构造器引用案例

    构造器引用:和方法引用类似,函数式接口的抽象方法的形参列表和构造器的形参列表一致。

    抽象方法的返回值类型即为构造器所属的类的类型。

    案例1:无参构造

 1     //Supplier中的T get()
 2     //Employee的空参构造器:Employee()
 3     @Test
 4     public void test1(){
 5 
 6         Supplier<Employee> sup = new Supplier<Employee>() {
 7             @Override
 8             public Employee get() {
 9                 return new Employee();
10             }
11         };
12         System.out.println("*******************");
13 
14         Supplier<Employee>  sup1 = () -> new Employee();
15         System.out.println(sup1.get());
16 
17         System.out.println("*******************");
18 
19         Supplier<Employee>  sup2 = Employee :: new;
20         System.out.println(sup2.get());
21     }

    案例2:需要一个参数的构造器

 1     //Function中的R apply(T t)
 2     @Test
 3     public void test2(){
 4         Function<Integer,Employee> func1 = id -> new Employee(id);
 5         Employee employee = func1.apply(1001);
 6         System.out.println(employee);
 7 
 8         System.out.println("*******************");
 9 
10         Function<Integer,Employee> func2 = Employee :: new;
11         Employee employee1 = func2.apply(1002);
12         System.out.println(employee1);
13 
14     }

    案例3:需要两个参数的构造器

 1     //BiFunction中的R apply(T t,U u)
 2     @Test
 3     public void test3(){
 4         BiFunction<Integer,String,Employee> func1 = (id,name) -> new Employee(id,name);
 5         System.out.println(func1.apply(1001,"Tom"));
 6 
 7         System.out.println("*******************");
 8 
 9         BiFunction<Integer,String,Employee> func2 = Employee :: new;
10         System.out.println(func2.apply(1002,"Tom"));
11 
12     }

八、数组引用案例

    数组引用:可以把数组看做是一个特殊的类,则写法与构造器引用一致。

    案例:

 1     //Function中的R apply(T t)
 2     @Test
 3     public void test4(){
 4         Function<Integer,String[]> func1 = length -> new String[length];
 5         String[] arr1 = func1.apply(5);
 6         System.out.println(Arrays.toString(arr1));
 7 
 8         System.out.println("*******************");
 9 
10         Function<Integer,String[]> func2 = String[] :: new;
11         String[] arr2 = func2.apply(10);
12         System.out.println(Arrays.toString(arr2));
13 
14     }