为什么会出现 :: 方法引用呢?

Java8 引入了lamba表达式,虽然这种写法已经很简单了,但是有时候你会发现,每次使用这种表达式的时候,你需要传入参数说明(例如: list.forEach(item->System.out.println(item))),那么有没有连参数说明都可以不用传入的写法呢?这里就出现了方法引用,就是 :: 用法,你可以不用指定任何参数说明(例如:list.forEach(System.out::println))。

如何使用 :: 方法引用?

先来看看下面的代码:

class User {
    String name;
    Integer age;
    public User(String name, Integer age){
        this.name = name;
        this.age = age;
    }
    public String getName(String name) {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

正常我们访问 User 类里面的方法是通过 new 一个 User 对象,但是现在我们不想通过这种方法取访问这些方法,我们想通过方法引用 :: 取访问这里面的方法,那么就必须定义一个函数式接口去访问,定义好的函数式接口,先来个访问构造方法的函数式接口,代码如下:

@FunctionalInterface
interface MyFunctionInterface<T,R,Q> {
    R accessUserConstructor(T name, Q q);
}

然后去访问构造方法,代码如下:

public class QutoeDemo {
    public static void main(String[] args) {
        MyFunctionInterface<String,User,Integer> functionInterface = User::new;
        User user = functionInterface.accessUserConstructor("a", 1);
    }
}

然后在定义一个访问 getName() 方法的函数式接口,为什么要分开,以为函数式接口只能有一个抽象方法,所以你要访问其他的方法你就需要重新创建一个函数式接口,代码如下:

@FunctionalInterface
interface MyFunctionInterface2<T,R> {
    R accessUserGetName(T name);
}

然后去用 :: 方法去访问,代码如下:

public class QutoeDemo {
    public static void main(String[] args) {
        //MyFunctionInterface<String,User,Integer> functionInterface = User::new;
        MyFunctionInterface2<String,String>  a = User::getName;
        String userGetName = a.accessUserGetName("ag");
    }
}

总结: 通过上述 :: 引用的方法去调用某个类的方法,可以发现有个规律,如果你要访问某个类的方法,你首先得定义一个函数式接口,这个没的说的,然后注意,里面的方法定义有3点规范,如下:

  • 函数式接口里的方法入参参数类型必须和你想要访问的某个类的方法的入参保持一致
  • 方法返回值要一致
  • 入参个数要一致

其实就是你想要通过 :: 方式去访问某个类的方法,首先得要先创建一个接口模版,这个接口模版就是函数式接口,然后通过用这个模版去访问某个类的方法,就可以简写成 :: 形式。

在回过头来看为什么 User 类的构造方法 User(String name,int age),入参有两个参数,参数类型一个是 String,一个是 int ,返回值是 User 类型,所以根据上面3点规范创建一个函数式接口,如下:

@FunctionalInterface
interface MyFunctionInterface<T,R,Q> {
    R accessUserConstructor(T name, Q q);
}

我这里是使用的泛型,这里的 T 可以换成 StringQ 可以换成 intR 可以替换成 User,这样替换的话再访问的地方就不用使用 <> 指定类型了。 我这里使用的泛型方式创建函数式接口,是为了能够访问更多符合这种模版的方法的类,不止于 User 一个类。所以访问 User 类的构造方法,可以直接 User::new 就可以了,代码如下:

public class QutoeDemo {
    public static void main(String[] args) {
        MyFunctionInterface<String,User,Integer> functionInterface = User::new;
    }
}

类似的,想通过 :: 访问 User 类的 getName() 方法,只需要定义一个满足上面提出的3点规范的函数式接口就可以了。

常见的 :: 案例

public static void main(String[] args) {
      Arrays.asList("a","c","d").forEach(System.out::println);
  }

分析 forEach() 中的 println 方法,可以使用 :: 引用,肯定是定义了一个 函数式接口,并且这个函数式接口里面定义的方法和 println 方法的入参类型,入参个数,返回值类型都是一一致的,因为满足这3个条件才可以使用::方式访问。
进入 println() 方法,代码如下:

public void println(String x) {
        synchronized (this) {
            print(x);
            newLine();
        }
    }

在进入 forEach() 方法,代码如下:

default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }

会发现这里有个 Consumer,这个其实也是属于函数式接口的一种形式,属于消费性的,也就是只需要传入入参,但是不会有返回值的函数式接口,这其实也就是定义的一种模板而已。进入 Consumer 代码如下:

@FunctionalInterface
public interface Consumer<T> {

    void accept(T t);
}

可以清晰的看到注解 @FunctionalInterface,有这个注解说明它是一个函数式接口,然后再看里面定义的模版方法,入参参数只有一个,没有返回值,T 泛型可以表示所有的类型,所以可以 println() 方法里面的入参 String 类型,同时也都是一个入参,符合参数个数规范,参数类型符合规范,都是没有返回值的,所以返回值也是符合规范,所以就满足 :: 访问方式,所以访问 println() 方法可以直接使用 ::