6

The JVM spec (5.4.3.3) describes how method resolution is done for method refs. If it cannot find a method in a class or its superclasses, it tries to find the method in the superinterfaces.

What is the reason for this? Wouldn't a method declared by a superinterface be listed in the constant pool as an interface method ref instead of a method ref?

My understanding is that method refs are used in invokevirtual operations, whereas interface method refs are used in invokeinterface operations. I don't see how one could invoke an interface method using invokevirtual <methodref> .

MattDs17
  • 401
  • 1
  • 4
  • 20

1 Answers1

5

Why not?

You can invoke .stream() on an ArrayList<> just fine. In fact, the following snippet

ArrayList<Object> arr = new ArrayList<>();
arr.stream();

will be compiled to

     0: new           #16                 // class java/util/ArrayList
     3: dup
     4: invokespecial #18                 // Method java/util/ArrayList."<init>":()V
     7: astore_1
     8: aload_1
     9: invokevirtual #19                 // Method java/util/ArrayList.stream:()Ljava/util/stream/Stream;

But ArrayList<> (or any of it's superclasses) don't have a .stream() method.
The method implementation that is used is from the interface Collection:

default Stream<E> stream() {
    return StreamSupport.stream(spliterator(), false);
}

But if, at any point, ArrayList<> decides that it can provide a better .stream() method, then you don't want to compile your code again.
Also the implementation (or non-implementation) of the .stream() method would leak through, and it's better to avoid that.

Johannes Kuhn
  • 14,778
  • 4
  • 49
  • 73
  • 1
    I see, I think I misunderstood method ref versus interface method ref. Which one is used depends on whether the static type of the receiver is a class or interface, not whether the method is declared in a class or interface. – MattDs17 Apr 23 '20 at 19:44
  • 3
    @MattDs17 how would you even know at compile time whether the runtime class will have overridden the method or not? That’s a fundamental principle of Java that the compiler will search for inherited methods only for validating the method invocation and getting the correct signature, but encode the compile-time receiver type. JLS §13 lists a lot of changes which are allowed without breaking already compiled classes. The distinction between Methodref and InterfaceMethodref has become less meaningful than it was in the past, see [this answer](https://stackoverflow.com/a/34361753/2711488). – Holger Apr 24 '20 at 08:40
  • 3
    As far as I remember, early versions of java did look who had the method for a call like `super.toString()` and recorded that. For example, if we have `class MyClass extends MySuper` but `MySuper` doesn't override `.toString()` then `Object.toString` was recorded as target for the `invokespecial` instruction. But when `MySuper` changed and new overrides `.toString()` then early java versions would keep calling `Object.toString()`. To mitigate that, `ACC_SUPER` was introduced. – Johannes Kuhn Apr 24 '20 at 09:57
  • 2
    @JohannesKuhn indeed. This allowed handcrafted bytecode to skip overriding methods. The behavior was changed in version 1.0.2. But the backward compatibility to pre 1.0.2 classes was dropped as late as with Java 8. There’s an exception for `final` methods of class `java.lang.Object` (`getClass()`, `wait()`, `notify()`) which are compiled using `java.lang.Object` as receiver type though. – Holger Apr 24 '20 at 11:29
  • 1
    The classic example: `invokespecial Object.clone()` when your parent class is `java.lang.Thread`. Happy (pre-8) VM crashing. – Johannes Kuhn Apr 24 '20 at 16:39