当前位置: 首页 > news >正文

动手动脑4

文档:https://files.cnblogs.com/files/blogs/847696/动手动脑4.zip?t=1761298729&download=true

image
class Parent {
int myValue = 0;

public void printValue() {System.out.println("Parent: myValue = " + myValue);
}

}

class Child extends Parent {
int myValue = 1; // 隐藏父类的myValue

@Override
public void printValue() {System.out.println("Child: myValue = " + myValue);
}

}

  1. 左边的程序运行结果是什么?
    运行ParentChildTest类的main方法,输出结果如下:

Parent: myValue = 0
Child: myValue = 1
Child: myValue = 1
Child: myValue = 1
Child: myValue = 2
2. 你如何解释会得到这样的输出?
输出结果可以通过Java的变量隐藏和方法重写机制来解释:

第1行输出:Parent parent = new Parent(); parent.printValue(); 创建Parent对象并调用printValue,输出Parent类的myValue(初始值为0)。

第2行输出:Child child = new Child(); child.printValue(); 创建Child对象并调用printValue,输出Child类的myValue(初始值为1)。注意,Child类重写了printValue方法,因此调用的是子类的方法。

第3行输出:parent = child; parent.printValue(); 将parent引用指向Child对象。由于方法调用是动态绑定的,parent.printValue()会调用Child类的printValue方法,输出Child类的myValue(仍为1)。

第4行输出:parent.myValue++; parent.printValue(); 通过parent引用访问myValue。变量访问是静态绑定的,基于引用类型(Parent),因此parent.myValue++增加的是Parent类的myValue变量(从0变为1),但Child类的myValue未被修改。所以parent.printValue()仍然输出Child类的myValue(1)。

第5行输出:((Child)parent).myValue++; parent.printValue(); 将parent强制转换为Child类型,然后访问myValue。这时访问的是Child类的myValue变量,将其从1增加到2。因此parent.printValue()输出Child类的myValue(2)。

  1. 从这些运行结果中,你能总结出Java的哪些语法特性?
    从运行结果中可以总结出Java的以下语法特性:

继承:子类(如Child)可以继承父类(如Parent)的字段和方法。

方法重写(Override):子类可以重写父类的方法。在运行时,方法调用根据实际对象类型动态绑定,即多态性的体现。例如,parent.printValue()在parent指向Child对象时调用的是Child类的printValue方法。

变量隐藏(Field Hiding):如果子类定义了与父类同名的实例变量,则子类的变量会隐藏父类的变量。变量访问是静态绑定的,基于引用类型(编译时类型),而不是实际对象类型。例如,通过Parent引用访问myValue时,总是访问Parent类的变量;通过Child引用访问时,总是访问Child类的变量。

类型转换:可以将父类引用强制转换为子类类型(向下转型),以访问子类特有的成员。但需要确保实际对象是子类类型,否则会抛出ClassCastException。例如,((Child)parent).myValue++允许访问Child类的myValue。

多态:方法调用表现出多态行为,但字段访问没有多态性。这强调了Java中方法和字段在继承体系中的不同处理方式。

代码测试验证

class Parent {
int myValue = 0;

public void printValue() {System.out.println("Parent: myValue = " + myValue);
}

}

class Child extends Parent {
int myValue = 1;

@Override
public void printValue() {System.out.println("Child: myValue = " + myValue);
}

}

public class ParentChildTest {
public static void main(String[] args) {
Parent parent = new Parent();
parent.printValue();
Child child = new Child();
child.printValue();
parent = child;
parent.printValue();
parent.myValue++;
parent.printValue();
((Child) parent).myValue++;
parent.printValue();
}
}

image
class Mammal {}
class Dog extends Mammal {}
class Cat extends Mammal {}

public class TestCast {
public static void main(String args[]) {
Mammal m;
Dog d = new Dog();
Cat c = new Cat();
m = d;
// d = m; // 语句2
d = (Dog) m; // 语句3
// d = c; // 语句4
// c = (Cat) m; // 语句5
}
}
分析语句:
m = d;

这是向上转型(upcasting),将子类引用赋值给父类引用。在Java中,这是安全的,不会引起编译错误或运行时错误。

d = m;

这将父类引用赋值给子类引用。由于父类引用可能指向其他子类对象(如Cat),编译器无法保证类型安全,因此会引发编译错误。错误信息类似于:incompatible types: Mammal cannot be converted to Dog。

d = (Dog) m;

