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:
-
package p1;
-
public
class
C1 {
-
public
int
m(
)
{
return
1
;}
-
}
-
public
class
C2 extends
C1 {
-
public
int
m(
)
{
return
2
;}
-
}
Class C3
will be in a separate package p2
:
JAVA:
-
package p2;
-
public
class
C3 extends
p1.C2
{
-
public
int
m(
)
{
return
3
;}
-
}
We will also have the test class p1.Main
with the following main
method:
JAVA:
-
public
static
void
main
(
String
[
]
args
)
{
-
C1 c = new
p2.C3
(
)
;
-
System
.
out
.
println
(
c.
m
(
)
)
;
-
}
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:
-
public
class
C1 {
-
/*default*/
int
m(
)
{
return
1
;}
-
}
-
public
class
C2 extends
C1 {
-
/*default*/
int
m(
)
{
return
2
;}
-
}
-
public
class
C3 extends
p1.C2
{
-
/*default*/
int
m(
)
{
return
3
;}
-
}
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:
-
public
class
C1 {
-
/*default*/
int
m(
)
{
return
1
;}
-
}
-
public
class
C2 extends
C1 {
-
/*default*/
int
m(
)
{
return
2
;}
-
}
-
public
class
C3 extends
p1.C2
{
-
public
int
m(
)
{
return
3
;}
-
}
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:
-
public
class
C1 {
-
protected
int
m(
)
{
return
1
;}
-
}
-
public
class
C2 extends
C1 {
-
protected
int
m(
)
{
return
2
;}
-
}
-
public
class
C3 extends
p1.C2
{
-
protected
int
m(
)
{
return
3
;}
-
}
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:
-
public
class
C1 {
-
/*default*/
int
m(
)
{
return
1
;}
-
}
-
public
class
C2 extends
C1 {
-
/*default*/
int
m(
)
{
return
2
;}
-
}
-
public
class
C3 extends
p1.C2
{
-
/*default*/
int
m(
)
{
return
3
;}
-
}
-
public
class
C4 extends
p2.C3
{
-
/*default*/
int
m(
)
{
return
4
;}
-
}
Note that C4
is in the package p1
. If we now change the Main
code as follows:
JAVA:
-
public
static
void
main
(
String
[
]
args
)
{
-
C1 c = new
C4(
)
;
-
System
.
out
.
println
(
c.
m
(
)
)
;
-
}
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:
-
public
static
void
main
(
String
[
]
args
)
{
-
p2.C3
c = new
C4(
)
;
-
System
.
out
.
println
(
c.
m
(
)
)
;
-
}
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 puzzle,细小之处见真功。 有chm,doc,pdf三个版本,包括中英文版
Java puzzler 解惑 中英双语 完整源代码 不错的书,下了包你不后悔。
Java的优秀图书,欢迎下载。
java-puzzler 学习Java Puzzler
java解惑的源代码。 里面的例子都是极简单的,但是稍不注意就会犯错。看一下,对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 ...
Puzzler.jar
本書包含了Java程式語言和核心函式庫中的各種謎題,任何具備使用Java經驗的讀者都可以看得懂,但是書裡有不少謎題的難度頗高,即便是對經驗豐富的Java程式設計師而言,都是一項挑戰,所以如果你解不出來,別覺得難過...
Java解惑是本很不错的书,讲解了Java中一些很有趣的东西
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解惑的PPT1 Java解惑是本很不错的书,讲解了Java中一些很有趣的东西
Java解惑是本很不错的书,讲解了Java中一些很有趣的东西
Java解惑是本很不错的书,讲解了Java中一些很有趣的东西
Java解惑是本很不错的书,讲解了Java中一些很有趣的东西
Java解惑是本很不错的书,讲解了Java中一些很有趣的东西
It details the creation of four games, all in different styles, from start to finish using the Microsoft XNA Framework, including a puzzler, space shooter, multi-axis shoot-’em-up, and a jump-and-...
来自Little Puzzler 实现
This was a bit of a puzzler, not least because what I really wanted to be was a computer game programmer (okay, okay, astronaut) and there was nobody in my immediate environment who had any idea what ...
益智游戏 Puzzler是一款面向新VR用户的移动VR游戏,它要求他们在新的独特但舒适的环境中解决熟悉的谜题类型。 有关详细信息,请在查看演练! 在下载构建 屏幕截图 制作说明 使用Unity 2017.2