方法引用由来
从 JDK8新特性 一文 『Lambda表达式』模块,当我们学习了 1.Lambda表达式 2.Lambda表达式原理分析 3.Lambda表达式----常用的内置函数式接口 ,学习了 Lambda 表达式以及它的简写形式,并使用 Lambda 表达式可以对代码进行简化。 但是 Lambda 表达式在某些情况下还是有可能会出现代码冗余的情况。
下面我们就来举一个栗子:使用 Lambda 表达式来求一个数组的和
public class MethodRefDemo{
public static void main(String[] args){
int resSum = printSum((int[] arr)->{
int sum = 0;
for(int n : arr){
sum += n;
}
return sum;
});
System.out.println("数组求和:"+resSum);
}
//使用 Lambda表达式 编写的printSum()方法
public static int printSum(Function<int[],Integer> function){
int[] arr = {11,22,33,44,55,66};
return function.apply(arr);
}
//数组求和方法 getSum()
public static int getSum(int[] arr){
int sum = 0;
for(int n : arr){
sum += n;
}
return sum;
}
}
我们分析发现,类中已经有一个 getSum() 方法可以用来为数组求和,但是此处为了使用 Lambda 表达式,迫于Lambda表达式使用的前提:①参数必须是抽象方法 ②接口类只有一个抽象方法。显然getSum()方法不能满足该条件,所以我们需要重新编写一个 printSum()方法,参数为 Function 函数式接口。
但是,使用 Lambda 表达式在调用 printSum() 方法时,就需要对数组求和方法在 Lambda 表达式中再重新写一次,显然这个求和的方法体已经和 getSum()方法有所重复。此处显然是冗余情况
针对这种冗余情况,你会说为什么不直接调用 getSum() 方法呢?是的,可以直接调用 getSum() 方法,如下所示
public class MethodRefDemo{
public static void main(String[] args){
int resSum = printSum(arr->{
//此处直接调用 getSum()方法
return getSum(arr);
});
System.out.println("数组求和:"+resSum);
}
//部分重复代码省略(printSum()、getSum())
}
我们再来分析,printSum()方法处 Lambda 表达式除了调用 getSum()方法,又啥都没做。所以在此处 Lambda 表达式代码显然也是冗余的。接下来我们就引入了 方法引用 概念
方法引用
继续修改代码后,方法引用(使用 :: 来表示)如下:
public class MethodRefDemo{
public static void main(String[] args){
int resSum = printSum(MethodRefDemo::getSum);
System.out.println("数组求和:"+resSum);
}
//部分重复代码省略(printSum()、getSum())
}
请注意其中的双冒号 :: 写法,这被称为"方法引用",是一种新的语法。
方法引用的格式
符号表示:双冒号(::)
符号说明:双冒号为方法引用运算符,而它所在的表达式被称为方法引用。
应用场景:如果 Lambda 所要实现的方案,已经在其他方法存在相同方案,那么则可以使用方法引用。
常见引用方式
方法引用在 JDK8 中使用方式相当灵活,有如下几种形式:
1.instanceName::methodName 对象名::方法名
2.ClassName::staticMethodName 类名::静态方法名
3.ClassName::methodName 类名::方法名
4.ClassName::new 类名::new
5.TypeName[ ]::new 数组类型::new (String[]::new)
1.对象名::方法名
这是最常见的一种用法,与上例相同。如果一个类中已经存在了一个成员方法,则可以通过对象名::方法名的方式调用
public class InstanceName_methodName_demo {
public static void main(String[] args) {
Date now = new Date();
//Lambda 普通写法(调用方法)
Supplier<Long> supplier1 = ()->{
return now.getTime();
};
Long aLong = supplier1.get();
System.out.println("普通写法:"+aLong);
//Lamdba 方法引用(对象名::成员方法名)
Supplier<Long> supplier2 = now::getTime;
Long bLong = supplier2.get();
System.out.println("方法引用写法:"+bLong);
//使用 方法引用 来修改Time
Consumer<Long> consumer = now::setTime;
consumer.accept(100000000000L);
Supplier<Long> supplier = now::getTime;
Long aLong1 = supplier.get();
System.out.println("修改后的Time:"+aLong1);
}
}
注意事项:
- 被引用的方法,参数要和接口中抽象方法的参数一样;(即:getTime() 方法无参,Supplier接口抽象方法get()方法也无参)
- 当接口抽象方法有返回值时,被引用的方法也必须有返回值。
2.类名::静态方法名
由于在 java.lang.System 类中已经存在了静态方法 currentTimeMillis(),所以当我们需要通过 Lambda 表达式来调用该方法时,可以使用方法引用。通过类名::静态方法名的方式来调用
public class className_abstractMethodName_demo {
public static void main(String[] args) {
//Lambda普通写法(类名.静态方法名)
Supplier<Long> supplier1 = ()->{
return System.currentTimeMillis();
};
Long aLong = supplier1.get();
System.out.println("普通调用:"+aLong);
//Lambda 方法引用
Supplier<Long> supplier2 = System::currentTimeMillis;
Long bLong = supplier2.get();
System.out.println("方法引用:"+bLong);
}
}
3.类名::方法名
在Java面向对象中,类名是只能调用静态方法的。此处使用类名::方法名是有前提的,实际上是拿第一个参数作为方法的调用者。看如下代码先了解一下你就明白了。
Demo:我们现在来计算字符串的长度。
public class className_methodName_demo {
public static void main(String[] args) {
//Lambda 表达式(使用 对象.方法名 形式调用)
Function<String,Integer> fn1 = (String str)->{
return str.length();
};
Integer aLength = fn1.apply("hello world");
System.out.println("字符长度为:"+aLength);
//Lambda 表达式(使用 方法引用【类名::方法名】方式调用)
Function<String,Integer> fn2 = String::length;
Integer bLength = fn2 .apply("hello world");
System.out.println("字符长度为:"+bLength);
}
}
分析一下:
如果还没有看懂,那就来看下面练习:自定义方法,如何来使用【类名::方法名】方法引用。看完下面练习,你就能够了解如何使用【类名::方法名】这种方式的方法引用了。
练习(步骤分析):
/**
* TODO 实例:自定义方法 (由繁到简)一步步分析
* 从 1.Lambda表达式 ---> 2.对象名::方法名 ---> 3.类名::方法名 形式 一步步分析
*
* @author liuzebiao
* @Date 2020-1-6 15:38
*/
public class Demo01 {
//数组求和方法
public int getSum(int[] arr){
int sum = 0;
for(int n : arr){
sum += n;
}
return sum;
}
public static void main(String[] args) {
//自定义一个数组求和方法 getSum(),本例使用【类名::方法名形式】方法引用方式
//分析:调用getSum()方法,传入一个 int[]类型参数,返回一个Integer值,此处用到 Function<int[],Integer> 函数式接口
//步骤:
//1.这种方式显然可以,但是Lambda表达式是冗余的
Demo01 demo = new Demo01();
Function<int[],Integer> f1 = (int[] arr)->{
return demo.getSum(arr);
};
//2.我们可以使用方法引用 demo::getSum;【对象名::方法名】
Function<int[],Integer> f2 = demo::getSum;
//3.上面2中使用的是 对象名::成员方法,自己写的方法怎么使用【类名::方法名】方式
// 分析:此时就得用到 BiFunction 函数式接口了。BiFunction 类似于 Function 函数式接口
// 差别在于参数个数不同:BiFunction 传递2个参数,Function传递1个参数
// 为什么需要传递两个参数呢?
// 通过刚才图片分析: 2=1.3方法。此处可以理解为【Integer =Demo01.getSum(int[])】这种格式
// 所以如果使用【类名::方法名】这种方式,第一个参数必须是当前类名,剩下才是传递的参数和返回参数类型
// 如果分析用2个参数,使用【类名::方法名】这种方式,则需要传3个参数,JDK8帮我们提供了BiFunction/BiConsumer/BiPredicate这3种
BiFunction<Demo01, int[], Integer> f3 = Demo01::getSum;
int[] arr = {11, 22, 33, 44, 55};
// 走到此处,调用apply()方法传参时,此处还得传递一个当前类的实例(new Demo01() new实例/Demo01.class.newInstance()反射等方式)
// 显然还不如直接使用 上面 2 中的【对象名::方法名】这种方式呢。所以【类名::方法名】这种方式很少会用到
// 所以 BiFunction/BiConsumer/BiPredicate这3种很少用到
Integer sum = f3.apply(new Demo01(), arr);
System.out.println(sum);
}
}
4.类名::new(构造器引用)
由于构造器的名称与类名完全一样,所以构造器引用使用 【类名称::new构造器】的格式表示。
Demo:根据构造器引入,创建 Person 对象
/**
* TODO person实体类
*
* @author liuzebiao
* @Date 2020-1-7 15:47
*/
public class Person {
private String name;
private int age;
public Person() {
System.out.println("执行无参构造方法");
}
public Person(String name, int age) {
System.out.println("执行有参构造方法,name:"+name+";age:"+age);
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
/**
* TODO 构造器引入Demo
*
* @author liuzebiao
* @Date 2020-1-7 15:49
*/
public class className_new_constructor {
public static void main(String[] args) {
//1.使用对象无参构造器,创建一个 Person 对象
//1.1 使用 Lambda 表达式
Supplier<Person> sup1 = ()->{
return new Person();
};
Person person1 = sup1.get();
System.out.println(person1);
//1.2 使用方法引用【类名::new(构造器)】方式
Supplier<Person> sup2 = Person::new;
Person person2 = sup2.get();
System.out.println(person2);
//2.使用对象有参构造器,创建一个 Person 对象
//此种情况会用到 BiFunction 函数式接口(前两个参数对应构造器参数类型,第三个参数为返回值类型)
//2.1 使用 Lambda 表达式
BiFunction<String,Integer,Person> bif1 = (String name,Integer age)->{
return new Person(name,age);
};
Person person3 = bif1.apply("Mary", 18);
System.out.println(person3);
//2.2 使用方法引用【类名::new(构造器)】方式
BiFunction<String,Integer,Person> bif2 = Person::new;
Person person4 = bif2.apply("Mary", 18); //它会根据 apply()方法传入的参数来判断调用哪个构造器
System.out.println(person4);
}
}
使用前提:
构造器参数列表要与接口中抽象方法的参数列表一致!
疑点:
针对大于2个参数的构造器(暂不清楚如何使用构造器引入,或许JDK就没提供吧,毕竟参数太多没意义,哈哈),暂时还是先用咱最传统的new Person(xxx,xxx,xxx)方式吧。
5.数组类型::new(数组引入)
你会纳闷:数组也有构造器?其实数组也是Object的子类,同样也是有构造器的,只是语法稍有不同。
Demo:根据数组引入,创建 一个数组
/**
* TODO 数组引用 创建数组
*
* @author liuzebiao
* @Date 2020-1-7 16:39
*/
public class array_new_demo {
public static void main(String[] args) {
//1.使用 Lambda 表达式,创建一个长度为10的int数组
Function<Integer,int[]> fn1 = (Integer num)->{
return new int[num];
};
int[] arr1 = fn1.apply(10);
System.out.println(Arrays.toString(arr1));
//2.使用 数组引用 方式,创建一个长度为10的int数组
Function<Integer,int[]> fn2 = int[]::new;
int[] arr2 = fn2.apply(10);
System.out.println(Arrays.toString(arr2));
}
}
总结
方法引用是对 Lambda表达式符合特定情况下的一种缩写,它使得我们的 Lambda表达式更加的精简,也可以理解为 Lambda 表达式的缩写形式,不过要注意的是方法引用只能"引用" 已经存在的方法。
附:JDK8新特性(目录)
本目录为 JDK8新特性 学习目录,包含JDK8 新增全部特性的介绍。
如需了解,请跳转链接查看:我是跳转链接
博主写作不易,来个关注呗
求关注、求点赞,加个关注不迷路 ヾ(◍°∇°◍)ノ゙
博主不能保证写的所有知识点都正确,但是能保证纯手敲,错误也请指出,望轻喷 Thanks♪(・ω・)ノ