Java8中直接new对象和用 类名::new 创建对象这两种形式有什么区别

    看过此篇文章后,你可以认为 类名::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
java8以上的函数式接口支撑三个以上参数

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值