Calling a Superclass’ Method

A user on #java asked how one might invoke a method of a superclass recently, and received some general answers that more or less were right, but weren’t technically correct.

Maldivia pointed out that you would use method handles, which is sort of the gist of the (incorrect) answers given. The attempt was made to invoke the method via getDeclaredMethod(), which does not work.

The way to do it is to use MethodHandles.lookup() to get, well, a MethodHandles.Lookup object, which provides a way to get methods in the context of a class.

First, you’d get a MethodType reference, that refers to the _return value_ of the method. Then, you’d use findSpecial() – as one likely possibility – to find a method by name in a given class.

With that, you’d be able to invoke() the method.

Here’s code showing a superclass – Drink – and a main() in a subclass, Coffee, that has a bad call (the suggestion from the channel, tried and failing) and a good call, based on Maldivia’s suggestion, that actually does invoke Drink.toString() even when called from the context of a Coffee instance.

This is not expected to be held as an example of “what you should do” – for one thing, calling superclass’ methods outside of explicit access from a subclass is terrible design, in most cases, and for another, this is an example designed to run and not run properly or exhaustively. It’s simply a starting point.

import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;

class Drink {
    public String toString() {
        return "This is a Drink!";
    }

}

public class Coffee extends Drink {
    public String toString() {
        return "This is a Coffee!";
    }

    static void badCall(Coffee coffee) throws Throwable {
        var method = coffee
                .getClass()
                .getSuperclass()
                .getDeclaredMethod("toString", null);
        System.out.println(method);
        System.out.println(method.invoke(coffee, null));
        System.out.println(coffee);
    }

    static void goodCall(Coffee coffee) throws Throwable {
        MethodHandles.Lookup lookup = MethodHandles.lookup();
        MethodType type = MethodType.methodType(String.class);
        var m = lookup.findSpecial(coffee
                        .getClass()
                        .getSuperclass(),
                "toString",
                type,
                coffee.getClass()
        );
        System.out.println(m);
        System.out.println(m.invoke(coffee));
    }

    public static void main(String[] args) throws Throwable {
        Coffee coffee = new Coffee();
        badCall(coffee);
        goodCall(coffee);
    }
}