一篇文章轻松理解::关键字

一、基本概念

::关键字是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.获得该实现类的类对象后,可供其它业务调用,调用时会将该类对象与具体的接口类型进行对应(强制类型转换为对应类型),从而执行具体的实现方法。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

追逐梦想永不停

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值