这是向下转型(downcasting),将父类引用强制转换为子类类型。在编译时不会错误,但运行时行为取决于m实际指向的对象。在代码中,m被赋值为d(Dog对象),因此转换是安全的,不会抛出异常。但如果m指向其他类型(如Cat),则会抛出ClassCastException。

d = c;

这将Cat引用赋值给Dog引用。Dog和Cat是兄弟类,没有继承关系,因此类型不兼容,会引发编译错误。错误信息类似于:incompatible types: Cat cannot be converted to Dog。

c = (Cat) m;

这是向下转型,将父类引用强制转换为Cat类型。在编译时不会错误,但运行时,由于m实际指向Dog对象(因为之前有m = d),因此会抛出ClassCastException,因为Dog对象不能转换为Cat类型。

总结:
引起编译错误的语句:d = m 和 d = c。
原因:d = m 需要显式类型转换,而编译器无法保证m总是Dog类型;d = c 因为类型不兼容,Dog和Cat没有继承关系。

引起运行时错误的语句:c = (Cat) m。
原因:m实际指向Dog对象,强制转换为Cat类型会抛出ClassCastException。

验证:
class Mammal {}
class Dog extends Mammal {}
class Cat extends Mammal {}

public class TestCast {
public static void main(String args[]) {
Mammal m;
Dog d = new Dog();
Cat c = new Cat();
m = d; // 正常
// d = m; // 取消注释会导致编译错误
d = (Dog) m; // 正常,因为m指向Dog
// d = c; // 取消注释会导致编译错误
// c = (Cat) m; // 取消注释会导致运行时ClassCastException
}
}
实际运行结果:

如果取消注释d = m;或d = c;,编译将失败。

如果取消注释c = (Cat) m;,运行时将抛出异常:Exception in thread "main" java.lang.ClassCastException: Dog cannot be cast to Cat。

image

  1. 编译代码并查看字节码
    首先,编译Java文件:

javac TestPolymorphism.java
然后,使用javap反编译类文件,查看字节码指令:

javap -c TestPolymorphism
TestPolymorphism 的 main 方法字节码:

public static void main(java.lang.String[]);
Code:
0: new #2 // class Parent
3: dup
4: invokespecial #3 // Method Parent.""😦)V
7: astore_1
8: aload_1
9: invokevirtual #4 // Method Parent.Introduce:()V
12: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
15: aload_1
16: getfield #6 // Field Parent.value:I
19: invokevirtual #7 // Method java/io/PrintStream.println:(I)V
22: new #8 // class Son
25: dup
26: invokespecial #9 // Method Son.""😦)V
29: astore_1
30: aload_1
31: invokevirtual #4 // Method Parent.Introduce:()V
34: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
37: aload_1
38: getfield #6 // Field Parent.value:I
41: invokevirtual #7 // Method java/io/PrintStream.println:(I)V
44: new #10 // class Daughter
47: dup
48: invokespecial #11 // Method Daughter.""😦)V
51: astore_1
52: aload_1
53: invokevirtual #4 // Method Parent.Introduce:()V
56: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
59: aload_1
60: getfield #6 // Field Parent.value:I
63: invokevirtual #7 // Method java/io/PrintStream.println:(I)V
66: return
关键字节码指令说明:
invokevirtual #4:用于调用实例方法(如 Introduce)。这里的 #4 是常量池索引,指向 Parent.Introduce 方法。但 invokevirtual 指令支持多态,运行时会根据对象的实际类型调用正确的方法。

getfield #6:用于访问字段(如 value)。这里的 #6 指向 Parent.value 字段。字段访问不是多态的,基于引用类型(编译时类型)而非实际对象类型。

new、dup、invokespecial:用于创建对象并调用构造函数。

  1. 多态的实现机制:从字节码到 JVM 运行时
    Java 中的多态是通过 虚方法表(vtable) 和 invokevirtual 指令 实现的。以下是详细解释:

(1)编译时的“滞后绑定”
在编译阶段,编译器为方法调用生成 invokevirtual 指令,并引用常量池中的方法符号(如 Parent.Introduce)。编译器无法知道实际对象类型,因此采用“滞后绑定”(late binding),将方法解析推迟到运行时。

从字节码可以看出,所有 p.Introduce() 调用都使用相同的 invokevirtual #4 指令,尽管 p 指向不同子类对象。

(2)运行时的多态解析
当 JVM 执行 invokevirtual 指令时,它会:

获取对象的实际类型(通过对象头中的类指针)。

查找该类的虚方法表(vtable)。每个类都有一个 vtable,其中存储了方法的实际入口地址。

