为什么在JVM中,字段和方法的解析相对于超类的方式不同?

huangapple go评论65阅读模式
英文:

Why are field and method resolution relative to superclasses different in the JVM?

问题

以下是翻译的内容:

研究JVM内部机制,对于这个示例感到有些困惑,其中既涉及子类中的覆盖方法,又涉及实例变量的屏蔽:

    class CA extends Object {
    	public int ivar;
    		
    	public void two() {
    		ivar = 20;
    		this.three();
    	}
    	
    	public void three() {
    		System.out.println("在CA中调用了three");
    		System.out.println(ivar);
    	}
    	
    }
    class CB extends CA {
    	public int ivar;
    	
    	public void one() {
    		ivar = 10;
    		two();
    	}
    	
    	public void three() {
    		System.out.println("在CB中调用了three");
    		System.out.println(ivar);
    		super.three();
    	}
    	
        public static void main(String[] args) {
    		CB cb = new CB();
    		cb.one();
    	}
    }

这将产生以下输出:

    在CB中调用了three
    10
    在CA中调用了three
    20

方法调用完全是预期的,但我观察到实例变量的行为不同-- 当在超类的实现内设置并访问它时,它访问的是“屏蔽”的值。

我似乎找不到这些行为在JVM规范中的讨论,分别在5.4.3.2和5.4.3.3节(字段和方法)。这两个部分的措辞非常相似:

> 如果`C`声明了与字段引用指定的名称和描述符相同的字段,则字段查找成功。[...]

以及

> [...] 如果`C`声明了与方法引用指定的名称和描述符相同的方法,则方法查找成功。

这对我来说意味着上述示例中的ivar行为是合理的-- `CA`的类文件不知道`CB`,因此它的字段引用指向*自己版本*的ivar,当JVM在执行该类的方法时设置和获取值时会尊重这一点。

但对于覆盖的方法来说也是一样的,我找不到解析算法来讨论“当前对象类”与“实现对象类”等概念。我是不是在找错地方?是否有人可以为我解释一下设计/实现?谢谢!
英文:

Studying JVM internals and am a bit confused about this example, which features both an overriding method in a subclass and an instance variable being shadowed:

class CA extends Object {
	public int ivar;
		
	public void two() {
		ivar = 20;
		this.three();
	}
	
	public void three() {
		System.out.println("three called in CA");
		System.out.println(ivar);
	}
	
}
class CB extends CA {
	public int ivar;
	
	public void one() {
		ivar = 10;
		two();
	}
	
	public void three() {
		System.out.println("three called in CB");
		System.out.println(ivar);
		super.three();
	}
	
    public static void main(String[] args) {
		CB cb = new CB();
		cb.one();
	}
}

This produces the following output:

three called in CB
10
three called in CA
20

The method calls are entirely expected but I observe that the instance variable behaves differently-- when it's set and accessed within the implementation of the superclass, it's accessing the "shadowed" value.

I can't seem to find the discussion of how these behaviors work in the JVM spec, sections 5.4.3.2 and 5.4.3.3 (fields and methods, respectively). Both sections have very similar wording:

> If C declares a field with the name and descriptor specified by the field reference, field lookup succeeds. [...]

and

> [...] if C declares a method with the name and descriptor specified by the method reference, method lookup succeeds.

This suggests to me that the ivar behavior in the example above is sensible-- the class file for CA doesn't know about CB, so its field ref points to its own version of ivar and the JVM respects that when it sets and gets the value as it executes that class's method.

But the same would seem true for the overriding method, and I don't find the resolution algorithm to talk about "current object class" vs "implementing object class", etc. Am I looking in the wrong place? Can someone illuminate for me the design/implementation? Thanks!

答案1

得分: 1

这在JLS的第15.12节“方法调用表达式”中有文档记录:

在编译时解析方法名称比解析字段名称更复杂,因为存在方法重载的可能性。在运行时调用方法也比访问字段更复杂,因为存在实例方法覆盖的可能性。

这只是一条注释,实际的方法调用规则非常冗长,您需要查看的部分是15.12.4.4“查找要调用的方法”。

如果类或接口C的方法m是私有的,那么它就是要调用的方法。

否则,可能会发生覆盖。下面指定了一种动态方法查找,用于定位要调用的方法。查找过程从类R开始,这是目标对象的实际运行时类。

Oracle官方文档链接

英文:

This is documented in 15.12 of the JLS, Method Invocation Expressions:

> Resolving a method name at compile time is more complicated than resolving a field name because of the possibility of method overloading. Invoking a method at run time is also more complicated than accessing a field because of the possibility of instance method overriding.

This is just a note, the actual rules for method invocation are very verbose and the part you want is 15.12.4.4. Locate Method to Invoke.

> If the method m of class or interface C is private, then it is the method to be invoked.
>
> Otherwise, overriding may occur. A dynamic method lookup, specified below, is used to locate the method to invoke. The lookup procedure starts from class R, the actual run-time class of the target object.

https://docs.oracle.com/javase/specs/jls/se15/html/jls-15.html#jls-15.12

答案2

得分: 0

我看不到你的困惑 - 它输出的正是我预期的。

main调用
CB.one,它将CB.ivar设置为10,然后调用
CA.two,它将CA.ivar设置为20,然后调用
CB.three,它打印CB.ivar(10),然后调用
CA.three,它打印CA.ivar(20)
英文:

I don't see your confusion -- what it outputted is exactly what I expected.

main calls
CB.one which sets CB.ivar to 10, then calls
CA.two which sets CA.ivar to 20, then calls
CB.three which prints CB.ivar (10), then calls
CA.three which prints CA.ivar (20)

huangapple
  • 本文由 发表于 2020年10月17日 04:14:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/64395823.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定