关于属性的获取

wildyy 2019-01-28 10:02:27
public class Test {

public static void main(String[] args) {
T t = new T2();
System.out.println(t.i);
System.out.println(t.getI());
}

public static class T {
public int i = 1;

public int getI() {
return this.i;
}
}

public static class T2 extends T {
public int i = 2;
public int getI() {
return this.i;
}
}
}
目前我只知道方法的调用会根据对象从最终子类对象依次往父类查找,找到即调用。但是属性呢,从上面的输出看与方法不同,输出1,即T中的属性,如果将T t = new T2();改成T2 t = new T2();那么会打印2,我的猜测是根据引用类型从最终子类开始依次查找父类,找到该引用类型对应的对象,然后根据属性的偏移量获取值。
上面是我的猜测,不知道有没有大牛能指导下。此过程具体jvm是如何做的?
...全文
167 8 打赏 收藏 转发到动态 举报
写回复
用AI写文章
8 条回复
切换为时间正序
请发表友善的回复…
发表回复
qybao 2019-01-29
  • 打赏
  • 举报
回复
上面我纠正一下,方法还是运行期决定的,编译期只是check方法的合法性
getfieldId返回压栈对象的属性,编译期生成的指令代码压栈对象就是T(编译期并不知道t会指向T2,只有运行期才知道),i属性也就是相对于T的内存地址偏移量的该内存地址的信息,所以就是父类T的属性。(当然如果你的代码把t强行转化为T2再取属性,那编译期生成的指令压栈的对象就是T2,i属性也就是相对于T2的内存地址偏移量的该内存地址的信息,即T2的属性)
invokevirtual在编译期就相当于一个link(链接),在运行期时会到实际指向对象的方法区去查找方法地址,如果子类重载了父类方法,那就方法区的方法表里的信息就是子类方法的地址,否则就是父类方法的地址

wildyy 2019-01-29
  • 打赏
  • 举报
回复
各位早上好,先顶一发。
wildyy 2019-01-28
  • 打赏
  • 举报
回复
引用 2 楼 Coder-YangMo 的回复:
[quote=引用 1 楼 Coder-YangMo 的回复:]
T t = new T2();
多态的问题:多态是针对子类重写父类的方法的,但对变量则没有多态。

比如A a = new B();是创建了一个子类对象并把它当成父类对象A用

也就是父类引用指向子类对象

此时,引用变量a有2个类型,编译时的类型为A,运行时的类型为B.在代码编译过程中,a 只能调用属于A的方法. 不能调用B类里面的方法.注意,由于继承关系,如果B重写了A的某个方法,比如说eat(),而在代码编译过程中,a.eat()调用的是A的eat(),但在程序运行时,却运行的是B的eat(). 这就是多态

如果你定义t的时候,是T2 t2 = new T2(),拿到的所有值就都是T2中的,不存在多态的问题。[/quote]
兄弟,我知道多态是什么,你看我对方法的描述,这是多态的底层实现,我现在需要知道的是jvm拿到你获取属性的指令后,是如何做的。对应的字节码指令是getfield,是不是我猜测的那种实现,还是说别的实现。
Coder-YangMo 2019-01-28
  • 打赏
  • 举报
回复
引用 1 楼 Coder-YangMo 的回复:
T t = new T2();
多态的问题:多态是针对子类重写父类的方法的,但对变量则没有多态。

比如A a = new B();是创建了一个子类对象并把它当成父类对象A用

也就是父类引用指向子类对象

此时,引用变量a有2个类型,编译时的类型为A,运行时的类型为B.在代码编译过程中,a 只能调用属于A的方法. 不能调用B类里面的方法.注意,由于继承关系,如果B重写了A的某个方法,比如说eat(),而在代码编译过程中,a.eat()调用的是A的eat(),但在程序运行时,却运行的是B的eat(). 这就是多态

如果你定义t的时候,是T2 t2 = new T2(),拿到的所有值就都是T2中的,不存在多态的问题。
Coder-YangMo 2019-01-28
  • 打赏
  • 举报
回复
T t = new T2();
多态的问题:多态是针对子类重写父类的方法的,但对变量则没有多态。

