从匿名内部类调用重写的默认方法

时间:2018-04-03 11:47:28

标签: java java-8 super anonymous-class default-method

考虑以下代码:

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

}

这是设计的,但基本上Test::doA()尝试将this包裹为B并让B::doB()调用其超级函数A.super.doA()

我可以在A.super.doA()类型的lambda中调用B就好了。但我无法弄清楚调用A.super.doA()的语法 在匿名B内。请参阅注释掉的代码。

有什么想法吗?

3 个答案:

答案 0 :(得分:2)

lambdas和匿名类中的代码区别对待

  

与匿名类声明中出现的代码不同,名称的含义以及出现在lambda主体中的thissuper关键字以及引用声明的可访问性, 与周围上下文相同(除了lambda参数引入新名称)。

     

lambda表达式主体中this(显式和隐式)的透明度 - 也就是说,将其视为与周围上下文相同 - 允许实现更灵活,并防止身体中不合格名称的含义不依赖于超载分辨率   实际上,lambda表达式需要谈论自身(要么递归地调用自身还是调用其他方法)是不寻常的,而更常见的是想要使用名称来引用封闭类中的内容。否则被遮蔽(this,toString())。如果lambda表达式需要引用它自己(就像通过this),则应该使用方法引用或匿名内部类。

     

JLS 10 - 15.27.2. Lambda Body

lambdas中的代码

  

只有在lambda表达式出现的上下文中允许使用关键字this时,才能在lambda表达式中使用关键字super。否则,发生编译时错误。

     

JLS 10 - 15.8.3. this

我认为它也可以应用于关键字A.super.doA();

语句Test#doA将在封闭的上下文(方法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(); // ... } } 的主体)中工作,因此也允许在lambdas中使用。

class Test
[interface A]

此代码段打印

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

}

我们会将它与匿名类结果进行比较。

匿名类中的代码

class Test$1
[interface B]

代码段输出

this

请记住,匿名类拥有superA并且它不会继承A.super.doA();(并且无法执行),很明显{{1} 1}}无法在其上下文中进行编译。

变通方法

解决方法可能是通过lambda记住封闭的上下文,并在匿名类的方法中调用该lambda:

class Test implements A {

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

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

}

如果B继承A,则可以使用默认方法调用doA()B.super.doA()

class Test implements A {

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

}

答案 1 :(得分:2)

this answer中所述,由于thissuper的含义不同(与内部类相比),因此在lambda表达式中这是可能的。

The Java® Language Specification, §15.12.1明确地解决了对内部类做同样事情的不可能性:

  

TypeName . super语法被重载:传统上, TypeName 是指一个词汇封闭类型声明,它是一个类,并且target是此类的超类,就好像在词法封闭类型声明中调用是非限定super一样。

     

...

     

为了支持在超接口中调用默认方法, TypeName 也可以引用当前类或接口的直接超接口,目标是超接口。

     

...

     

没有语法支持这些表单的组合,即调用词法封闭类型声明的超接口方法,这是一个类,就好像调用的形式是 InterfaceName {{1}词法封闭类型声明中的.

super
     

解决方法是在词法封闭类型声明中引入class Subclass3 implements Superinterface { void foo() { throw new UnsupportedOperationException(); } Runnable tweak = new Runnable() { void run() { Subclass3.Superinterface.super.foo(); // Illegal } }; } 方法,该方法执行接口private调用。

答案 2 :(得分:1)

我不认为这是可能的。

这一个:

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

或者这个:

A.super.doA();

是有效的,因为这些语句使用A实例方法作为上下文。

从匿名类中,由于您无权访问A实例上下文,因此情况有所不同。 因此无法引用A 匿名类中的方法可以引用封闭方法的final变量或封闭方法的实例(通过前缀classname.this),但该方法的行为不能像在{的上下文中执行一样{1}}实例方法:A的含义。

我认为JLS的一部分必须指明这一点。