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 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
可以换成 String
,Q
可以换成 int
,R
可以替换成 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()
方法可以直接使用 ::
。