Java谜题畅读版之更多的类谜题

谜题66:一件私事

在下面的程序中,子类的一个域具有与超类的一个域相同的名字。那么,这个程序会打印出什么呢?
  1. classBase{
  2. publicStringclassName="Base";
  3. }
  4. classDerivedextendsBase{
  5. privateStringclassName="Derived";
  6. }
  7. publicclassPrivateMatter{
  8. publicstaticvoidmain(String[]args){
  9. System.out.println(newDerived().className);
  10. }
  11. }
这个程序不能编译, 他说你你试图访问不可见的className.
如果className是一个实例方法,而不是一个实例域,那么Derived.className()将覆写Base.className(),而这样的程序是非法的。一个覆写方法的访问修饰符不能降低方法的可见性.
为className是一个域,所以Derived.className隐藏(hide)了Base.className,而不是覆盖了它.对一个域来说,当它要隐藏另一个域时,如果隐藏域的访问修饰符提供的访问权限比被隐藏域的少,尽管这么做不可取的,但是它确实是合法的。事实上,对于隐藏域来说,如果它具有与被隐藏域完全无关的类型,也是合法的:即使Derived.className是GregorianCalendar类型的,Derived类也是合法的。
尽管在Derived实例中的公共域Base.className被隐藏了,但是我们还是可以通过将Derived实例转型为Base来访问到它。
System.out.println(((Base)new Derived()).className);
这说明了覆写与隐藏之间的一个非常大的区别。一旦一个方法在子类中被覆写,你就不能在子类的实例上调用它了(除了在子类内部,通过使用super关键字来方法)。然而,你可以通过将子类实例转型为某个超类类型来访问到被隐藏的域,在这个超类中该域未被隐藏。

谜题67:对字符串上瘾

下面的程序就是在探究当你重用了一个平台类的名字时,会发生什么。
  1. publicclassStrungOut{
  2. publicstaticvoidmain(String[]args){
  3. Strings=newString("Helloworld");
  4. System.out.println(s);
  5. }
  6. }
  7. classString{
  8. privatefinaljava.lang.Strings;
  9. publicString(java.lang.Strings){
  10. this.s=s;
  11. }
  12. publicjava.lang.StringtoString(){
  13. returns;
  14. }
  15. }
如果你运行程序,它会说java.lang.NoSuchMethodError: main. 可是main白纸黑字写在那里呢.
问题在于main的方法签名出问题了.

谜题68:灰色的阴影

下面的程序在相同的范围内具有两个名字相同的声明,并且没有任何明显的方式可以在它们二者之间做选择。这个程序会打印Black吗?它会打印White吗?甚至,它是合法的吗?
  1. publicclassShadesOfGray{
  2. publicstaticvoidmain(String[]args){
  3. System.out.println(X.Y.Z);
  4. }
  5. }
  6. classX{
  7. staticclassY{
  8. staticStringZ="Black";
  9. }
  10. staticCY=newC();
  11. }
  12. classC{
  13. StringZ="White";
  14. }
有一条规则决定着程序的行为,即当一个变量和一个类型具有相同的名字,并且它们位于相同的作用域时,变量名具有优先权[JLS 6.5.2]。

谜题69:黑色的渐隐

假设你不能修改前一个谜题(谜题68)中的X和C这两个类。你能否编写一个类,其main方法将读取X.Y类中的Z域的值,然后打印它。注意,不能使用反射。
方法1:
  1. publicclassFadeToBlack{
  2. publicstaticvoidmain(String[]args){
  3. System.out.println(((X.Y)null).Z);
  4. }
  5. }
方法2:
  1. publicclassFadeToBlack{
  2. staticclassXyextendsX.Y{}
  3. publicstaticvoidmain(String[]args){
  4. System.out.println(Xy.Z);
  5. }
  6. }

方法3:

  1. publicclassShadesOfGray{
  2. publicstaticvoidmain(String[]args){
  3. X.Ya=newX.Y();
  4. System.out.println(a.Z);
  5. }
  6. }

方法4:

  1. publicclassFadeToBlack{
  2. publicstatic<TextendsX.Y>voidmain(String[]args){
  3. System.out.println(T.Z);
  4. }
  5. }

谜题70:一揽子交易

下面这个程序设计在不同的包中的两个类的交互,main方法位于hack.TypeIt中。那么,这个程序会打印什么呢?
  1. packagehack;
  2. importclick.CodeTalk;
  3. publicclassTypeIt{
  4. privatestaticclassClickItextendsCodeTalk{
  5. voidprintMessage(){
  6. System.out.println("Hack");
  7. }
  8. }
  9. publicstaticvoidmain(String[]args){
  10. ClickItclickit=newClickIt();
  11. clickit.doIt();
  12. }
  13. }
  14. packageclick;
  15. publicclassCodeTalk{
  16. publicvoiddoIt(){
  17. printMessage();
  18. }
  19. voidprintMessage(){
  20. System.out.println("Click");
  21. }
  22. }
经过一番分析,你会说,打印的是Hack,因为你实例化的是ClickIt的对象,最终调用printMessge也将调用ClickIt的。
这是不对的。printMessage这个方法只有包访问权限,包房问权限的方法在其他包是不可见,而写是不能被复写的。所以这两个printMessage之间没有关系,只是名字一样而已。
同样的道理可以扩充到类的私有方法上。私有方法在子类中不可见,且是不可重写的。比如下面:
  1. publicclassSubextendsSuper{
  2. publicvoiddoSomething(){
  3. System.out.println("Sub.doSomething");
  4. }
  5. publicstaticvoidmain(String[]args){
  6. Subs=newSub();
  7. s.doIt();
  8. }
  9. }
  10. classSuper{
  11. privatevoiddoSomething(){
  12. System.out.println("super.doSomething");
  13. }
  14. publicvoiddoIt(){
  15. doSomething();
  16. }
  17. }
