IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Java8中直接new对象和用 类名::new 创建对象这两种形式有什么区别 -> 正文阅读

[Java知识库]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

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-05-07 11:02:36  更:2022-05-07 11:04:00 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 0:22:50-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码