帮助理解受保护方法的问题

我正在阅读2005年4月的Sybex完整Java 2认证研究指南 (ISBN0782144195)。 本书适用于想要通过java认证的java开发人员。

在关于访问修饰符(以及其他修饰符)的章节之后,我发现了以下问题(#17):

对或错:如果类Y扩展了类X,那么这两个类在不同的包中,而类X有一个名为abby()的受保护方法,那么Y的任何实例都可以调用Y的任何其他实例的abby()方法。

这个问题让我有点困惑。

据我所知,你可以在同一个类(或子类)的任何变量上调用protected方法。 您不能在变量上调用它,层次结构中的变量比您更高(例如,您实现的接口)。

例如,您不能仅仅因为inheritance它而克隆任何对象。

但问题没有说明变量类型,只涉及实例类型。

我有点困惑,回答“真实”。

书中的答案是

假。 从不同包中的超类inheritance受保护方法的对象可以在自身上调用该方法,但不能在同一类的其他实例上调用该方法。

这里没有关于变量类型的内容,只有实例类型。

这很奇怪,我不明白。

谁能解释一下这里发生了什么?

对或错:如果类Y扩展了类X,那么这两个类在不同的包中,而类X有一个名为abby()的受保护方法,那么Y的任何实例都可以调用Y的任何其他实例的abby()方法。

“False。从不同包中的超类inheritance受保护方法的对象可以在自身上调用该方法,但不能在同一类的其他实例上调用”。

让我们写下来,就像BalusC所做的那样,并向Y添加一个调用Y的任何其他实例的abby()的方法:

package one; public class X { protected void abby() { } } package other; import one.X; public class Y extends X { public void callAbbyOf(Y anyOther) { anyOther.abby(); } } 

Y可以调用它具有引用的任何Y实例的abby()方法。 所以书中的答案显然是错误的。 Java没有特定于实例的范围(与例如具有实例私有范围的Scala不同)。

如果我们试图变得仁慈,也许这个问题意味着说“Y的任何其他实例”,它可以访问碰巧在内存中任何Y实例的方法 – 这是不可能的,因为Java没有直接的内存访问。 但在这种情况下,问题的措辞非常糟糕,您甚至可以回答:“错误。您无法在不同JVM上的实例上调用方法,或者在垃圾收集的实例上调用方法,或者JVM上的实例无法调用一年前等“

来自Java语言规范 :

6.6.2.1访问受保护的成员

设C是声明受保护成员m的类。 仅允许在C的子类S的主体内进行访问。此外,如果Id表示实例字段或实例方法,则:

  • 如果访问是通过限定名称Q.Id,其中Q是ExpressionName,则当且仅当表达式Q的类型是S或S的子类时才允许访问。
  • 如果访问是通过字段访问表达式E.Id,其中E是主表达式,或者是通过方法调用表达式E.Id(…),其中E是主表达式,那么只允许访问如果E的类型是S或S的子类。

因此受保护的成员可以在S的所有实例中访问,并且您的书中的答案是错误的。

这个问题似乎措辞严厉 – 并询问一个非常罕见的边缘情况(我甚至不确定在SCJP测试中有所涉及)。 措辞的方式使你的答案正确,给定的答案不正确。 编写类似的结构并轻松运行它certificate了这一点……

 package inside; public class Base { private String name; public Base(String name) { this.name = name; } public void setName(String name) { this.name = name; } public String getName() { return name; } protected String abby(String name) { String old = this.name; this.name = name; return old; } } package outside; import inside.Base; public class Another extends Base { public Another(String name) { super(name); } public String setAnother(Another another, String hack) { return another.abby(hack); } public static void doCrazyStuff() { Another one = new Another("one"); Another two = new Another("two"); one.abby("Hi one"); two.abby("Hi two"); one.setAnother(two, "Hi two from one"); System.out.println("one = " + one.getName()); System.out.println("two = " + two.getName()); } public static void main(String[] args) { Another.doCrazyStuff(); } } 

因为变量类型在这里是无关紧要的,直到它在问题的上下文中“理智”。 由于方法abby()属于X (并且Yinheritance它),因此声明引用Y实例的类型变量无关紧要:它可以是XYabby()可访问,我们可以通过两个变量调用它:

 X myY1 = new Y(); myY1.abby(); Y myY2 = new Y(); myY2.abby(); 

对或错:如果类Y扩展了类X,那么这两个类在不同的包中,而类X有一个名为abby()的受保护方法,那么Y的任何实例都可以调用Y的任何其他实例的abby()方法。

让我们描绘一下。

X级:

 package one; public class X { protected void abby() {} } 

Y级:

 package other; public class Y extends X {} 

测试用例:

 public static void main(String[] args) { Y y1 = new Y(); Y y2 = new Y(); Y y3 = new Y(); // ... } 

现在重读一下这个问题:你可以y1y2y3等上调用abby()吗? 在y1上调用abby()还会调用y2y3等吗?

对于未来的问题,尝试抓住笔和纸并按字面解释问题。 这类模拟问题有很多漏洞。

我几乎可以肯定这个问题意味着:

“Y的任何实例都可以调用X ”(不是Y)的任何其他实例的abbey()方法。

在那种情况下,它确实会失败。 借用上面另一个答案的例子,以下内容:

 package one; public class X { protected void abby() { } } package other; import one.X; public class Y extends X { public void callAbbyOf(X anyOther) { anyOther.abby(); } } 

将无法编译。

Java语言规范解释了原因: http : //java.sun.com/docs/books/jls/third_edition/html/names.html#6.6.2

6.6.2.1访问受保护的成员

设C是声明受保护成员m的类。 仅允许在C的子类S的主体内进行访问。此外,如果Id表示实例字段或实例方法,则:如果访问是通过限定名称Q.Id,其中Q是ExpressionName,则访问当且仅当表达式Q的类型是S或S的子类时才被允许。如果访问是通过字段访问表达式E.Id,其中E是主表达式,或者是通过方法调用表达式E.Id( ……),其中E是主表达式, 当且仅当E的类型是S或S的子类时才允许访问 (强调我的)。