ChatGPT似乎有的时候并不能搞懂Java的动态分派,你懂了吗?

news2024/7/6 19:38:56

目录

碎碎念

ChatGPT 中出现的问题

那么正确答案应该是什么呢?

分派的相关知识点总结:

分派是什么?

静态分派与动态分派:

Java语言是静态多分派,动态单分派的;

静态分派:静态重载多分派:

动态分派:动态重写单分派:

多分派类型与单分派类型

例题

例题一:重载方法匹配优先级(基本类型):

请分析如下程序的运行结果:

运行结果:

解题关键:

例题二:重载方法匹配优先级(引用类型):

请分析如下程序的运行结果:

运行结果:

解题关键:

例题三:动态分派

请分析如下程序的运行结果:

运行结果:

解题关键:

例题四:动态分派

请分析如下程序的运行结果:

运行结果:

解题关键:

例题五:单分派和多分派:

请分析如下程序的运行结果:

运行结果:

解题关键:

例题六:【用友笔试】

根据下面这个程序的内容,判断哪些描述是正确的:( )

运行结果:

解题关键:

ChatGPT的出现引发的思考

乐一下,让ChatGPT扮演服务端开发人员,看他自己对ChatGPT有什么看法:

参考文献

碎碎念

近期英子姐推荐了一本有关JVM的书,所以最近在看这个,发现,之前看的好多八股都是从这里出来的,收益颇多(虽然不一定能记住,但是看了总比不看强,或许看多了就记住了);

这本书的名字叫:《深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)周志明》,本来是边看,不懂的就跟ChatGPT讨论,ChatGPT绝大多数时候都是靠谱的,直到第八章 虚拟机字节码执行引擎中的8.3 方法调用中的8.3.2分派这一节出现了一点问题,ChatGPT似乎有的时候并不能搞懂Java的动态分派;

PS:本来这个文章是周四晚上立的,准备周五写的flag,但是由于种种原因拖到了周日晚上,好在是在临近周一之前完成了

ChatGPT 中出现的问题

书中讲完动态分派之后,举了两个例子,ChatGPT就是在这第二个例子上翻车了,翻车实况见下图:

发现他说的有有问题之后,又让他改了两次,但可以,ChatGPT并没有把握住机会(doge)

 

那么正确答案应该是什么呢?

正确的输出结果应该是:

I am Son, i have $0
I am Son, i have $4
This gay has $2

原因如下:

  • 输出两句都是“I am Son”,这是因为Son类在创建的时候,首先隐式调用了Father的构造函数,而Father构造函数中对showMeTheMoney()的调用是一次虚方法调用,实际执行的版本是Son::showMeTheMoney()方法,所以输出的是“I am Son”;
    • 如果是父类与子类之间的重写方法的选择,则是使用动态类型
      • (如果有多个父类,那么接近上层的优先级越低)
    • 如果你想简单的理解,那就记住上面的话,动态重写多分派,因为是重写,所以这里用动态分派,所以接近上层的优先级越低,所以这里调用的是Son类中重写的方法;
  • 而这时候虽然父类的money字段已经被初始化成2了,但Son::showMeTheMoney()方法中访问的却是子类的money字段,这时候结果自然还是0,因为它要到子类的构造函数执行时才会被初始化;
    • 这里可能有人会乱套,乱套不要怕,直接IDEA debug!
  • 走完父类的构造方法,那就回到子类中继续往下走,这个应该没啥问题,就正常的赋值,调用重写方法;
  • 至此,new Son() 结束;
  • main()的最后一句通过静态类型访问到了父类中的money,输出了2;

那么让我们一起重新复习一下分派的知识点吧!

分派的相关知识点总结:

