????看过此篇文章后,你可以认为 类名::new 并没有创建对象,类名::new 只代表了一个lambda表达式,是一个构造方法的引用。(感觉这个理解不太正确,再次验证更新于2022.5.6 20:34,见博客新增内容) ????小编这两天在看算法相关的知识,结果遇到了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方法执行,那么你可以按照如下方法:
Supplier<PrintThreadName> supplier = 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<PrintThreadName> 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<T, U, V, R> {
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
更新理解
????对于昨天的理解,始终感觉有点不正确。于是继续上网查资料,在JDK8新特性 - Lambda表达式、内置函数式接口、方法引用及构造器引用里面有一段代码
public void test1() {
// 演示匿名内部类的方式
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("匿名内部类:r1");
}
};
// 使用Lambda表达式简化
Runnable r2 = () -> System.out.println("Lambda表达式 匿名内部类:r2");
// 调用
r1.run();
r2.run();
}
????打印r2:
runnable2:com.maven.demo.PrintThreadDemo$$Lambda$15/0x0000000800081c40@7a0ac6e3
????调用r2.run();后也打印了方法体中的 Lambda表达式 匿名内部类:r2。 ????说明r2确实是一个对象。打印r2的时候也是$$Lambda$15/0x0000000800081c40@7a0ac6e3,所以对于昨天分析的Runnable runnable3 = PrintThreadName::new;打印runnable3是一个lambda表达式,说它不是对象是错误的。 ????于是想到是不是可以从另外一个角度分析问题:对于昨天说的那个线程3,线程3到底创建了没,运行没,如果运行了,那说明PrintThreadName::new创建了对象,至于PrintThreadName的run方法没有执行,那只能说明创建的只是Runnable对象。 ????通过打印线程状态,发现是运行的。说明线程3确实是创建了,也运行了,只是没有执行PrintThreadName里面的run方法,这样的话是不是可以理解为:
Runnable runnable3 = PrintThreadName::new;创建的只是runnable对象,如果执行也是执行runnable对象里面的run方法,跟父类PrintThreadName(PrintThreadName实现了Runnable接口,理解为PrintThreadName是Runnable的父类)没有关系,所以并不会执行PrintThreadName里面的run方法。
????通过上面的分析,可以理解为
PrintThreadName::new相当于创建了一个空的Runnable方法体(这个方法体并没有任何打印。因为无法打印Runnable接口里面的run方法看其是否执行,所以这一点无法验证得知,只能靠猜测),这个方法体可以理解为是:() ->{};
完整调试代码
package com.maven.demo;
import java.util.function.Supplier;
public class PrintThreadDemo extends Thread {
public PrintThreadDemo(){
System.out.println("构造:"+Thread.currentThread().getName());
}
public PrintThreadDemo(String name){
super(name);
System.out.println("构造:"+Thread.currentThread().getName());
}
public static void main(String[] args) {
//test1();
Runnable runnable1 = PrintThreadDemo::new;
System.out.println("runnable1:"+runnable1);
Runnable runnable2 = () -> System.out.println("匿名内部类:runnable2");
System.out.println("runnable2:"+runnable2);
Thread thread1 = new Thread(runnable1, "1");
thread1.start();
try{
sleep(1000);
System.out.println(thread1.getState());
}catch (Exception e){}
Thread thread2 = new Thread(runnable2, "2");
thread2.start();
try{
sleep(1000);
System.out.println(thread2.getState());
}catch (Exception e){}
Runnable runnable3 = () -> System.out.println("匿名内部类:runnable3");
System.out.println("runnable3:"+runnable3);
Thread thread3 = new Thread(runnable3, "3");
try{
sleep(1000);
System.out.println(thread3.getState());
}catch (Exception e){}
Runnable runnable4 = new PrintThreadDemo("4");
System.out.println("runnable4:"+runnable4);
new Thread(runnable4, "4").start();
}
public static void test1(){
// 演示匿名内部类的方式
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("匿名内部类:r1");
}
};
// 使用Lambda表达式简化
Runnable r2 = () -> System.out.println("Lambda表达式 匿名内部类:r2");
Runnable r3 = () -> {};
// 调用
r1.run();
r2.run();
System.out.println("r1:"+r1);
System.out.println("r2:"+r2);
}
@Override
public void run() {
super.run();
System.out.println("run方法 Thread name:"+Thread.currentThread().getName());
}
}
线程状态:TERMINATED,表示这个线程运行已经结束 ????建议大家遇到问题查资料的时候,对于博客内容有疑问的,或者为了更好的理解一个问题,多看一些博客,小编光看这个构造引用的问题,搜的博客就有很多,下面这些还没截已经关闭掉的网页。因为有些博客,可能只是照搬,或者不够细致,或者内容过时,或者他本人理解的很到位但是你对于他讲解内容的说法不是很理解,或者博客内容一篇中的大部分理解是对的,其中某个点说的不太对,等等,都有可能,所以多看一些不同博客的内容讲解,有助于个人理解。做研究应该与时俱进(尽量研究最新的写法),一丝不苟,有疑问就要弄清楚。 ????这个问题有理解错误的地方或者更好的理解,请大家指正。 参考: Java8新特性【方法引用】与【构造器引用】详细讲解 Java8新特性之方法引用与构造器引用 Lambda表达式之方法引用和构造器引用 Java8新特性_方法引用与构造器引用 JAVA8新特性之方法引用与构造器引用 Java8 新特性 方法引用和构造器引用 2022.5.6 21:36 ylxy
|