Calling the default override method from an 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 far-fetched, but basically Test::doA() tries to wrap this as B and call B::doB() its superfunction A.super.doA() .

I can call A.super.doA() in type B lambda just fine. But I can not understand the syntax of calling A.super.doA() inside anonymous B See Commented Code.

Any ideas?

+5
source share
3 answers

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

The inability to do the same with inner classes in the Javaยฎ Language Specification, ยง15.12.1 explicitly:

Syntax TypeName . super overloaded: traditionally, TypeName refers to a lexically-encompassing type declaration, which is a class, and the target is a superclass of that class, as if the call were an unqualified super in a declaration of a lexically enclosed type.

...

To support the default method invocation in superinterfaces, TypeName can also refer to the direct superinterface of the current class or interface, and the target is the superinterface.

...

No syntax supports a combination of these forms, that is, it calls the superinterface method of a lexically spanning type declaration, which is a class, as if the call had the form InterfaceName . super in a lexically closing type declaration.

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

The workaround is to introduce the private method in a declaration of type lexically enclosing, which makes a call to the super interface.

+2
source

Code in lambdas and anonymous classes handled differently

Unlike the code that appears in anonymous class declarations, the meaning of the this and super names and keywords included in the lambda body, as well as the availability of link declarations, are the same as in the surrounding context (except that the lambda parameters introduce new names) .

The transparency of this (both explicit and implicit) in the body of the lambda expression - that is, considering it in the same way as in the surrounding context - provides greater flexibility for implementations and prevents meaning unqualified names in the body depend on overload resolution. In fact, itโ€™s unusual that a lambda expression has to talk about itself (either call itself recursively or refer to other methods), while the more common desire is to use names to indicate things in the covering class that would otherwise obscure (this, toString () ) If you want the lambda expression to refer to itself (as if through this ), you should instead use a reference to a method or an anonymous inner class.

JLS 10 - 15.27.2. Lambda body

Code in lambdas

The this can 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 can also be applied to the super keyword.

Operator A.super.doA(); will work in an encompassing context (the body of the Test#doA ), so it is also allowed in lambdas.

 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 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(); } } 

Fragmentation

 class Test$1 [interface B] 

Remembering that the anonymous class has its own this and super , and it does not inherit A (and cannot do this), it becomes clear that A.super.doA(); cannot be compiled into its context.

Bypass

A workaround is to remember the storage context with a lambda and call that lambda in an anonymous class method:

 class Test implements A { @Override public void doA() { Runnable doA = () -> A.super.doA(); new B() { public void doB() { doA.run(); } }.doB(); } } 

If B inherits A , one could call doA() or B.super.doA() , invoking the default method:

 class Test implements A { @Override public void doA() { new B() { public void doB() { doA(); // or B.super.doA(); } }.doB(); } } 
+2
source

I do not think that's possible.

This:

 B b = () -> A.super.doA(); 

or this one:

 A.super.doA(); 

valid because these operators use instance method A as context.

From an anonymous class, everything is different since you do not have access to the context of instance A So A cannot be referenced.
A method in an anonymous class can refer to final variables of the environment method or an instance of the environment method (by prefixing classname.this ), but the method cannot behave as if it were executed in the context of the instance method A : which means A.super.doA()

I think that part of JLS should indicate this point.

+1
source

Source: https://habr.com/ru/post/1276229/


All Articles