在使用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对字符串进行了输出

注意:

  1. System.out对象是已经存在的
  2. 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);
	}
}

毕生所求无它 爱与自由而已