比如A a = new B();是创建了一个子类对象并把它当成父类对象A用

也就是父类引用指向子类对象

此时,引用变量a有2个类型,编译时的类型为A,运行时的类型为B.在代码编译过程中,a 只能调用属于A的方法. 不能调用B类里面的方法.注意,由于继承关系,如果B重写了A的某个方法,比如说eat(),而在代码编译过程中,a.eat()调用的是A的eat(),但在程序运行时,却运行的是B的eat(). 这就是多态
wildyy 2019-01-28
  • 打赏
  • 举报
回复
引用 7 楼 qybao 的回复:
每个java都有class文件,如果定义了属性,class文件就有属性的描述信息
java有编译期和运行期,当编译T t = new T2()时,t是T类型,所以参照的T的class文件,如果T的class文件没有i属性,那么下一句t.i就会报编译错误,也就是属性在编译期就已经决定
而方法是运行期决定的,解析器执行getI方法时,会先查找new T2()对象的内存里是否有getI方法(也就是T2的方法本身的内存地址),如果没找到,那么解析器会继续查找调用父类T的方法(直到找到调用为止)。其实方法也是编译期时决定的(只是因为大家对运行时对象的内存地址比较直观,所以用运行期讨论比较容易理解),如果T2定义了getI,那么T2的class文件里的getI方法映射存的就是T2的方法地址,否则class文件里的getI方法映射存的是T的方法地址


首先感谢你的回答。我提出些我的看法。
第一点,"如果T2定义了getI,那么T2的class文件里的getI方法映射存的就是T2的方法地址,否则class文件里的getI方法映射存的是T的方法地址",对于这句话,我觉得class对象应该只会存储父类的class对象的引用地址,不会存父类方法的任何信息,这个需要在父类class中去找,你可以去看Class类的getMethods方法,获取所有公共方法包括继承的,如果子类存储着继承的方法,就不用去查找父类了,而实际上这个方法的实现中会一级一级的调用父类class的同名方法。编译期只是判断引用类型是否有这个方法。方法还是运行期决定的。
第二点就是"会先查找new T2()对象的内存里是否有getI方法"这一句,实例对象内存是不存方法的信息的,方法调用会去class中找。
对于i属性,你说的是编译期决定,编译期决定了字节码指令是什么,字节码中和方法一样,获取的类的类型都是T类。
我的问题其实简单点说就是字节码指令getfield具体实现流程是什么,jvm对这个指令做了哪些操作,如何去查找的?我能够确定的就是
T类和T2类的class中都存储着各自的i属性的内存地址偏移量,而且字节码指令获取指令针对的类的类型也是一致的,但是获取的结果不一致,那么获取属性的getfield指令与调用方法的指令invokevirtual的查找方式有何不同,属性是如何查找的
qybao 2019-01-28
  • 打赏
  • 举报
回复
每个java都有class文件,如果定义了属性,class文件就有属性的描述信息
java有编译期和运行期,当编译T t = new T2()时,t是T类型,所以参照的T的class文件,如果T的class文件没有i属性,那么下一句t.i就会报编译错误,也就是属性在编译期就已经决定
而方法是运行期决定的,解析器执行getI方法时,会先查找new T2()对象的内存里是否有getI方法(也就是T2的方法本身的内存地址),如果没找到,那么解析器会继续查找调用父类T的方法(直到找到调用为止)。其实方法也是编译期时决定的(只是因为大家对运行时对象的内存地址比较直观,所以用运行期讨论比较容易理解),如果T2定义了getI,那么T2的class文件里的getI方法映射存的就是T2的方法地址,否则class文件里的getI方法映射存的是T的方法地址



wildyy 2019-01-28
  • 打赏
  • 举报
回复
顶一发,顶顶顶顶顶顶顶顶顶顶顶顶顶顶顶顶顶顶顶顶顶顶顶顶

50,541

社区成员

发帖
与我相关
我的任务
社区描述
Java相关技术讨论
javaspring bootspring cloud 技术论坛(原bbs)
社区管理员
  • Java相关社区
  • 小虚竹
  • 谙忆
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