分派是什么?

  • Java中的分派(Dispatch)指的是根据方法的接收者和参数的实际类型,选择正确的方法实现的过程;
  • Java中的分派主要包括静态分派和动态分派两种类型。
    • 静态分派(Static Dispatch):
      • 发生在编译期间,由编译器根据方法接收者和参数的静态类型确定具体调用的方法实现。例如,如果在代码中定义了一个方法,它的参数是Object类型,但在调用时传入了一个String类型的实例,编译器会选择Object类型的方法实现。
    • 动态分派(Dynamic Dispatch):
      • 发生在运行期间,由Java虚拟机根据方法接收者的实际类型确定具体调用的方法实现。例如,如果在代码中定义了一个父类和一个子类,它们都有一个同名的方法,在运行时调用子类实例的方法时,Java虚拟机会选择子类的方法实现。
  • Java中的分派是基于多态的概念实现的。多态指的是同一操作作用于不同的对象,可以有不同的解释和不同的实现方式。在Java中,通过使用继承和重写方法实现多态。

静态分派与动态分派:

Java语言是静态多分派,动态单分派的;

  • 如果是重载方法之间的选择,则是使用静态类型
  • 如果是父类与子类之间的重写方法的选择,则是使用动态类型
  • (如果有多个父类,那么接近上层的优先级越低)
  • A a = new B(); 会使用类型B去查找重写的方法,使用类型A去查找重载的方法
  • 静态分派发生在编译期间,根据参数的静态类型来决定选择哪个重载方法;
  • 动态分派发生在运行期间,根据对象的实际类型来决定调用哪个重写方法;

静态分派:静态重载多分派

  • 静态分派(《Thinking In Java》中称之为静态绑定(前期绑定)):
    • 所有依赖静态类型来定位方法执行版本(版本即哪一个方法)的分派动作,静态分派的最典型的应用就是方法重载;
  • 静态类型在编译期是可知的;
  • 1)基本类型(包装类型):
    • 以char为例,按照char>int>long>double>float>double>Character>Serializable>Object>...(变长参数,将其视为一个数组元素)
    • 变长参数的重载优先级最低;
    • (注意char到byte或short之间的转换时不安全的)
    • 基本类型与基本类型之间存在自动类型转换;
    • 基本类型到包装类型之间存在自动装箱;
    • java.lang.Serializable是java.lang.Character类实现的一个接口;
    • Character是绝对不会转型为Integer的,它只能安全地转型为它实现的接口或父类;Character还实现了另外一个接口java.lang.Comparable<Character>,如果同时出现两个参数分别为Serializable和 Comparable<Character>的重载方法,那它们在此时的优先级是一样的;编译器无法确定要自动转型为哪种类型,会提示“类型模糊”(Type Ambiguous),并拒绝编译;但是如果绕过Javac编译器,自己去构造出表达相同语义的字节码,将会发现这是能够通过Java虚拟机的类加载校验,而且能够被Java虚拟机正常执行的,但是会选择Serializable还是Comparable<Character>的重载方法则并不能事先确定,这是《Java虚拟机规范》所允许的;
    • 【注意】有一些在单个参数中能成立的自动转型,如char转型为int,在变长参数中是不成立的;
  • 2)引用类型:
    • 则需要根据继承关系进行匹配,注意只跟其编译时类型即静态类型相关;
    • 如果是重载方法之间的选择,则是使用静态类型

动态分派:动态重写单分派

  • 如果是父类与子类之间的重写方法的选择,则是使用动态类型
    • (如果有多个父类,那么接近上层的优先级越低)
  • 动态分派(《Thinking In Java》中称之为动态绑定(后期绑定)):
    • 在运行期根据实际类型确定执行版本的分派过程称为动态分派,这是重写的实际本质,在重写过程中并不是唯一的版本,而是选择更加合适的版本(如果有多个父类,那么接近上层的优先级越低);

多分派类型与单分派类型

  • 多分派类型:
    • 根据一个以上的宗量(方法的接受者与方法的参数统称为方法的宗量)进行方法的选择方法的分派类型;其中静态分派属于多分派类型;即Father father = new Son(); father.overloadMethod(param),中overloadMethod()方法的选择是要根据静态类型Father与方法的参数param共同确定的;
  • 单分派类型:
    • 动态分配属于单分派类型,即只会根据实际类型Son选择方法
  • 总结:
    • 静态多分派,动态单分派的语言

或许看完很抽象,所以应该结合例子看,例子如下!

例题

例题将包括5个书上的,和一个笔试题

例题一:重载方法匹配优先级(基本类型):

请分析如下程序的运行结果:

