5

I have following codes.

public class Parent {

    @Override
    public int hashCode() {
         return 0;
    }

}

public class Child extends Parent {

    public void test() {
        this.toString();
        this.hashCode();
    }

}

As you see in the above codes, Child inherits toString() from Object and hashCode() from Parent. Bytecode operation of Child#test is as following.

ALOAD 0: this
INVOKEVIRTUAL Object.toString() : String
ALOAD 0: this
INVOKEVIRTUAL Child.hashCode() : int
RETURN

I think if invokevirtual calls Object.toString(), it should call Parent.hashCode() for consistency. or, Child.hashCode() called, then Child.toString() should be called.

However, invokevirtual does not keep its consistency if and only if target method is inherited by Object.

Only that case, invokevirtual calls method in the Object. For other cases, invokevirtual calls method in current class.

I want to know why this happens.

vijay
  • 2,646
  • 2
  • 23
  • 37
Hozard
  • 193
  • 2
  • 9
  • 5
    Given that the actual method executed will *always* be based on the actual type at execution time, why does this matter? They're equivalent, surely? – Jon Skeet Mar 02 '13 at 08:06
  • 1
    Maybe the compiler is special-casing inheritance from Object, because it is such a common case. – Patricia Shanahan Mar 02 '13 at 08:17
  • And how does it impacts your logic? – CuriousMind Mar 02 '13 at 08:31
  • @PatriciaShanahan But why wouldn't it special-case `hashCode` too? Note that `Parent` can change and be recompiled without recompiling `Child` (for example, `Parent` is a dependency whose minor version changes). `Parent` could thus acquire a `toString`. – Marko Topolnik Mar 02 '13 at 09:53
  • What kind of compiler and disassambler are you using? A normal compiler should have a POP after the inveokevirtual to remove the return value from the stack, and your assembly doesn't have that. Also, javap output is different, it indicates the id in the constant pool and as a comment, the name of the method being called. So it creates less confusion – LtWorf Mar 02 '13 at 10:21
  • @LtWorf I use a JDK 1.6, and above code is not actual result of javap. That result was from ASM eclipse plugin. And as you said, pop was omitted in above code. However, javap still produces same result for invokevirtual. – Hozard Mar 02 '13 at 13:29
  • Which javac version are you using? Or which non javac compiler? – LtWorf Mar 02 '13 at 14:23

3 Answers3

5

According to JVM specification p. 3.7:

The compiler does not know the internal layout of a class instance. Instead, it generates symbolic references to the methods of an instance, which are stored in the runtime constant pool. Those runtime constant pool items are resolved at runtime to determine the actual method location.

It means that all these symbolic Child.hashCode() are only constants, which are not specifying some kind of how JVM calls this methods. It seems, that for toString() method compiler knows, that this method has its base implementation in Object class, so it puts symbolic constant to Object class in constant pool - this is some kind of optimization, which makes compiler for JVM:

  Constant pool:
const #2 = Method   #24.#25;    //  java/lang/Object.toString:()Ljava/lang/String;
...
const #24 = class   #34;    //  java/lang/Object
const #25 = NameAndType #35:#36;//  toString:()Ljava/lang/String;
Andremoniy
  • 34,031
  • 20
  • 135
  • 241
  • Shouldn't the compiler give the same treatment to `hashCode`? It knows the same things about it as about `toString` and its performance is, if anything, even more critical. – Marko Topolnik Mar 02 '13 at 09:55
  • @MarkoTopolnik `hashCode` was overridden in `Parent` class, so this optimization doesn't work for this case. Compiler leaves this method's reference resolution for JVM – Andremoniy Mar 02 '13 at 10:16
  • No, method resolution will be exactly the same in both cases. `toString` may get an implementation in `Parent` without recompiling `Child` to reflect this, and the runtime must still work properly. – Marko Topolnik Mar 02 '13 at 10:40
4

You are correct that the compiler behaves unlogical. But the effect of this code is identical to the effect of both variants that you suggested. So this is likely not an intentional behaviour, but result of long evolution of the code of the compiler. Other compilers may produce different code.

Alexei Kaigorodov
  • 13,189
  • 1
  • 21
  • 38
  • Your answer doesn't covers the main question "Why this happens?". You only said, that: *yes, it happens, because it happens*. – Andremoniy Mar 02 '13 at 09:03
  • 1
    Actually, this answer comes closest to the truth. The end result may have no motivation whatsoever behind it; it just happens to be that way because it doesn't really matter. – Marko Topolnik Mar 02 '13 at 10:52
  • @Marko Topolnik I know that end result may be same for any cases. However I think there may be some reasons for designing invokevirtual in that way. For optimization, they may make invokevirtual like 'Object.toString()' or 'Parent.hashCode()' because in this way, JVM can direct reference actual method implementation without finding it. However, they did'nt do. I think there may be some reasons. – Hozard Mar 02 '13 at 13:41
  • @Hozard The JVM knows everything it needs to know about all the virtual methods; it needs no hints from the bytecode, and in fact ignores them. Imagine if it didn't ignore them, and you removed `hashCode` from `Parent` without recompiling `Child`. The resulting error would be a violation of the JLS. – Marko Topolnik Mar 02 '13 at 15:45
1

My theory: toString() is used very frequently, so javac use the common Object.toString() to save constant pool entries.

For example if the code contains foo.toString() and bar.toString(), the contant pool only needs one Object.toString, instead of two entries Foo.toString and Bar.toString

Javac probably hard coded this optimization, instead of analyzing code to see if it's really needed.

irreputable
  • 44,725
  • 9
  • 65
  • 93
  • then it could save constant pool entries for all methods of `Object` class, including `hashCode`. – Alexei Kaigorodov Mar 02 '13 at 17:43
  • hashCode() is not frequently invoked. while `toString()` invocation almost exists in every class. – irreputable Mar 02 '13 at 17:47
  • This sounds very convincing, +1 for such a nice hypothesis :) Yes, the ratio of `toString` call sites vs. `hashCode` call sites is quite wide. This could have been a targeted optimization driven by the assessment of actual constant pools. `toString` conflation could have been identified as a clear win, where the same was not identified for `hashCode`. BTW 'javac' is hardly in a position to call these shots; only the JVM has the whole classpath to assess. – Marko Topolnik Mar 02 '13 at 18:35