私有接口方法的方法参考

And*_*niy 24 java language-lawyer java-9

请考虑以下代码:

public class A {
    public static void main(String[] args) {
        Runnable test1 = ((I)(new I() {}))::test;  // compiles OK
        Runnable test2 = ((new I() {}))::test;     // won't compile 
    }

    interface I {
        private void test() {}
    }
}
Run Code Online (Sandbox Code Playgroud)

我真的不明白......我明白这种test()方法是私密的.但是,如果我们将匿名类强制转换为其接口,会发生什么变化((I)(new I() {}))?更确切地说,我希望看到一个允许该技巧的特定JLS点.

PS我已将其报告为编译器的错误(ID:9052217).在我看来,Runnable test2 = ((new I() {}))::test;在这种特殊情况下应该编译得很好.

PPS到目前为止,根据我的报告创建了一个错误:https://bugs.openjdk.java.net/browse/JDK-8194998.它可能会被关闭为"不会修复"或者什么.

And*_*eas 28

这不是一个新问题,与私有接口方法或方法引用无关.

如果您更改代码以扩展类而不是实现接口,并调用方法而不是引用它,您仍然会得到完全相同的问题.

class A {
    public static void main(String[] args) {
        ((I)(new I() {})).test();  // compiles OK
        ((new I() {})).test();     // won't compile 
    }

    class I {
        private void test() {}
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,该代码可以应用于较旧的Java版本,我尝试了Java 9,8,7,6,5和1.4.一切都表现得一样!!

问题是私有方法没有被继承1,所以匿名类根本就没有这个方法.由于私有方法甚至不存在于匿名类中,因此无法调用它.

当你转换为I,现在存在供编译器查看的方法,并且由于它I是一个内部类,你被授予访问权限(通过合成方法),即使它是私有的.

在我看来,这不是一个错误.这是私有方法在继承的上下文中的工作方式.

1)由Jorn VerneeJLS 6.6-5中发现:"[私有类成员]不是由子类继承的".

  • @FedericoPeraltaSchaffner想象一下你是编译器,程序员要求在某个表达式上调用`test`.你查找表达式的类型,打开它的方法列表,并且在那里找不到`test`(因为它没有被继承).但是,随着强制转换,表达式的类型会发生变化,并且由于这一点,您获得的方法列表会发生变化,现在您可以在那里找到`test`. (5认同)
  • @FedericoPeraltaSchaffner*澄清:*只有当类型是声明方法的地方时,编译器*才会看到它,因为私有方法在所有其他类型上是不可见的.*访问方法是一个单独的东西,即如果你移出界面,成为顶级界面,那么编译器仍会看到它与强制转换,但你现在不再拥有访问权限了.就JVM而言,即使接口是内部类型,您也无法访问,因此编译器通过创建隐藏的合成非专用桥接方法伪装访问私有方法,并调用它. (5认同)
  • @Andremoniy为什么会这样?接口中的私有方法是供接口使用的,不是其他人.通过创建一个内部接口来传播该规则,所以顶级类具有访问权限,实际上是一个技巧,而且在我看来是糟糕的代码. (4认同)
  • 我想我明白发生了什么,但是,我发现这里有一个矛盾:*由于私有方法甚至不存在于匿名类中,因此无法调用*VS*当你转换为I时,该方法现在存在于编译器看*.无论是否为强制转换,实例的实际类型都是不可表示的匿名类型.所以,如果方法被执行,那么它就在那里. (4认同)
  • 这个规则的一个结果是,不可能在类型参数的引用上调用`private`方法.这是如此不直观,以至于编译器弄错了Java 5(即使在Java 6中也是Eclipse),并且在将源级别设置为这些旧版本时仍保持错误的行为,以实现兼容性. (2认同)

Jor*_*nee 20

private方法不是继承的(到目前为止我发现的最近的是:JLS6.6-5:"[私有类成员]不是由子类继承的").这意味着您无法从子类型访问私有方法(因为它根本没有"拥有"该方法).例如:

public static void main(String[] args) {
    I1 i1 = null;
    I2 i2 = null;

    i1.test(); // works
    i2.test(); // method test is undefined
}

interface I1 {
    private void test() {}
}

interface I2 extends I1 {}
Run Code Online (Sandbox Code Playgroud)

这也意味着您无法test通过匿名子类的类型直接访问该方法.表达式的类型:

(new I() {})
Run Code Online (Sandbox Code Playgroud)

不是I,但实际上是匿名子类的非可表示类型,因此您无法test通过它进行访问.

但是,表达式的类型:

((I) (new I() {}))
Run Code Online (Sandbox Code Playgroud)

I(当您明确地将其转换为I)时,您可以test通过它访问该方法.(就像你((I1) i2).test();在上面的例子中所做的那样)

类似的规则适用于static方法,因为它们也不是继承的.

