Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling Overridden Default Method from Anonymous Inner Class

Consider this code:

interface A {
    default void doA() {
        System.out.println("a");
    } 
}

interface B {
    void doB(); 
}

class Test implements A {

    @Override
    public void doA() {        
        // Works
        B b = () -> A.super.doA();
        b.doB();

        // Does not compile
        /*
        new B() {      
            public void doB() {  
                A.super.doA();
            }       
        }.doB();
        */
    }

    public static void main(String[] args) {
        new Test().doA();
    }

}

This is contrived, but basically Test::doA() tries to wrap this as a B and have B::doB() call its super function A.super.doA().

I can call A.super.doA() in a lambda of type B just fine. But I cannot figure out the syntax of calling A.super.doA() inside an anonymous B. See the commented out code.

Any ideas?

like image 471
Alexander Torstling Avatar asked Apr 03 '18 11:04

Alexander Torstling


People also ask

Can an anonymous inner class contain static methods?

Anonymous classes also have the same restrictions as local classes with respect to their members: You cannot declare static initializers or member interfaces in an anonymous class. An anonymous class can have static members provided that they are constant variables.

Can we override method in inner class?

No, you cannot override private methods in Java, private methods are non-virtual in Java and access differently than non-private one. Since method overriding can only be done on derived class and private methods are not accessible in a subclass, you just can not override them.

How do you use an anonymous inner class?

An anonymous inner class can be useful when making an instance of an object with certain “extras” such as overriding methods of a class or interface, without having to actually subclass a class. Tip: Anonymous inner classes are useful in writing implementation classes for listener interfaces in graphics programming.

Can inner class instantiate anonymously?

An anonymous inner class can be useful when making an instance of an object with certain "extras" such as overloading methods of a class or interface, without having to actually subclass a class. In simple words, a class that has no name is known as an anonymous inner class in Java.


2 Answers

As said in this answer, this is possible in lambda expressions due to the different meaning of this and super (compared to inner classes).

The impossibility to do the same with inner classes has been addressed in The Java® Language Specification, §15.12.1 explicitly:

The TypeName . super syntax is overloaded: traditionally, the TypeName refers to a lexically enclosing type declaration which is a class, and the target is the superclass of this class, as if the invocation were an unqualified super in the lexically enclosing type declaration.

To support invocation of default methods in superinterfaces, the TypeName may also refer to a direct superinterface of the current class or interface, and the target is that superinterface.

No syntax supports a combination of these forms, that is, invoking a superinterface method of a lexically enclosing type declaration which is a class, as if the invocation were of the form InterfaceName . super in the lexically enclosing type declaration.

class Subclass3 implements Superinterface {
    void foo() { throw new UnsupportedOperationException(); }

    Runnable tweak = new Runnable() {
        void run() {
            Subclass3.Superinterface.super.foo();  // Illegal
        }
    };
}

A workaround is to introduce a private method in the lexically enclosing type declaration, that performs the interface super call.

like image 133
Holger Avatar answered Sep 30 '22 03:09

Holger


Code in lambdas and anonymous classes is treated differently

Unlike code appearing in anonymous class declarations, the meaning of names and the this and super keywords appearing in a lambda body, along with the accessibility of referenced declarations, are the same as in the surrounding context (except that lambda parameters introduce new names).

The transparency of this (both explicit and implicit) in the body of a lambda expression - that is, treating it the same as in the surrounding context - allows more flexibility for implementations, and prevents the meaning of unqualified names in the body from being dependent on overload resolution.
Practically speaking, it is unusual for a lambda expression to need to talk about itself (either to call itself recursively or to invoke its other methods), while it is more common to want to use names to refer to things in the enclosing class that would otherwise be shadowed (this, toString()). If it is necessary for a lambda expression to refer to itself (as if via this), a method reference or an anonymous inner class should be used instead.

JLS 10 - 15.27.2. Lambda Body

Code in lambdas

The keyword this may be used in a lambda expression only if it is allowed in the context in which the lambda expression appears. Otherwise, a compile-time error occurs.

JLS 10 - 15.8.3. this

I think it also can be applied to the keyword super.

The statement A.super.doA(); would be working in the enclosing context (the body of the method Test#doA), so it is allowed in lambdas as well.

class Test implements A {

    @Override
    public void doA() {
        B b = () -> {
            System.out.println(super.getClass());
            System.out.println(Arrays.toString(super.getClass().getInterfaces()));
        };
        b.doB();
    
        // ...
    }

}

This snippet prints

class Test
[interface A]

We will compare it with the anonymous class result.

Code in anonymous classes

class Test implements A {

    @Override
    public void doA() {
        // ...
        
        new B() {
            public void doB() {
                System.out.println(super.getClass());
                System.out.println(Arrays.toString(super.getClass().getInterfaces()));
            }
        }.doB();
    }

}

The snippet outputs

class Test$1
[interface B]

Keeping in mind that an anonymous class has own this and super and it does not inherit A (and can't do it), it becomes clear that A.super.doA(); can't be compiled in its context.

Workarounds

A workaround could be remembering the enclosing context by a lambda, and invoking that lambda in the method of an anonymous class:

class Test implements A {

    @Override
    public void doA() { 
        Runnable doA = () -> A.super.doA();

        new B() {
            public void doB() {
                doA.run();
            }
        }.doB();
    }

}

If B inherited A, it would be possible to call doA() or B.super.doA() referring to the default method:

class Test implements A {

    @Override
    public void doA() {
        new B() {
            public void doB() {
                doA(); // or B.super.doA();
            }
        }.doB();
    }

}
like image 40
Andrew Tobilko Avatar answered Sep 30 '22 03:09

Andrew Tobilko