`
anglefly
  • 浏览: 209898 次
  • 性别: Icon_minigender_2
  • 来自: 北京
社区版块
存档分类
最新评论

The Ultimate Java Puzzler

阅读更多

Why is this particular one the ultimate? Two reasons:

  • It’s at the very core of the Java language, not some obscure piece of API.
  • It melted my brain when I hit it.

UPDATE 2: If you want to test yourself before reading the post take this test . Results are not saved (it’s a paid feature apparently and I just don’t care enough), but you can post them in the comments.

Let’s start by setting up the puzzler environment. We’ll have three classes in two packages. Classes C1 and C2 will be in package p1 :

JAVA:
  1. package p1;
  2. public class C1 {
  3.   public int m( ) { return 1 ;}
  4. }
  5. public class C2 extends C1 {
  6.   public int m( ) { return 2 ;}
  7. }

Class C3 will be in a separate package p2 :

JAVA:
  1. package p2;
  2. public class C3 extends p1.C2 {
  3.   public int m( ) { return 3 ;}
  4. }

We will also have the test class p1.Main with the following main method:

JAVA:
  1. public static void main( String [ ] args) {
  2.   C1 c = new p2.C3 ( ) ;
  3.   System .out .println ( c.m ( ) ) ;
  4. }

Note that we’re calling the method of C1 on an instance of C3 . The output for this example is “3″ as you’d expect. Now let’s change the m() visibility in all three classes to default:

JAVA:
  1. public class C1 {
  2.   /*default*/ int m( ) { return 1 ;}
  3. }
  4. public class C2 extends C1 {
  5.   /*default*/ int m( ) { return 2 ;}
  6. }
  7. public class C3 extends p1.C2 {
  8.   /*default*/ int m( ) { return 3 ;}
  9. }

The output will now be “2″!

Why is that? The Main class that invokes the method does not see the m() method in the C3 class, it being in a separate package. As far as it cares the chain ends with C2 . But as C2 is in the same package it overrides the m() method in C1 . This does not seem too intuitive, but that’s the way it is.

Now let’s try something different, let’s change the modifier of C3.m() back to public . What will that do?

JAVA:
  1. public class C1 {
  2.   /*default*/ int m( ) { return 1 ;}
  3. }
  4. public class C2 extends C1 {
  5.   /*default*/ int m( ) { return 2 ;}
  6. }
  7. public class C3 extends p1.C2 {
  8.   public int m( ) { return 3 ;}
  9. }

Now Main can clearly see the C3.m() method. But amazingly enough output is still “2″!

Apparently C3.m() is not considered to override C2.m() at all. One way to think about it is overriding methods should have access to the super methods (via super.m() ). However in this case C3.m() wouldn’t have access to its super method, as it it not visible to it, being in another package. Therefore C3 is considered to be in a completely different invocation chain from C1 and C2 . Were we to call C3.m() directly from Main the output would actually be “3″.

Now let’s look at one last example. Protected is an interesting visibility. It behaves like default for members in the same package and like public for subclasses. What will happen if we change all of the visibilities to protected?

JAVA:
  1. public class C1 {
  2.   protected int m( ) { return 1 ;}
  3. }
  4. public class C2 extends C1 {
  5.   protected int m( ) { return 2 ;}
  6. }
  7. public class C3 extends p1.C2 {
  8.   protected int m( ) { return 3 ;}
  9. }

My reasoning goes like this: as Main is not a subclass of any classes protected should behave as default in this case and output should be “2″. However that is not the case. The crucial thing is that C3.m() has access to super.m() and thus the actual output will be “3″.

Personally, when I first encountered this accessibility issue I got thoroughly confused and couldn’t get it until I did all of this examples through. The intuition I got from this is that if and only if you can access super.m() the subclass is a part of the invocation chain.

UPDATE: Apparently even though the whole thing is obvious to anyone, the intuition I came up with was wrong. A mysterious commenter know only as “C” has provided the following example:

JAVA:
  1. public class C1 {
  2.   /*default*/ int m( ) { return 1 ;}
  3. }
  4. public class C2 extends C1 {
  5.   /*default*/ int m( ) { return 2 ;}
  6. }
  7. public class C3 extends p1.C2 {
  8.   /*default*/ int m( ) { return 3 ;}
  9. }
  10. public class C4 extends p2.C3 {
  11.   /*default*/ int m( ) { return 4 ;}
  12. }

Note that C4 is in the package p1 . If we now change the Main code as follows:

JAVA:
  1. public static void main( String [ ] args) {
  2.   C1 c = new C4( ) ;
  3.   System .out .println ( c.m ( ) ) ;
  4. }

Then it will output “4″. However super.m() is not accessible from C4 and putting @Override on the C4.m() method will stop the code from compiling. At the same time if we change the main method to:

JAVA:
  1. public static void main( String [ ] args) {
  2.   p2.C3 c = new C4( ) ;
  3.   System .out .println ( c.m ( ) ) ;
  4. }

The output will be “3″. This means that C4.m() overrides C2.m() and C1.m() , but not C3.m() . This also makes the issue even more confusing, and the amended intuition is that a method in a subclass overrides a method in a superclass if and only if the method in the superclass is accessible from the subclass . Here superclass can be any ancestor, not necessarily the direct parent and the relation has to be transitive.

For the kicker try reading all of this out from the JVM specification that selects the method to be invoked:


Let C be the class of objectref. The actual method to be invoked is selected by the following lookup procedure:

  • If C contains a declaration for an instance method with the same name and descriptor as the resolved method, and the resolved method is accessible from C, then this is the method to be invoked, and the lookup procedure terminates.
  • Otherwise, if C has a superclass, this same lookup procedure is performed recursively using the direct superclass of C; the method to be invoked is the result of the recursive invocation of this lookup procedure.
  • Otherwise, an AbstractMethodError is raised.
分享到:
评论

相关推荐

    java puzzler (java谜题)

    《Java Puzzler》是Java开发者的一本独特指南,它以一种有趣且富有挑战性的方式揭示了语言中的陷阱和易犯错误。这本书的核心理念是通过一系列精心设计的谜题,帮助读者深入理解Java语言的微妙之处,从而提高编程技能...

    Javapuzzler+JAVA解惑 中英双语 完整源代码

    《Javapuzzler+JAVA解惑 中英双语 完整源代码》是一份集学习与实践于一体的Java编程资源,包含《Java Puzzlers》和《JAVA解惑》两部分,以及对应的完整源代码。这份资料对于深入理解Java语言的特性和陷阱,提升编程...

    JavaPuzzler.pdf

    不错的好东西 一个个小题目 非常关注细节

    Java解惑(javapuzzler.chm)

    Java的优秀图书,欢迎下载。

    java puzzler code

    Java Puzzlers是Java编程中的一系列巧妙问题,旨在揭示语言中的陷阱、误导性和意外行为。这些谜题通常由简单的代码片段构成,看似无害,但执行结果却往往出乎意料。通过解决这些Puzzlers,开发者可以增强对Java语言...

    java-puzzler:学习Java Puzzler

    《Java Puzzler》是一本深受Java开发者喜爱的书籍,它揭示了Java语言中的一些微妙陷阱和不明显的特性,帮助程序员避免在实际编程过程中遇到的困惑。这些“Puzzlers”通常是由看似简单但实则暗藏玄机的代码片段构成,...

    Java解惑.中文完整版

    This lively book reveals oddities of the Java programming language through entertaining and thought-provoking programming puzzles." --Guy Steele, Sun Fellow and coauthor of The Java(t) Language ...

    java puzzle

    ### Java Puzzler: The Joy of Sets #### Introduction 在探讨Java编程中可能出现的各种奇怪行为时,"Java Puzzlers"系列提供了一系列短小精悍的示例代码,旨在揭示那些初看之下难以理解的行为背后的原因。这些...

    java puzzlers code

    例如,一个经典的Java Puzzler是关于变量初始化的。在Java中,局部变量必须先初始化后才能使用,但有时候编译器可能会做出一些你意想不到的优化。如果在函数内部声明并初始化一个变量,然后在后续的代码块中再次初始...

    Java解惑PPT8

    Java编程语言中有许多微妙而有趣的细节,这些细节可能会在开发过程中造成困扰,这就是"Java解惑"系列试图解决的问题。本文将深入探讨PPT8中提及的三个Java谜题:Puzzle 76 乒乓、Puzzle 77 搞乱锁的妖怪和Puzzle 78 ...

    Puzzler.jar

    Puzzler.jar

    Java+Puzzlers(中英文并且带源码)

    每个Puzzler都是一次深入学习的机会,通过解决这些谜题,开发者不仅可以提升对Java语言的理解,还能增强代码调试和问题解决的能力。书中附带的源代码可以方便读者直接运行和测试,加深对知识点的理解。无论你是Java...

    java Puzzlers 中文版带完整目录

    本書包含了Java程式語言和核心函式庫中的各種謎題,任何具備使用Java經驗的讀者都可以看得懂,但是書裡有不少謎題的難度頗高,即便是對經驗豐富的Java程式設計師而言,都是一項挑戰,所以如果你解不出來,別覺得難過...

    Java puzzlers(java 解惑)附源码

    例如,puzzler之一可能涉及自动装箱和拆箱,当Integer对象与int基本类型进行操作时,如果不理解它们之间的关系,可能会出现意料之外的结果。源码中可能包含这样的例子,展示了如何正确处理这种情况,避免隐式转换...

    <好书>java解惑(java puzzlers),过来挑战吧

    每个puzzler都会引导读者思考为什么这段代码会以某种意想不到的方式运行,然后提供详细的解答,解释背后的语言原理。这些谜题涵盖了从基本语法到高级特性的各种主题,包括但不限于类型转换、对象引用、内存管理、多...

    Java解惑PPT7

    Java编程中的迷惑问题,或者称为Puzzlers,是学习Java时常常遇到的陷阱和微妙之处。这些Puzzlers有助于深入理解Java的语法规则和运行机制。以下将详细解析给出的Puzzles。 首先,我们来看Puzzle 66:一件私事。这个...

    Android代码-kotlin-puzzlers

    A puzzler is some code that doesn't work the way it seems it should work. An ideal programming language won't have any. The puzzlers here are presented as kts files (Kotlin Scripts), which can skip ...

    Java解惑ppt5

    《Java解惑PPT5》深入探讨了Java编程中的一些常见困惑和陷阱,特别是关于类、构造器重载以及静态域的使用。以下是对其中两个关键Puzzle的详细解析: **Puzzle 46:令人混淆的构造器** 在这个谜题中,作者展示了...

    Java解惑 PPT1

    【Java解惑PPT1】深入探讨Java编程中的常见迷惑 在Java编程中,了解语言的细微之处至关重要,因为这可能会导致意想不到的行为。本PPT主要涵盖了五个有趣的Java谜题,帮助开发者理解Java的一些核心概念。 **谜题1:...

    Java解惑PPT6

    在Java编程中,有一些微妙而重要的知识点,这些知识点在实际编程中可能会引起混淆或错误。在"Java解惑PPT6"中,我们探讨了几个关键的Java特性,特别是关于不变性、equals()和hashCode()方法的约定以及它们在HashSet...

Global site tag (gtag.js) - Google Analytics