在使用`Lambda`表达式的时候,我们实际上传递进去的代码就是一种解决方案:拿什么参数做什么操作。那么考虑一种情况:如果我们在`Lambda`中所指定的操作方案,已经有地方存在相同方案,那是否还有必要再写重复逻辑?
## 冗余的Lambda场景
来看一个简单的函数式接口以应用Lambda表达式:
```java
public interface Printable {
void print(String s);
}
```
```java
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`方法
```java
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`的李生兄弟。
## 通过对象名引用成员方法
这是最常见的一种用法,与上例相同。如果一个类中已经存在了一个成员方法:
```java
public class MethodRefObject{
public void printUpperCase(String str){
System.out.printin(str.toUppercase());
}
}
```
函数式接口仍然定义为:
```java
@FunctionalInterface
public interface Printable{
void print(String str);
}
```
那么当需要使用这个`printUpperCase `成员方法来替代`printable`接口的`Lambda`的时候,已经具有了`MethodRefobject`类的对象实例,则可以通过对象名引用成员方法,代码为:
```java
/**
* 通过对象名引用成员方法
* 使用前提是:
* 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`来调用该方法时,有两种写法。首先是函数式接口:
```java
@FunctionalInterface
public interface Calcable{
int calc(int num);
}
```
使用`Lambda`表达式和静态方法引用:
```java
/**
* 通过类名引用静态成员方法
* 类已经存在,静态成员方法也已经粗壮乃
* 就可以通过类名直接引用静态成员方法
*
* @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`调用时,也可以使用方法引用进行替代。首先是函数式接口:
```java
@FunctionalInterface
public interface Greetable{
void greet();
}
```
然后是父类`Human`的内容:
```java
public class Human {
public void sayHi() {
System.out.println("Hello 大家好我是周杰伦~");
}
}
```
最后是子类`Man`的内容,其中使用了`Lambda`的写法和`supper`方法引用的方法:
```java
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::成员方法`的格式来使用方法引用。首先患简单的函数式接口:
```java
@FunctionalInterface
public interface Richable{
void buy();
}
```
下面是一个丈夫`Husband`类:
```java
public class Husband{
private void marry(Richable 1ambda){
1ambda.buy();
}
public void beHappy() {
marry(()->System.out.println("买套房子”));
}
}
```
开心方法`beHappy `调用了结婚方法`marry`,后者的参数为函数式接口`Richable`,所以需要一个`Lambda`表达式。
但是如果这个`Lambda`表达式的内容已经在本类当中存在了,则可以对`Husband `丈夫类进行修改:
```java
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`类:
```java
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`对象的函数式接口:
```java
/**
* 定义一个创建Person对象的函数式接口
* @author guqing
*/
@FunctionalInterface
public interface PersonBuilder {
/**
* 根据名字创建Person对象的方法
*
* @param name Person中的名称
* @return Person
*/
Person builderPerson(String name);
}
```
使用Lambda表达式和构造方法引用创建对象
```java
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`的使用场景中时,需要一个函数式接口:
```java
/**
* 定义一个创建数组的函数式接口
* @author guqing
*/
public interface ArrayBuilder {
/**
* 通过长度构建一个数组
* @param length 数组长度
* @return 返回构建好的数组
*/
public int[] builderArray(int length);
}
```
在应用该接口的时候,可以通过`Lambda`表达式:
```java
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);
}
}
```
但是更好的写法是使用数组的构造器引用:
```java
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);
}
}
```

Java 8新特性之方法引用