package org.fenixsoft.polymorphic;
public class Overload {
	public static void sayHello(Object arg) {
		System.out.println("hello Object");
	}
	public static void sayHello(int arg) {
		System.out.println("hello int");
	}
	public static void sayHello(long arg) {
		System.out.println("hello long");
	}
	public static void sayHello(Character arg) {
		System.out.println("hello Character");
	}
	public static void sayHello(char arg) {
		System.out.println("hello char");
	}
	public static void sayHello(char... arg) {
		System.out.println("hello char ...");
	}
	public static void sayHello(Serializable arg) {
		System.out.println("hello Serializable");
	}
	public static void main(String[] args) {
		sayHello('a');
	}
}

运行结果:

hello char

解题关键:

你只需要记住上面说的基本类型静态分派的顺序即可:

以char为例,按照char>int>long>double>float>double>Character>Serializable>Object>...(变长参数,将其视为一个数组元素)

例题二:重载方法匹配优先级(引用类型):

请分析如下程序的运行结果:

public class StaticDispatch {
	static abstract class Human {
	}
	static class Man extends Human {
	}
	static class Woman extends Human {
	}
	public void sayHello(Human guy) {
		System.out.println("hello,guy!");
	}
	public void sayHello(Man guy) {
		System.out.println("hello,gentleman!");
	}
	public void sayHello(Woman guy) {
		System.out.println("hello,lady!");
	}
	public static void main(String[] args) {
		Human man = new Man();
		Human woman = new Woman();
		StaticDispatch sr = new StaticDispatch();
		sr.sayHello(man);
		sr.sayHello(woman);
	}
}

运行结果:

hello,guy!
hello,guy!

解题关键:

  • Java语言是静态多分派,动态单分派的;
    • 如果是重载方法之间的选择,则是使用静态类型
    • 如果是父类与子类之间的重写方法的选择,则是使用动态类型
      • (如果有多个父类,那么接近上层的优先级越低)
    • 如A a = new B(); 会使用类型B去查找重写的方法,使用类型A去查找重载的方法;

例题三:动态分派

请分析如下程序的运行结果:

public class DynamicDispatch {
	static abstract class Human {
		protected abstract void sayHello();
	}
	static class Man extends Human {
		@Override
		protected void sayHello() {
			System.out.println("man say hello");
		}
	}
	static class Woman extends Human {
		@Override
		protected void sayHello() {
			System.out.println("woman say hello");
		}
	}
	public static void main(String[] args) {
		Human man = new Man();
		Human woman = new Woman();
		man.sayHello();
		woman.sayHello();
		man = new Woman();
		man.sayHello();
	}
}

运行结果:

man say hello
woman say hello
woman say hello

解题关键:

  • 在Java里面只有虚方法存在,字段永远不可能是虚的,换句话说,字段永远不参与多态,哪个类的方法访问某个名字的字段时,该名字指的就是这个类能看到的那个字段;当子类声明了与父类同名的字段时,虽然在子类的内存中两个字段都会存在,但是子类的字段会遮蔽父类的同名字段;

例题四:动态分派

请分析如下程序的运行结果:

public class FieldHasNoPolymorphic {
	static class Father {
		public int money = 1;
		public Father() {
			money = 2;
			showMeTheMoney();
		}
		public void showMeTheMoney() {
			System.out.println("I am Father, i have $" + money);
		}
	}
	static class Son extends Father {
		public int money = 3;
		public Son() {
			money = 4;
			showMeTheMoney();
		}
		public void showMeTheMoney() {
			System.out.println("I am Son, i have $" + money);
		}
	}
	public static void main(String[] args) {
		Father gay = new Son();
		System.out.println("This gay has $" + gay.money);
	}
}

运行结果:

I am Son, i have $0
I am Son, i have $4
This gay has $2

