Java Puzzlers(5)更多类之谜

本章更多讨论了子类对父类的继承可能导致的各种陷阱,比如隐藏(hidden),遮蔽(shadow),遮掩(obscure),覆写(override),重载(overload)等行为。

1。首先来看看一个隐藏的例子:

class Base {
    public String className = "Base";
}

class Derived extends Base {
    private String className = "Derived";
}

public class PrivateMatter {
    public static void main(String[] args) {
        System.out.println(new Derived().className);
    }
}

我们可能指望它打印Base,可很抱歉,此程序是无法编译通过,刚一看错误信息你可能愣住:

无权访问private的className。。。

对于实例方法,子类可以对父类的实例方法进行覆写,可对于实例变量(而且包括类变量,静态方法,final静态变量),子类只能隐藏父类中的相同名称的变量,而不是覆写。根据new Derived()的编译期类型为子类Derived,调用子类的className属性,可此属性是声明为private的,所以无法编译通过。如果我们想调用父类中被隐藏的className,可以通过向上转型来实现:

System.out.println(((Base)new Derived()).className);

此例告诉我们,JAVA语言中子类定义与父类相同类型和名称的变量、嵌套类型和静态方法,都将隐藏掉父类中相应的方法,而不是覆写,所以,请避免隐藏!此过程中当然也不存在所谓多态。另外,我们不应该违反这样一条规则:对基类所做的任何行为,都应当可以同样作用于子类。此例中子类className为private,违反了基类中className是public的定义,这样的写法应该避免。

 

2。也许哪一天你突然想自己写一个String来代替java.lang中的String,让我们来看看会发生什么?

public class StrungOut {
    public static void main(String[] args) {
        String s = new String("Hello world");
        System.out.println(s);
    }
}

class String {
    private final java.lang.String s;

    public String(java.lang.String s) {
        this.s = s;
    }

    public java.lang.String toString() {
        return s;
    }
}
试运行此程序,JVM会给你一个非常奇怪的消息:

StrungOut dose not have a main method!

怪了,明明有个main方法啊??请注意,main方法中的参数String []args,其中的String类型要求是java.lang.String,可JVM会自动地把把这些参数认为是我们自定义的下面那个String类型,这就是错误的原因所在。教训:避免重用类名,特别是java平台的类型,特别是java.lang包下的类名!在此例中,当前类所在包中的所有类的main方法都将因此失效。

3。遮掩(obscure):我觉的翻译成模糊也许更好。看看下面的例子:

public class ShadesOfGray {
    public static void main(String[] args){
        System.out.println(X.Y.Z);
    }
}

class X {
    static class Y {
        static String Z = "Black";
    }

    static C Y = new C();
}

class C {
    String Z = "White";
}

你认为他应该打印什么呢??黑还是白?还是黑白不分:),光明的力量总是伟大,它一直打印的是:white。这说明了X.Y一直调用的是静态变量Y,而不是静态内隐类Y。JAVA语言规范告诉我们,当一个变量和一个类型具有相同的名字,明确他们位于相同的作用范围内,变量名具有优先权,同样,变量名与类型名将遮掩包名。

即变量名>类型名>包名。其实上面的例子有更严重的问题,它并没有遵循标准的JAVA命名习惯,变量应该以小写开头(mixedCase的格式),类名以大写开头(MaxedCase的格式),如果完全遵照习惯来写,就不会出现此问题了。所以,请遵守标准的命名习惯。退一步,假设在某些情况下我们只能以此方式书写,那我们怎么访问静态内隐类Y呢?两种方法:

System.out.println(((X.Y)null).Z);   //借助表达式访问类变量,还记的吗?

在JDK5中还可以这样:

public static <T extends X.Y> void main(String args[]){     //继承X.Y类解决此问题。

            System.out.println(T.Z);

 }   

 

4。包A中的某个类被另一个包C中的子类所继承,如果子类当中“覆写”了父类中的方法,而此方法在父类中不是声明为public或者protected,那么这并非覆写,这两个方法将没有任何关系。所以,如果你希望某个类的一个方法被包外的子类所覆写,请把此方法声明为protected或者public。

5。遮蔽(shadow),这里讨论了JDK5静态导入需要注意的问题,看下面的例子:

import static java.util.Arrays.toString;

class ImportDuty {
    public static void main(String[] args) {
        printArgs(1, 2, 3, 4, 5);
    }

    static void printArgs(Object... args) {
        System.out.println(toString(args));
    }
}
这个例子表面上看起来很正常,可事实上是无法编译通过的,编译器告诉我们,找不到恰当的toString()方法。这是为何?我们明明已经导入了Arrays.toString方法了啊?应该打印:[1,2,3,4,5]才对!这是因为编译器将首先在类ImportDuty的范围内寻找toString方法,这个方法将从Object类继承而来的toString()方法,它并不能接受参数Object []args!这就是原因所在,某个范围内的成员对比于静态导入的具有优先权。也就是类ImportDuty的toString()方法遮蔽了Arrays.toString(Object []args)方法。遮蔽与遮掩的区别在于,遮蔽的只能是同类型的名称,而遮掩的是不同类型的(如变量名遮掩类名,类名遮掩包名)。慎重使用静态导入。

6。最后一个谜题很重要哦,我现在才知道JDK5对条件操作符(a?b:c)已经有重大改变。试着分别在JDK1.4和JDK5中运行下面的程序:

import java.util.Random;

public class CoinSide {
    private static Random rnd = new Random();

    public static CoinSide flip() {
        return rnd.nextBoolean() ?
            Heads.INSTANCE : Tails.INSTANCE;
    }

    public static void main(String[] args) {
        System.out.println(flip());
    }
}

class Heads extends CoinSide {
    private Heads() { }
    public static final Heads INSTANCE = new Heads();

    public String toString() {
        return "heads";
    }
}

class Tails extends CoinSide {
    private Tails() { }
    public static final Tails INSTANCE = new Tails();

    public String toString() {
        return "tails";
    }
}
发现了吗?在jdk1.4及以前版本当中,此程序无法通过,报错:

incompatible types for ?: neither is a subtype of the other

说什么第2个操作数和第3个操作数都不是另外一个子类。而在JDK5下这个程序将正常运行,随机打印heads或者tails。这是因为在JDK5以前,条件运算符要求:当第2个和第3个操作数是引用类型时,它们其中的一个必须是另外一个的子类。而例子中Heads和Tails都不是对方的子类,所以产生了上面的错误。而在JDK5中,这个条件放宽了,第2个和第3个操作数如果是引用那么都是合法的,只不过其结果类型将是这两种类型的最小公共超类。此例中Heads和Tails的超类向上追溯有CoinSide,Object,而CoinSide是他们的最小公共超类。

如果想在JDK5以前运行上面的程序,可以把第2或者第3操作数向上转型为他们的超类即可:

   public static CoinSide flip() {
        return rnd.nextBoolean() ?
            (CoinSide)Heads.INSTANCE : Tails.INSTANCE;
    }

另外一些谜题讨论了对Object类中方法的覆写问题,特别要注意不要覆写变成了重载

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值