  • 不错的JLS参考.+1.在我们之间(见[我的回答](/sf/answers/3375112681/)),我想我们已经涵盖了这一点. (4认同)

Eug*_*ene 6

这是违反直觉的.首先让我们简化一下:

static interface Inter {
    private void test() {
        System.out.println("test");
    }
}


public static void main(String[] args) {
    ((Inter) new Inter() {
    }).hashCode();
}
Run Code Online (Sandbox Code Playgroud)

这是有道理的,因为您正在调用公共hashCode方法,这里是它的(仅限重要部分)字节代码:

public static void main(java.lang.String[]);
Code:
   0: new           #2   // class com/test/DeleteMe$1
   3: dup
   4: invokespecial #3   // Method com/test/DeleteMe$1."<init>":()V
   7: invokevirtual #4   // Method java/lang/Object.hashCode:()I
  10: pop
  11: return
Run Code Online (Sandbox Code Playgroud)

看起来非常理智.现在让我们改变它来调用test():

public static void main(String[] args) {
    ((Inter) new Inter() {
    }).test();
}
Run Code Online (Sandbox Code Playgroud)

这个字节代码:

 invokestatic  #4  // InterfaceMethod com/test/DeleteMe$Inter.access$000:(Lcom/test/DeleteMe$Inter;)V
Run Code Online (Sandbox Code Playgroud)

由于私有方法不是继承的,因此您实际上是通过access$n静态合成方法"转向"该方法.


Hol*_*ger 6

private无论方案如何,只能通过完全声明类型的表达式调用方法.

让我们用最简单的例子来解释它

public class A {
    public static void main(String[] args) {
        B b = new B();
        b.someMethod(); // does not compile
        A a = b;
        a.someMethod(); // no problem
    }
    private void someMethod() {}
}
class B extends A {
}
Run Code Online (Sandbox Code Playgroud)

您可能希望使用此编译b.someMethod()调用AsomeMethod().但如果B被宣布为

class B extends A {
    public void someMethod() {}
}
Run Code Online (Sandbox Code Playgroud)

这是可能的,因为private void someMethod()它不是继承的,所以public void someMethod()不会覆盖它.但应该很清楚,现在b.someMethod()应该调用B方法.

因此,如果允许b.someMethod()以一种private方法结束A,那将取决于是否B声明另一个someMethod(),在哪个实际方法中调用将结束.这显然与整个private方法概念相矛盾.private方法不是继承的,也不会被覆盖,因此它不应该依赖于子类,无论调用是以private方法还是子类的方法结束.

你的例子很相似.实现的匿名内部类I可以声明它自己的test()方法,例如Runnable test2 = ((new I() {void test() {}}))::test;,它将依赖于匿名内部类,无论是匿名内部类的private方法I还是方法,都是不可接受的.当然,对于这样一个内部类,直接在调用或方法引用之前,读者可以立即告诉调用将以哪种方式结束,但如果允许匿名内部类,则它将非常不一致其他.

所述private的方法I是可访问的A,因为它是一个嵌套接口,但如图所示与上面的简单例子中,规则是不有关辅助,作为规则,即使适用于当private方法是相同的类作为呼叫者内.