解题关键:

  • 输出两句都是“I am Son”,这是因为Son类在创建的时候,首先隐式调用了Father的构造函数,而Father构造函数中对showMeTheMoney()的调用是一次虚方法调用,实际执行的版本是Son::showMeTheMoney()方法,所以输出的是“I am Son”;
    • 如果是父类与子类之间的重写方法的选择,则是使用动态类型
      • (如果有多个父类,那么接近上层的优先级越低)
    • 如果你想简单的理解,那就记住上面的话,动态重写多分派,因为是重写,所以这里用动态分派,所以接近上层的优先级越低,所以这里调用的是Son类中重写的方法;
  • 而这时候虽然父类的money字段已经被初始化成2了,但Son::showMeTheMoney()方法中访问的却是子类的money字段,这时候结果自然还是0,因为它要到子类的构造函数执行时才会被初始化;
    • 这里可能有人会乱套,乱套不要怕,直接IDEA debug!
  • 走完父类的构造方法,那就回到子类中继续往下走,这个应该没啥问题,就正常的赋值,调用重写方法;
  • 至此,new Son() 结束;
  • main()的最后一句通过静态类型访问到了父类中的money,输出了2;

例题五:单分派和多分派:

请分析如下程序的运行结果:

public class Dispatch {
	static class QQ {}
	static class _360 {}
	public static class Father {
		public void hardChoice(QQ arg) {
			System.out.println("father choose qq");
		}
		public void hardChoice(_360 arg) {
			System.out.println("father choose 360");
		}
	}
	public static class Son extends Father {
		public void hardChoice(QQ arg) {
			System.out.println("son choose qq");
		}
		public void hardChoice(_360 arg) {
			System.out.println("son choose 360");
		}
	}
	public static void main(String[] args) {
		Father father = new Father();
		Father son = new Son();
		father.hardChoice(new _360());
		son.hardChoice(new QQ());
	}
}

运行结果:

father choose 360
son choose qq

解题关键:

  • 在Java语言中,方法的选择过程包括两个阶段:静态分派和动态分派;
  • 静态分派发生在编译期间,根据参数的静态类型来决定选择哪个重载方法;而动态分派发生在运行期间,根据对象的实际类型来决定调用哪个重写方法;
  • 在本例中,静态分派选择的目标方法签名是hardChoice(QQ)和hardChoice(_360),但实际执行的方法取决于运行时对象的实际类型;
  • 因此,father.hardChoice(new _360())调用了Father类中的hardChoice(_360)方法,而son.hardChoice(new QQ())调用了Son类中的hardChoice(QQ)方法;
  • 由于动态分派的目标方法只与接收者的实际类型有关,而与参数的类型无关,因此Java语言的动态分派属于单分派类型;

例题六:【用友笔试】

根据下面这个程序的内容,判断哪些描述是正确的:( )

public class Test {
    public static void main(String args[]) {
        String s = "tommy";
        Object o = s;
        sayHello(o); //语句1
        sayHello(s); //语句2
    }
    public static void sayHello(String to) {
        System.out.println(String.format("Hello, %s", to));
    }
    public static void sayHello(Object to) {
        System.out.println(String.format("Welcome, %s", to));
    }
}
  1. A. 这段程序有编译错误
  2. B. 语句1输出为:Hello, tommy
  3. C. 语句2输出为:Hello, tommy
  4. D. 语句1输出为:Welcome, tommy
  5. E. 语句2输出为:Welcome, tommy
  6. F. 根据选用的Java编译器不同,这段程序的输出可能不同

运行结果:

正确答案: C D 

解题关键:

相信懂了书里面5个较难例子的你,肯定做对啦,所以也就不讲啦!

ChatGPT的出现引发的思考

ChatGPT作为一个超强的AI,或许能取代一部分程序员,但是正所谓”智者千虑必有一失,愚者千虑必有一得“,在学习和生活中要不断思考,深挖才会有更多的价值!

并且现在ChatGPT已经能输出图片,并且也接入了一些聊天软件

可以看出,虽然ChatGPT能够根据文字描述生成大体符合要求的图片,但是一些细节问题仍有较大的发展空间,之前我还担心有了ChatGPT我的研究生方向:人脸超分辨率恢复与重建会不会变的没有意义,现在看来,会有意义,至少在我毕业之前仍会存在异议,从上图中可以看出,虽然ChatGPT能够生成大概的场景,但是对人脸五官的细节恢复十分差劲;

乐一下,让ChatGPT扮演服务端开发人员,看他自己对ChatGPT有什么看法:

参考文献

  • 《深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)周志明》
  • ChatGPT

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/374191.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

