通过 lambda 表达式,我们可以更加优雅的替代匿名内部类,生成一个函数式接口的实例,使我们的编码更加简洁。
这篇文章进一步介绍Java8的新特性,可以让我们的编码相对于 lambda 表达式更为简洁。
分别是:方法引用、构造器引用还有数组引用。
说白了就是借用别人的轮子
。
提示:
这篇文章结合函数式接口和lambda表达式进行讲解,Demo中应用了一些Java内置的函数式接口,比如 BiConsumer<T, U>、BinaryOperator<T>、BiFunction<T, U, R>等等。
还不熟悉这个这两个特性,或者对一些Java内置的函数式接口不熟悉的,请看先阅读 01-函数式接口和 lambda 表达式。
若想了解java8其它新特性,请到 00-java8常用新特性文章索引 阅读。
1 方法引用
1.1 方法引用是什么
如果已经有其他类的某一个方法实现了 函数式接口抽象方法需要实现的代码逻辑(lambda 体),可以通过方法引用,直接引用该类已经实现的方法。
简单地讲,只要满足以下条件,即可使用方法引用:
-
- 参数列表一样
-
- 返回值一样
-
- lambda体的代码逻辑和目标类成员方法的代码逻辑一样
1.2 方法引用的格式
使用**::
**(双冒号)将类名或者对象名和方法名分隔开。
对象::实例方法名
类名::实例方法名
类名::静态方法名
1.3 方法引用怎么用
1.3.1 对象::实例方法名
对象::实例方法名 Demo
/**
* Description: 运算类
*
* @author Xander
* datetime: 2020/9/1 0:14
*/
public class Calc {
/**
* 加法运算,对象::实例方法名 时可以使用
*
* @param x
* @param y
* @return
*/
public int add(int x, int y) {
return x + y;
}
/**
* 乘法运算,类名::静态方法名 时可以使用
*
* @param x
* @param y
* @return
*/
public static long multi(long x, long y) {
return x * y;
}
}
/**
* Description: 方法引用简单应用
*
* @author Xander
* datetime: 2020/8/31 23:57
*/
public class MethodRefTest {
@Test
public void test00() {
//lambda表达式
Consumer<String> cLambda = str -> System.out.println(str);
// 对象::实例方法名
Consumer<String> cMethRef = System.out::println;
cLambda.accept("hello Lambda");
cMethRef.accept("hello Method Reference");
}
@Test
public void test01() {
Calc calc = new Calc();
//lambda表达式
BinaryOperator<Integer> oprLambda = (x, y) -> x + y;
// 对象::实例方法名
BinaryOperator<Integer> oprMethRef = calc::add;
System.out.println(oprLambda.apply(2, 3));
System.out.println(oprMethRef.apply(2, 3));
}
}
test00、test01两个测试用例运行结果:
hello Method Reference
hello Method Reference
5
5
1.3.2 类名::实例方法名
类名::实例方法名,这个使用的场景比较特殊,要求第一个参数是实例方法的调用者,剩余的参数都是实例方法的入参,剩余的参数个数可以是0个或者多个。
提示:目标类的实例方法的参数列表会比函数式接口的抽象方法入参个数少一个,因为第一个参数是实例方法的调用者。
public class Person {
//姓名
private String name;
//年龄
private long age;
public Person(String name, long age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public long getAge() {
return age;
}
public void setAge(long age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
/**
* 打招呼
*
* @param p
*/
public void sayHello(Person p) {
System.out.println(this.getName() + " say hello to " + p.getName());
}
}
public class MethodRefTest {
...
@Test
public void test02() {
//lambda表达式
BiConsumer<Person,Person> bcLambda = (x, y) -> System.out.println(x.getName()+" say hello to "+y.getName());
// 类名::实例方法名
BiConsumer<Person,Person> bcMethRef = Person::sayHello;
bcLambda.accept(new Person("P1",18),new Person("P2",18));
bcMethRef.accept(new Person("P3",18),new Person("P4",18));
}
@Test
public void test03() {
// 判断两个String是否equals
//lambda表达式
BiFunction<String, String, Boolean> bfLambda = (x, y) -> x.equals(y);
// 类名::实例方法名
BiFunction<String, String, Boolean> bfMethRef = String::equals;
System.out.println(bfLambda.apply("a", "b"));
System.out.println(bfMethRef.apply("a", "a"));
//求 String 的 hashCode
//lambda表达式
Function<String, Integer> funcLambda = str -> str.hashCode();
// 类名::实例方法名
Function<String, Integer> funcMethRef = String::hashCode;
System.out.println(funcLambda.apply("a"));
System.out.println(funcMethRef.apply("a"));
}
}
test02、test03 运行结果:
P1 say hello to P2
P3 say hello to P4
false
true
97
97
1.3.3 类名::实例方法名
@Test
public void test04() {
//lambda表达式
BinaryOperator<Long> bfLambda = (x, y) -> x * y;
// 类名::静态方法名
BinaryOperator<Long> bfMethRef = Calc::multi;
System.out.println(bfLambda.apply(2L, 3L));
System.out.println(bfMethRef.apply(2L, 3L));
}
@Test
public void test05() {
//lambda表达式
BinaryOperator<Double> bfLambda = (x, y) -> Math.pow(x,y);
// 类名::静态方法名
BinaryOperator<Double> bfMethRef = Math::pow;
System.out.println(bfLambda.apply(2D, 3D));
System.out.println(bfMethRef.apply(2D, 3D));
}
test04、test05 运行结果:
6
6
8.0
8.0
2 构造器引用
2.1 构造器引用是什么
构造器引用依然是要结合函数式接口进行使用。
为了简化函数式接口的抽象方法需要实现的代码逻辑(lambda 体),如果lambda体中有返回值,而且返回值类型的构造器参数列表和函数式接口抽象方法的参数列表一致。
这时,就可以使用构造器引用。
看定义,应该看得很懵逼,直接看下面的demo。
2.2 语法格式
类名::new
2.3 构造器引用简单使用
@Test
public void test06() {
//通过函数式接口IntFunction 创建 Integer 实例
//lambda表达式
IntFunction<Integer> intFuncLambda = i -> new Integer(i);
// 构造器引用:类名::new
IntFunction<Integer> intFuncConstRef = Integer::new;
//lambda表达式生成 函数式接口IntFunction 的实例,执行apply方法后,生成的 Integer 实例
Integer integerLambda = intFuncLambda.apply(5);
//构造器引用生成 函数式接口IntFunction 的实例,执行apply方法后,生成的 Integer 实例
Integer integerConstRef = intFuncConstRef.apply(15);
System.out.println(integerLambda.intValue());
System.out.println(integerConstRef.intValue());
}
@Test
public void test07() {
//通过函数式接口BiFunction 创建 Person 实例
//lambda表达式
BiFunction<String, Long, Person> bfLambda = (name, age) -> new Person(name, age);
// 构造器引用:类名::new
BiFunction<String, Long, Person> bfConstRef = Person::new;
// lambda表达式生成的Person
Person personLambda = bfLambda.apply("张三", 20L);
// 构造器引用生成的Person
Person personConstRef = bfConstRef.apply("李四", 25L);
System.out.println(personLambda);
System.out.println(personConstRef);
}
运行结果:
5
15
Person{name='张三', age=20}
Person{name='李四', age=25}
3 数组引用
3.1 是什么
如果你理解了上面的构造器引用,那理解数组引用的使用就很简单了。
构造器引用通过函数式接口的抽象方法调用,使用返回值类型中与函数式接口抽象方法的参数列表一致的构造器,创建一个对象。
数组引用:通过函数式接口的抽象方法调用,将参数作为返回值数组的构造器入参,创建一个数组。
单看这句话应该还是懵逼,下面看一下使用demo。
3.2 语法格式
类名[]:new
3.3 数组引用简单应用
@Test
public void test08() {
// 通过数组引用,生成 String[] 实例
//lambda表达式
Function<Integer, String[]> funcLambda = length -> new String[length];
// 数组引用:类名[]::new
Function<Integer, String[]> funcArrayRef = String[]::new;
// lambda表达式生成的String[]
String[] arrayLambda = funcLambda.apply(2);
arrayLambda[0] = "lambda";
// 数组引用生成的String[]
String[] arrayArrayRef = funcArrayRef.apply(2);
arrayArrayRef[0] = "Array Reference";
System.out.println("lambda生成数组:" + Arrays.toString(arrayLambda));
System.out.println("数组引用生成数组:" + Arrays.toString(arrayArrayRef));
}
运行结果:
lambda生成数组:[lambda, null]
数组引用生成数组:[Array Reference, null]
方法引用,构造器引用,数组引用都需要结合函数式接口进行使用,当执行函数式接口的抽象方法时,其实调用的是引用类的方法逻辑或者构造器的逻辑,这就是典型的借轮子。
使用简单,设计灵巧,但要达到熟练程度,还需要多加理解,学以致用。