一、基本概念
::关键字是Java 8中的一个新特性,可以用来访问类的构造方法、类的静态方法,以及借助对象访问类中的普通方法。
二、实例
网上有一个常用的使用::关键字的例子,如下:
List<String> list = Arrays.asList("1","2","3");
list.forEach(System.out::println);
执行后输出结果为:
1
2
3
说明:
1.这段代码的功能是循环list,使用System.out.println()输出每一个元素。
2.实际上,循环list的操作是forEach实现的,下方是forEach的具体代码:
//源码,需要传入一个Consumer接口类型的参数
default void forEach(Consumer<? super T> action) {
//判空方法,如果为空会抛出异常,不为空则继续执行
Objects.requireNonNull(action);
//循环,this相当于list,t相当于每一个元素
for(T t : this) {
//执行action中的accept方法,传入参数t
action.accept(t);
}
}
其中的action.accept(t),可以理解为System.out.println(t),所以最终结果会打印每个元素。
如果还是不太明白,可以继续往下看。
3.这里需要看一下Consumer的源码,有助于理解,源码如下:
//加了这个注解,该接口类中就只能有一个抽象方法,否则会编译不通过
@FunctionalInterface
public interface Consumer<T> {
//只有这一个抽象方法(省略了abstract也是抽象方法,默认会加上)
void accept(T t);
}
4.因此,这个例子还可以写成下方这种形式,便于理解:
List<String> list = Arrays.asList("1","2","3");
Consumer action = System.out::println;
list.forEach(action);
(1)这句【Consumer action = System.out::println;】中,使用了::表达式,相当于实现了Consumer接口类,实现了抽象方法accept()为println(),实现类的类对象名称为action。
(2)因为Consumer本身是一个接口,其中只有一个抽象方法void accept(T t),与void println(String s)方法格式相同,因此可以被实现。
println()方法的源码如下(这段源码是说明println()与accept()格式相同):
//PrintStream ps = System.out;也就是说System.out是PrintStream类型的
//因此这是PrintStream中的源码
public void println(String s) {
synchronized (this) {
print(x);
newLine();
}
}
(3)之后,将action对象传入forEach()方法中,执行action.accept(t)方法,就相当于执行System.out.println(t)了。
5.这个例子还可以写成这种形式,便于理解:
List<String> list = Arrays.asList("1","2","3");
//可以比较一下::表达式与匿名内部类的区别
//Consumer action = System.out::println;
Consumer action = new Consumer<String>(){
@Override
public void accept(String s) {
System.out.println(s);
}
};
List.forEach(action);
这个匿名内部类代码与::关键字实现的功能是一样的。
6.再回头看一下这个例子,应该就能理解了:
list.forEach(System.out::println);
(1)System.out::println相当于一个接口的实现类的类对象,实现接口的抽象方法为println()方法。
(2)实现的具体接口类型不确定,可以理解为是任意某个接口,只要是抽象方法与实现方法的格式相同的,都可以。
(3)因为forEach()中需要传入的是Consumer类型的接口的实现类参数,因此这里可以理解为,System.out::println实现了Consumer接口类,实现了抽象方法accept()为println(),然后把实现类的类对象传入forEach()中。
三、具体用法
1.假设现在有一个目标类,要使用::表达式获取类中的构造方法,静态方法,通常方法;然后供其它业务逻辑使用。目标类代码如下:
public class MyClass {
private String id;
public MyClass(String id){
this.id=id;
System.out.println("构造方法被调用,id为"+id);
}
public String normalOut(){
System.out.println("通常方法被调用,id为"+id);
return id;
}
public static String staticOut(){
System.out.println("静态方法被调用");
}
}
2.准备一些接口类,用于接收方法,方法格式与目标类方法要相同
public interface NewInterface<F,T> {
//准备接收对应类的构造方法,格式与目标类的构造方法相同
//传入F,返回T
public abstract T func(F t);
}
public interface StaticInterface {
//准备接收对应类的静态方法,格式与目标类的静态方法相同
public abstract void func();
}
public interface NewInterface<F> {
//准备接收对应类的通常方法,格式与目标类的通常方法相同
//返回F
public abstract F func();
}
3.写一个main方法,用来测试
public static void main(String[] args) {
//获得构造方法
NewInterface<String,MyClass> i1 = MyClass::new;
//使用构造方法初始化类对象
MyClass mc1 = i1.func("初始化mc1");
//获得静态方法
StaticInterface i2 = MyClass::staticOut;
//调用静态方法
i2.func();
//通过类对象,获得类的通常方法
MyClass mc = new MyClass("初始化mc");
NormalInterface i3 = mc::normalOut;
//调用通常方法
i3.func();
//或者通过强制类型转换,调用方法
((StaticInterface)MyClass::staticOut).func();
}
控制台打印如下:
构造方法被调用,id为初始化mc1
静态方法被调用
构造方法被调用,id为初始化mc
通常方法被调用,id为初始化mc
静态方法被调用
四、总结
1.【::】关键字可以用来获取某个类的构造方法、静态方法、通常方法,将目标方法转换为某个接口的实现类的类对象。
2.要求某个接口中只有一个抽象方法,并且抽象方法的格式与目标方法相同;调用抽象方法,即是调用获取到的具体方法。
3.获得该实现类的类对象后,可供其它业务调用,调用时会将该类对象与具体的接口类型进行对应(强制类型转换为对应类型),从而执行具体的实现方法。