追梦之旅【数据结构篇】——详解C语言实现二叉树

详解C语言实现二叉树~&#x1f60e;前言&#x1f64c;什么是二叉树&#xff1f;二叉树的性质总结&#xff1a;整体实现内容分析&#x1f49e;1.头文件的编写&#xff1a;&#x1f64c;2.功能文件的编写&#xff1a;&#x1f64c;1&#xff09;前序遍历的数值来创建树——递归函…

IGKBoard(imx6ull)-Input设备编程之按键控制

文章目录1- input子系统介绍2- input事件目录&#xff08;1&#xff09;struct input_event 结构体&#xff08;2&#xff09;type&#xff08;事件类型&#xff09;&#xff1a;&#xff08;3&#xff09;code&#xff08;事件编码&#xff09;&#xff08;4&#xff09;value…

【华为OD机试模拟题】用 C++ 实现 - 九宫格按键输入(2023.Q1)

最近更新的博客 【华为OD机试模拟题】用 C++ 实现 - 去重求和(2023.Q1) 文章目录 最近更新的博客使用说明九宫格按键输入题目输入输出示例一输入输出说明示例二输入输出说明Code使用说明 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过率才会高…

webp格式转换成png怎么转

相对于png 图片&#xff0c;webp比png小了45%&#xff0c;但是缺点是你压缩的时候需要的时间更久了&#xff1b;优点是体积小巧&#xff1b;缺点是兼容性不太好, 只有opera,和chrome支持&#xff0c;不仅如此在后期的编辑修改上也很多软件无法打开。所以我们通常要将webp格式转…

9.1 IGMPv1实验

9.4.1 IGMPv1 实验目的 熟悉IGMPv1的应用场景掌握IGMPv1的配置方法实验拓扑 实验拓扑如图9-7所示&#xff1a; 图9-7&#xff1a;IGMPv1 实验步骤 &#xff08;1&#xff09;配置IP地址 MCS1的配置 MCS1的IP地址配置如图9-8所示&#xff1a; 图9-8&#xff1a;MCS1的配置 …

xgboost学习-XGBoost的智慧

文章目录一、选择弱评估器&#xff1a;重要参数booster二、XGB的目标函数&#xff1a;重要参数objective三、求解XGB的目标函数四、参数化决策树 alpha&#xff0c;lambda五、寻找最佳树结构&#xff1a;求解 ω与T六、寻找最佳分枝&#xff1a;结构分数之差七、让树停止生长&a…

redis(10)事务和锁机制

Redis事务定义 Redis 事务是一个单独的隔离操作&#xff1a;事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中&#xff0c;不会被其他客户端发送来的命令请求所打断。 Redis 事务的主要作用就是串联多个命令防止别的命令插队。 Multi、Exec、discard Redis 事务中…

【数据挖掘实战】——应用系统负载分析与容量预测(ARIMA模型)

项目地址&#xff1a;Datamining_project: 数据挖掘实战项目代码 目录 一、背景和挖掘目标 1、问题背景 2、传统方法的不足 2、原始数据 3、挖掘目标 二、分析方法与过程 1、初步分析 2、总体流程 第一步&#xff1a;数据抽取 第二步&#xff1a;探索分析 第三步&a…

【华为OD机试模拟题】用 C++ 实现 - 内存池(2023.Q1)

最近更新的博客 【华为OD机试模拟题】用 C++ 实现 - 去重求和(2023.Q1) 文章目录 最近更新的博客使用说明内存池题目输入输出示例一输入输出说明Code使用说明 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过率才会高。 华为 OD 清单查看地址:…

C++【string类用法详细介绍string类模拟实现解析】

文章目录string 类用法介绍及模拟实现一、string介绍二、string类常用接口1. string类对象的常见构造接口2.string类对象的常见容量接口3.string类对象的常见修改接口4. string类对象的常见访问及遍历接口5.string其他接口1.不常用查找接口2.字符替换3.字符串拼接4.字符串排序5…

纯x86汇编实现的多线程操作系统实践 - 第三章 BSP的守护执行

