在使用Lambda
表达式的时候,我们实际上传递进去的代码就是一种解决方案:拿什么参数做什么操作。那么考虑一种情况:如果我们在Lambda
中所指定的操作方案,已经有地方存在相同方案,那是否还有必要再写重复逻辑?
冗余的Lambda场景
来看一个简单的函数式接口以应用Lambda表达式:
public interface Printable {
void print(String s);
}
public class PrintableDemo {
public static void printString(Printable printable) {
printable.print("Hello World");
}
public static void main(String[] args) {
printString(str -> System.out.println(str));
}
}
Lambda
表达式的目的,打印参数传递的字符串把参数str
,传递给了System.out
对象,调用out
对象中的方法println
对字符串进行了输出
注意:
System.out
对象是已经存在的println
方法也是已经存在的
所以我们可以使用方法引用来优化Lambda
表达式可以使用System.out
方法直接引用(调用)printin
方法
public class PrintableDemo {
public static void printString(Printable printable) {
printable.print("Hello World");
}
public static void main(String[] args) {
//printString(str -> System.out.println(str));
printString(System.out::println);
}
}
请注意其中的双冒号::
写法,这被称为“方法引用”,而双冒号是一种新的语法。
方法引用符
双冒号::
为引用运算符,而它所在的表达式被称为方法引用。如果Lambda
要表达的函数方案已经存在于某个方法的实现中,那么则可以通过双冒号来引用该方法作为Lambda
的替代者。
语义分析
例如上例中,System.out
对象中有一个重载的println(String)
方法恰好就是我们所需要的。那么对于printString
方法的函数式接口参数,对比下面两种写法,完全等效:
- Lambda表达式写法:
str->System.out.println(str);
- 方法引用写法:
System.out::println
第一种语义是指:拿到参数之后经Lambda
之手,继而传递给
System.out.println
方法去处理。
第二种等效写法的语义是指:直接让System.out
中的println
方法来取代Lambda
。两种写法的执行效果完全一样,而第二种方法引用的写法复用了已有方案,更加简洁。
注:
Lambda
中传递的参数一定是方法引用中的那个方法可以接收的类型,否则会抛出异常
推导与省略
如果使用Lambda
,那么根据”可推导就是可省略”的原则,无需指定参数类型,也无需指定的重载形式——它们都
将被自动推导。而如果使用方法引用,也是同样可以根据上下文进行推导。函数式接口是Lambda
的基础,而方法
引用是Lambda
的李生兄弟。
通过对象名引用成员方法
这是最常见的一种用法,与上例相同。如果一个类中已经存在了一个成员方法:
public class MethodRefObject{
public void printUpperCase(String str){
System.out.printin(str.toUppercase());
}
}
函数式接口仍然定义为:
@FunctionalInterface
public interface Printable{
void print(String str);
}
那么当需要使用这个printUpperCase
成员方法来替代printable
接口的Lambda
的时候,已经具有了MethodRefobject
类的对象实例,则可以通过对象名引用成员方法,代码为:
/**
* 通过对象名引用成员方法
* 使用前提是:
* 1.对象名是已经存在的
* 2.成员方法也是已经存在的
* 就可以使用对象名来引用成员方法
*
* @author guqin
* @date 2019-07-23 21:21
*/
public class MethodRefObject {
public void printUpperCaseString(String str) {
System.out.println(str.toUpperCase());
}
public static void printString(Printable printable) {
printable.print("Hello World...");
}
public static void main(String[] args) {
// 对已经存在的对象使用对象引用调用成员方法
MethodRefObject methodRefObject = new MethodRefObject();
// 使用methodRefObject的方法引用完成输出
printString(methodRefObject::printUpperCaseString);
}
}
通过类名引用静态方法
由于在java.lang.Math
类中已经存在了静态方法abs
,所以当我们需要通过Lambda
来调用该方法时,有两种写法。首先是函数式接口:
@FunctionalInterface
public interface Calcable{
int calc(int num);
}
使用Lambda
表达式和静态方法引用:
/**
* 通过类名引用静态成员方法
* 类已经存在,静态成员方法也已经粗壮乃
* 就可以通过类名直接引用静态成员方法
*
* @author guqin
* @date 2019-07-23 21:32
*/
public class StaticMethodRefDemo {
/**
* 定义一个方法,参数传递要计算绝对值的整数和函数式接口
* @param num
* @param calcable
*/
public static int absMethod(int num, Calcable calcable) {
return calcable.calc(num);
}
public static void main(String[] args) {
// Lambda表达式写法
// int number = absMethod(-10, num->Math.abs(num));
// System.out.println(number);
// Math.abs是静态方法,使用静态方法引用
int absNumber = absMethod(-10, Math::abs);
System.out.println(absNumber);
}
}
通过super引用成员方法
如果存在继承关系,当Lambda
中需要出现super
调用时,也可以使用方法引用进行替代。首先是函数式接口:
@FunctionalInterface
public interface Greetable{
void greet();
}
然后是父类Human
的内容:
public class Human {
public void sayHi() {
System.out.println("Hello 大家好我是周杰伦~");
}
}
最后是子类Man
的内容,其中使用了Lambda
的写法和supper
方法引用的方法:
public class Man extends Human {
public void greet(Greetable greetable) {
greetable.greet();
}
public void showGreet() {
// Lambda写法
greet(()->{
// 创建父类Human对象
Human human = new Human();
human.sayHi();
});
/**
* 因为有字符类关系,所有有supper关键字
* 所以可以直接使用supper调用父类的成员变量方法
*/
greet(super::sayHi);
}
public static void main(String[] args) {
new Man().showGreet();
}
}
通过this引用成员方法
this
代表当前对象,如果需要引用的方法就是当前类中的成员方法,那么可以使用this::成员方法
的格式来使用方法引用。首先患简单的函数式接口:
@FunctionalInterface
public interface Richable{
void buy();
}
下面是一个丈夫Husband
类:
public class Husband{
private void marry(Richable 1ambda){
1ambda.buy();
}
public void beHappy() {
marry(()->System.out.println("买套房子”));
}
}
开心方法beHappy
调用了结婚方法marry
,后者的参数为函数式接口Richable
,所以需要一个Lambda
表达式。
但是如果这个Lambda
表达式的内容已经在本类当中存在了,则可以对Husband
丈夫类进行修改:
public class Husband {
public void buyHouse() {
System.out.println("北京二环买一套四合院");
}
public void marry(Richable richable) {
richable.buy();
}
public void veryHappy() {
// 调用marry方法,使用this调用本来方法buyHouse
//marry(()-> this.buyHouse());
// 使用this引用成员方法
marry(this::buyHouse);
}
public static void main(String[] args) {
new Husband().veryHappy();
}
}
类的构造器引用
由于构造器的名称与类名完全一样,并不固定。所以构造器引用使用类名称::new
的格式表示。首先是一个简单的Person
类:
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
然后是用来创建person
对象的函数式接口:
/**
* 定义一个创建Person对象的函数式接口
* @author guqing
*/
@FunctionalInterface
public interface PersonBuilder {
/**
* 根据名字创建Person对象的方法
*
* @param name Person中的名称
* @return Person
*/
Person builderPerson(String name);
}
使用Lambda表达式和构造方法引用创建对象
public class PersonDemo {
public static void createPerson(String name, PersonBuilder personBuilder) {
System.out.println(personBuilder.builderPerson(name));
}
public static void main(String[] args) {
// 根据name创建一个Person对象
//createPerson("张三", (name)->new Person(name));
// 构造方法引用,使用Person引用new创建对象
createPerson("项羽",Person::new);
}
}
数组的构造器引用
数组也是object
的子类对象,所以同样具有构造器,只是语法稍有不同。如果对应到Lambda
的使用场景中时,需要一个函数式接口:
/**
* 定义一个创建数组的函数式接口
* @author guqing
*/
public interface ArrayBuilder {
/**
* 通过长度构建一个数组
* @param length 数组长度
* @return 返回构建好的数组
*/
public int[] builderArray(int length);
}
在应用该接口的时候,可以通过Lambda
表达式:
public class ArrayBuilderDemo {
public static int[] createArray(int length, ArrayBuilder arrayBuilder) {
return arrayBuilder.builderArray(length);
}
public static void main(String[] args) {
int[] arr = createArray(3, length->new int[length]);
System.out.println(arr.length);
}
}
但是更好的写法是使用数组的构造器引用:
public class ArrayBuilderDemo {
public static int[] createArray(int length, ArrayBuilder arrayBuilder) {
return arrayBuilder.builderArray(length);
}
public static void main(String[] args) {
// 使用方法引用
int[] arr = createArray(4, int[]::new);
System.out.println(arr.length);
}
}