看过此篇文章后,你可以认为 类名::new 并没有创建对象,类名::new 只代表了一个lambda表达式,是一个构造方法的引用。
小编这两天在看算法相关的知识,结果遇到了java8的东西,于是开始研究java8(小编对于自己的性格也很无奈,不影响算法的情况下,是应该继续看算法的。但是里面涉及到的java8知识不懂,就感觉很别扭,于是就开始研究java8),在看到 构造方法引用 相关的知识时,看到了一个别人问的问题Java8中直接new对象和用 类名::new 创建对象这两种形式有什么区别吗?一直想不明白这个问题:
public static void main(String[] args) {
new Thread(new PrintThreadName(), "1").start();
new Thread(new PrintThreadName(), "2").start();
new Thread(PrintThreadName::new, "3").start();
new Thread(PrintThreadName::new, "4").start();
}
为什么以上代码:在线程中用 类名::new 创建Runable对象后,这个对象的run方法没有被执行(PrintThreadName就是输出当前线程名称的,但是 线程3和4没有输出内容)
别人的回答:
小编看了他的回答还是一脸懵,于是小编开始一直研究,一直找资料:最后小编还是不太明白,但是似乎又明白了一点。小编自己的理解:虽然能写成
Runnable runnable3 = PrintThreadName::new;
但这并不代表runnable3是一个对象(不确认这么说对不对,因为线程3 PrintThreadName的构造方法确实执行了),runnable3只是一个lambda表达式。
打印runnable3如下:
runnable3:com.maven.demo.PrintThreadName$$Lambda$14/0x000000080009a040@2cb4c3ab
而对于PrintThreadName runable1 = new PrintThreadName();
打印runable1如下:
runable1:Thread[Thread-0,5,main]你可以理解为PrintThreadName::new 只是代表一个构造方法的引用,只是引用而已,并没有真正的创建对象
(参见构造器引用和直接用new创建对象区别)
如果你像小编一样有点轴,非要用PrintThreadName::new的形式,还要让线程3,线程4的run方法执行,那么你可以按照如下方法:Suppliersupplier = PrintThreadName::new; Runnable runnable5 = supplier.get(); new Thread(runnable5, "5").start();
这时候打印supplier:
supplier:com.maven.demo.PrintThreadName$$Lambda$16/0x000000080009a840@2cb4c3ab
可以看到supplier也只是一个lambda表达式
再打印runnable5:
runnable5:Thread[Thread-2,5,main]
即supplier调用get方法后,才真正的创建了PrintThreadName对象,没调用方法之前supplier只是一个构造方法的引用,只是一个表达式。
小编就是尝试了这种写法,又打印了日志之后,才强迫认为自己有了一知半解的。至于既然PrintThreadName::new只是一个lambda表达式,只是一个引用,并不是对象,那为何还让这个表达式能作为需要runnable对象的 new Thread的参数呢,然后run方法又不执行,这不是坑人吗。
new Thread(PrintThreadName::new, “3”).start()。这种写法编译的时候就应该提示并报错,写成 new Thread(runnable5, “5”).start()才可以。
下面是小编研究的完整代码
package com.maven.demo;
import java.util.function.Supplier;
public class PrintThreadName extends Thread {
public PrintThreadName(){
System.out.println("构造:"+Thread.currentThread().getName());
}
public static void main(String[] args) {
PrintThreadName runable1 = new PrintThreadName();
System.out.println("runable1:"+runable1);
new Thread(runable1, "1").start();
Thread thread2 = new Thread(new PrintThreadName(), "2");
thread2.start();
Runnable runnable3 = PrintThreadName::new;
Runnable runnable4 = PrintThreadName::new;
Supplier supplier = PrintThreadName::new;
Runnable runnable5 = supplier.get();
System.out.println("runnable3:"+runnable3);
System.out.println("runnable4:"+runnable4);
System.out.println("runnable5:"+runnable5);
new Thread(runnable3, "3").start();
new Thread(runnable5, "5").start();
Thread thread4 = new Thread(runnable4, "4");
thread4.start();
System.out.println("2 isAlive:"+thread2.isAlive());
System.out.println("4 isAlive:"+thread4.isAlive());
for(int i = 0;i<10000;i++){
}
System.out.println("2 isAlive:"+thread2.isAlive());
System.out.println("4 isAlive:"+thread4.isAlive());
//new Thread(() -> {}, "4").start();
}
@Override
public void run() {
super.run();
System.out.println("run方法 Thread name:"+Thread.currentThread().getName());
}
}
打印的日志
再说下什么时候可以写 Xxxx::new:
java8 lambda 内部接口需要@FunctionalInterface这个注解,这个注解是一个说明性质的注解,被@FunctionalInterface注解的接口只能由一个抽象方法,@FunctionalInterface只能用于注解接口而不能用在class以及枚举上.
被@FunctionalInterface注解的符合规则的接口,可以用lambda表达式。
即Xxxx::new所代表的引用必须是一个接口,并且有且只有一个抽象方法(可以有其他的非抽象方法)
如上面代码中所写的 Runnable runnable3 = PrintThreadName::new;
Runnable即是有且只有一个抽象方法的接口。(如果看不明白,可以看原文Java双冒号(::)运算符详解)
另外:其他接口如下图
详情见JDK8新特性 - Lambda表达式、内置函数式接口、方法引用及构造器引用
即:
如果构造方法没有参数,可以用Supplier
如果构造方法有一个参数,可以用Function
如果构造方法有两个参数,可以用BiFunction
如果构造方法有三个或三个以上参数,需要自己定义接口方法(java8之方法引用),牛逼的例子,如
interface TriFunction{ R apply(T t, U u, V v); }
容易理解的例子,如
interface InterfaceExample{
Example create(String str,String st1,String st2);
//可以理解为Example(需要创建的对象)就是上一个写法中的R。t,u,v分别是参数str,str1,str2
}
对于这个问题,如果你有更好的理解,可以写到评论区,让更多的人知道
参考:
Java8中直接new对象和用 类名::new 创建对象这两种形式有什么区别吗?
构造器引用和直接用new创建对象区别
https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html
Java双冒号(::)运算符详解
java8新特性之Stream
Java8 新特性
JDK8新特性 - Lambda表达式、内置函数式接口、方法引用及构造器引用
java8之方法引用
2022.5.5 22:39 shylxy 50211986