本章我们将详细讲解BSP剩下的执行代码&#xff0c;它们被安排在bp_32.asm文件中。bp_32.asm主要完成以下功能&#xff1a;系统中断初始化加载字符图形数据到内存区域将AP的启动代码和32位保护模式下的代码分别加载到内存中显示主界面以及系统启动信息向所有AP群发启动命令进入守…

linux 解压.gz文件 报错 gzip:stdin:not in gzip format(已解决)

目录 1、问题&#xff1a; 2、分析原因 3、解决办法 1、问题&#xff1a; 在解压一个以【.gz】&#xff08;注意不是.tar.gz&#xff09;结尾的压缩包时&#xff0c;遇到报错 【gzip&#xff1a;stdin&#xff1a;不是gzip格式】 翻译一下问题&#xff1a;【gzip&#xff1a;st…

纯x86汇编实现的多线程操作系统实践 - 第一章 系统整体结构说明

现代CPU都是多核系统&#xff0c;拥有多个执行内核&#xff08;即计算引擎&#xff09;&#xff0c;可并发执行不同的代码。在CPU众多的执行内核中&#xff0c;有一个为主执行内核&#xff08;BSP&#xff09;&#xff0c;在CPU上电后&#xff0c;该主执行内核会率先启动&#…

lighthouse-自定义Gatherer与Audits

这篇文章是Lighthouse的后续&#xff0c;之前介绍了 lighthouse的介绍和基本使用方法 Lighthouse组合Puppeteer检测页面 这两篇文章&#xff0c;在这两篇文章中介绍了lighthouse的整体架构和基本运行的逻辑&#xff0c;lighthouse默认也采集了足够丰富的数据供我们去分析页面的…

都在用 AI 生成美少女,而我却。。。

最近 AI 画画特别的火&#xff0c;你能从网上看到非常多好看的图片&#xff0c;于是我就开始了我的安装之旅&#xff0c;我看到的图是这样的。这样的。还有这样的。然后我就开始了我的 AI 安装生成计划。安装环境首先我们需要安装 Python 环境&#xff0c;因为这个需要显卡&…

NCRE计算机等级考试Python真题(二)

第二套试题1、关于算法的描述&#xff0c;以下选项中错误的是A.算法具有可行性、确定性、有穷性的基本特征B.算法的复杂度主要包括时间复杂度和数据复杂度C.算法的基本要素包括数据对象的运算和操作及算法的控制结构D.算法是指解题方案的准确而完整的描述正确答案&#xff1a; …

Java基础之日志

2.日志 2.1概述【理解】 概述 程序中的日志可以用来记录程序在运行的时候点点滴滴。并可以进行永久存储。 日志与输出语句的区别 输出语句日志技术取消日志需要修改代码&#xff0c;灵活性比较差不需要修改代码&#xff0c;灵活性比较好输出位置只能是控制台可以将日志信息写…

用于C++的对象关系映射库—YB.ORM

1 介绍YB.ORM YB.ORM 旨在简化与关系数据库交互的 C 应用程序的开发。 对象关系映射器(ORM) 通过将数据库表映射到类并将表行映射到应用程序中的对象来工作&#xff0c;这种方法可能不是对每个数据库应用程序都是最佳的&#xff0c;但它被证明在需要复杂逻辑和事务处理的应用程…

不怕被AirTag跟踪?苹果Find My技术越来越普及

苹果的 AirTag 自推出以来&#xff0c;如何有效遏制用户用其进行非法跟踪&#xff0c;是摆在苹果面前的一大难题。一家为执法部门制造无线扫描设备的公司近日通过 KickStarter 平台&#xff0c;众筹了一款消费级产品&#xff0c;可帮助用户检测周围是否存在追踪的 AirTag 等设备…

Spring中的FactoryBean 和 BeanFactory、BeanPostProcessor 和BeanFactoryPostProcessor解析

文章目录FactoryBean 和 BeanFactory后置处理器BeanPostProcessor 和 BeanFactoryPostProcessorBeanPostProcessorBeanFactoryPostProcessorFactoryBean 和 BeanFactory BeanFactory接⼝是容器的顶级接⼝&#xff0c;定义了容器的⼀些基础⾏为&#xff0c;负责⽣产和管理Bean的…