根据方法引用(常量池索引)在 vtable 中找到对应的方法实现。

调用该方法。

例如:

当 p 指向 Son 对象时,invokevirtual #4 会查找 Son 类的 vtable,找到 Son.Introduce 方法并调用。

当 p 指向 Daughter 对象时,同样过程会调用 Daughter.Introduce。

(3)字段访问没有多态
字段访问使用 getfield 指令,直接基于引用类型(编译时类型)解析。因此,p.value 总是访问 Parent 类中定义的 value 字段,即使 p 指向子类对象。这就是为什么输出中 value 总是 100,而不是子类的 101 或 102。

  1. 代码运行结果验证
    运行 TestPolymorphism 程序,输出如下:

I'm father
100
I'm son
100
I'm daughter
100
方法调用体现多态:Introduce 方法根据实际对象类型输出不同内容。

字段访问没有多态:value 字段始终输出 Parent 类的值。

  1. 总结 Java 的语法特性
    从多态的实现中,可以总结出以下 Java 特性:

方法重写(Override):子类可以重写父类的方法,运行时通过多态调用正确版本。

动态绑定:方法调用在运行时根据对象实际类型决定,通过 invokevirtual 指令和 vtable 实现。

字段隐藏:如果子类定义了与父类同名的字段,则子类字段会隐藏父类字段。字段访问是静态绑定的,基于引用类型。

编译时与运行时差异:编译器处理方法引用基于声明类型,但 JVM 在运行时动态解析方法。

多态的限制:多态仅适用于实例方法,不适用于字段、静态方法或私有方法。

http://www.hskmm.com/?act=detail&tid=38260

相关文章:

  • python+request+unittest自动化测试
  • 2025 年保温涂料厂家最新推荐排行榜:聚焦技术专利与管理体系认证的优质品牌耐高温/防火耐热/防腐/纳米介孔微珠中空粒子保温涂料公司推荐
  • 2025年云南独立成团游公司权威推荐榜单:云南旅游团/云南私享之旅/云南专属行程游源头公司精选
  • 2025 年气凝胶生产厂家最新推荐排行榜:含气凝胶毡 / 粉 / 隔热板 / 保温罩 / 陶瓷板品牌,优质厂家推荐
  • 102302143郑泽雄第一次作业
  • 作业4
  • 2025年5.5KW工业吸尘器厂家权威推荐榜单:380V防爆吸尘器/7.5KW工业吸尘器/水浴式吸尘器源头厂家精选
  • 2025 年兰州凯文中学推荐:兰州凯文中学,二十载深耕民办教育 双师赋能全维育人 以低进高出成效书写成长答卷
  • OpenEuler 22.03 手动升级 OpenSSH 至 10.2p1 完整方案
  • 配置GOPRIVATE引用私有仓库
  • 【C++】函数参数传递
  • 2025年3d全息投影生产厂家权威推荐榜单:全息投影展厅/全息投影沙盘/全息投影源头厂家精选
  • 用AI“抄底”双十一
  • 基于数据库实现分布式锁
  • 2025.10.24第一节课内容
  • 2025 年国内磁吸盘源头厂家最新推荐排行榜:聚焦电永 / 焊接电 / 电控永 / 起重电 / 液压潜水电等品类实力企业
  • 监督学习、无监督学习、半监督学习、强化学习、自监督学习
  • 2025 年退磁器生产厂家最新推荐榜:技术创新、行业适配与服务保障全景对比及权威测评结果强力退磁器/手提退磁器/小型退磁器公司推荐
  • word批量转pdf
  • 【IEEE出版 | 高届数会议 | 上届已于会后3个多月完成见刊检索】2025第九届控制工程与国际论坛(IWCEAA 2025)
  • SQLServer截取字符串、字符串长度、特殊字符在字符串的下标索引
  • 题解:P8134 [ICPC 2020 WF] Opportunity Cost
  • 解决Qt 不能debug问题
  • 2025年项目总延期?这30款项目进度管理软件让我提前交付率85%!
  • 2025 年最新护眼灯生产厂家推荐榜:含全光谱智能照明标杆企业及高产能品牌优选指南自然光护眼/全光谱护眼/儿童护眼吸顶灯公司推荐
  • Java多线程梳理
  • QT的事件循环(一)
  • 【开题答辩全过程】以 “辛巴克餐饮”小程序为例,具备答辩的问题和答案
  • QT中的反射机制
  • Exadata数据库性能异常,备份进程卡住