如果让子类可以继承,最起码要是protected。

谜题71:进口税

在5.0版中,Java平台引入了大量的可以使操作数组变得更加容易的工具。下面这个谜题使用了变量参数、自动包装、静态导入以及便捷方法Arrays.toString。那么,这个程序会打印什么呢?
  1. importstaticjava.util.Arrays.toString;
  2. classImportDuty{
  3. publicstaticvoidmain(String[]args){
  4. printArgs(1,2,3,4,5);
  5. }
  6. staticvoidprintArgs(Object...args){
  7. System.out.println(toString(args));
  8. }
  9. }
你可能会期望该程序打印[1,2,3,4,5],但是它不能通过编译。
编译器尝试着去应用Object.toString()。导入的toString方法被ImportDuty从Object那里继承而来的具有相同名字的方法所遮蔽(shade)。

谜题72:终极危难

本谜题旨在检验当你试图隐藏一个final域时将要发生的事情。下面的程序将做些什么呢?
  1. classJeopardy{
  2. publicstaticfinalStringPRIZE="$64,000";
  3. }
  4. publicclassDoubleJeopardyextendsJeopardy{
  5. publicstaticfinalStringPRIZE="2cents";
  6. publicstaticvoidmain(String[]args){
  7. System.out.println(DoubleJeopardy.PRIZE);
  8. }
  9. }
你或许会说这个程序不能通过编译,因为被final修饰的不能被覆写或隐藏。 但是它打印了2 cents。
被final修饰的域只是不能被赋值多次。 这个题还说明了一点,子类final域可以隐藏父类final域。父类和子类的赋值语句都发生了,但是不作用在同一个域上。

谜题74:同一性的危机

下面的程序是不完整的,它缺乏对Enigma的声明,这个类扩展自java.lang.Object。请为Enigma提供一个声明,它可以使该程序打印false:
  1. publicclassConundrum{
  2. publicstaticvoidmain(String[]args){
  3. Enigmae=newEnigma();
  4. System.out.println(e.equals(e));
  5. }
  6. }
噢,还有一件事:你不能覆写equals方法。
方法是:你可以重载(overload)它。
  1. finalclassEnigma{
  2. //Don’tdothis!
  3. publicBooleanequals(Enigmaother){
  4. returnfalse;
  5. }
  6. }

名字重用的术语表

覆写(override)

一个实例方法可以覆写(override)在其超类中可访问到的具有相同签名的所有实例方法[JLS 8.4.8.1],从而使能了动态分派(dynamic dispatch);换句话说,VM将基于实例的运行期类型来选择要调用的覆写方法[JLS 15.12.4.4]。覆写是面向对象编程技术的基础,并且是唯一没有被普遍劝阻的名字重用形式:
class Base {
public void f() { }
}

class Derived extends Base {
public void f() { } // overrides Base.f()
}

隐藏(hide)

一个域、静态方法或成员类型可以分别隐藏(hide)在其超类中可访问到的具有相同名字(对方法而言就是相同的方法签名)的所有域、静态方法或成员类型。隐藏一个成员将阻止其被继承[JLS 8.3, 8.4.8.2, 8.5]:
class Base {
public static void f() { }
}

class Derived extends Base {
private static void f() { } // hides Base.f()
}

重载(overload)

在某个类中的方法可以重载(overload)另一个方法,只要它们具有相同的名字和不同的签名。由调用所指定的重载方法是在编译期选定的[JLS 8.4.9, 15.12.2]:
class CircuitBreaker {
public void f(int i) { } // int overloading
public void f(String s) { } // String overloading
}

遮蔽(shadow)

一个变量、方法或类型可以分别遮蔽(shadow)在一个闭合的文本范围内的具有相同名字的所有变量、方法或类型。如果一个实体被遮蔽了,那么你用它的简单名是无法引用到它的;根据实体的不同,有时你根本就无法引用到它[JLS 6.3.1]:
class WhoKnows {
static String sentence = "I don't know.";
public static woid main(String[ ] args) {
String sentence = “I know!”; // shadows static field
System.out.println(sentence); // prints local variable
}
}
尽管遮蔽通常是被劝阻的,但是有一种通用的惯用法确实涉及遮蔽。构造器经常将来自其所在类的某个域名重用为一个参数,以传递这个命名域的值。这种惯用法并不是没有风险,但是大多数Java程序员都认为这种风格带来的实惠要超过其风险:

class Belt {
private final int size;
public Belt(int size) { // Parameter shadows Belt.size
this.size = size;
}
}

遮掩(obscure)

一个变量可以遮掩具有相同名字的一个类型,只要它们都在同一个范围内:如果这个名字被用于变量与类型都被许可的范围,那么它将引用到变量上。相似地,一个变量或一个类型可以遮掩一个包。遮掩是唯一一种两个名字位于不同的名字空间的名字重用形式,这些名字空间包括:变量、包、方法或类型。如果一个类型或一个包被遮掩了,那么你不能通过其简单名引用到它,除非是在这样一个上下文环境中,即语法只允许在其名字空间中出现一种名字。遵守命名习惯就可以极大地消除产生遮掩的可能性[JLS 6.3.2, 6.5]:
public class Obscure {
static String System; // Obscures type java.lang.System
public static void main(String[ ] args) {
// Next line won't compile: System refers to static field
System.out.println(“hello, obscure world!”);
}
}

你可能感兴趣的:(java)