1.Java中的隐藏(hide):
问题:
下面的小程序用来演示java中的隐藏,代码如下:
- class Base{
- public String className = "Base";
- }
- class Derived extends Base{
- private String className = "Derived";
- }
- public class Test{
- public static void main(String[] args){
- System.out.println(new Derived().className);
- }
- }
有人觉得应该打印输出Base,也有觉得Derived类编译报错,因为同名的变量访问控制权限比父类更严格了。
真实情况确实是程序编译有错,但是不是Derived类编译报错,而是在main函数的打印语境中报错,因为Derived类的className属性是私有的,无法访问。
原因:
java中,子类和父类拥有相同方法签名的方法被称为方法覆盖,在方法覆盖中子类方法的访问控制权限不能被父类更严格,同时子类方法也不能抛出被父类方法更多的异常。
而子类和父类拥有同名的变量被称为变量隐藏(hide),变量隐藏没有访问控制权限的限制,因此虽然Base类中的className属性是共有的,子类Derived类可以继承,但是由于子类Derived中拥有同名的className变量,即使是私有的,Derived类的className变量还是会隐藏了父类Base类的className变量。
结论:
如果想要解决上述程序中变量隐藏带来的编译错误,有以下两种解决方法:
方法1:使用类型转换将子类转换父类,代码如下:
- class Base{
- public String className = "Base";
- }
- class Derived extends Base{
- private String className = "Derived";
- }
- public class Test{
- public static void main(String[] args){
- System.out.println(((Base)new Derived()).className);
- }
- }
此时程序的打印输出结果为Base。
方法2:使用方法覆盖代替变量隐藏,代码如下:
- class Base{
- public String getClassName() {
- return "Base";
- }
- }
- class Derived extends Base{
- public String getClassName() {
- return "Derived";
- }
- }
- public class Test{
- public static void main(String[] args){
- System.out.println(new Derived().getClassName());
- }
- }
java中允许在程序中隐藏域,成员类型,甚至是静态方法,但是最好不要这么做,因为隐藏带来的问题通常让人混乱(被隐藏的域会被阻止继承),同时隐藏也违反了程序设计原则中的里氏替换原则。
2.java中的遮掩(Obscure):
问题:
下面的小程序用来演示java中的遮掩,代码如下:
- class X{
- static class Y{
- static String Z = "Black";
- }
- static C Y = new C();
- }
- class C{
- String Z = "White";
- }
- public class Test{
- public static void main(String[] args){
- System.out.println(X.Y.Z);
- }
- }
原因:
java中一个变量可以遮掩具有相同名字的一个类型,只要她们都在同一作用范围内:如果这个名字被用于变量与类型都被许可的范围,那么它将引用到变量上,即变量遮掩类型,类似地,一个变量或者一个类型可以遮掩一个包。遮掩是唯一一种两个名字位于不同的名字空间的名字重用形式。
上述程序代码刚好为我们演示了变量遮掩类型这一原则,由于Y既是类型名又是变量名,并且处于同一作用范围,虽然类型声明在变量声明之前,变量名还是会遮掩类型名。
结论:
如果遵循java的命名规范,可以有效规避遮掩,java命名规范如下:
(1).变量名通常是以首字母小写的驼峰命名法.
(2).类型名通常是以首字母大写的驼峰命名法;包名应该是全小写。
(3).常量名应该是全部大写(多个单词直接使用下划线连接)。
(4).单个的大写字母只能用于类型参数,就像泛型接口Map<K, V>中那样。
因此,遵循命名规范重写上面的程序就可以毫无歧义地打印输出Black,代码如下:
- class Ex{
- static class Why{
- static String Z = "Black";
- }
- static See y = new See();
- }
- class See{
- String Z = "White";
- }
- public class Test{
- public static void main(String[] args){
- System.out.println(Ex.Why.Z);
- }
- }
方法1:
巧用类型转换,代码如下:
- class X{
- static class Y{
- static String Z = "Black";
- }
- static C Y = new C();
- }
- class C{
- String Z = "White";
- }
- public class Test{
- public static void main(String[] args){
- System.out.println(((X.Y)null).Z);
- }
- }
方法2:
使用继承,代码如下:
- class X{
- static class Y{
- static String Z = "Black";
- }
- static C Y = new C();
- }
- class C{
- String Z = "White";
- }
- public class Test{
- static class Xy extends X.Y{}
- public static void main(String[] args){
- System.out.println(Xy.Z);
- }
- }
方法3:
使用泛型,代码如下:
- class X{
- static class Y{
- static String Z = "Black";
- }
- static C Y = new C();
- }
- class C{
- String Z = "White";
- }
- public class Test{
- public static <T extends X.Y> void main(String[] args){
- System.out.println(T.Z);
- }
- }
泛型的上边界和下边界可以起到extends类似的作用。
3.java中的遮蔽(shadow):
问题:
下面的代码展示java中的遮蔽,代码如下:
- import static java.util.Arrays.toString;
- public class Test{
- public static void main(String[] args){
- printArgs(1, 2, 3, 4, 5);
- }
- static void printArgs(Object... args){
- System.out.println(toString(args));
- }
- }
但是真实情况是程序编译报错The method toString() in the type Object is not applicable for the arguments (Object[])。
原因:
在java中遮蔽是指一个变量、方法或者类型可以分别遮蔽在一个闭合的文本范围内的具有相同名字的所有变量、方法或类型。java中的同名局部变量优先同名全局变量就是遮蔽的最常见例子。
编译器在选择运行期将被调用的方法时,所作的第一件事就是在肯定能找到该方法的范围内挑选,编译器将在包含了具有恰当名字的方法的最小闭合范围内进行挑选,上述程序中的选择方法的最小范围就是Test类,它包含了从Object继承而来的toString方法,而静态导入的toString方法恰好被从Object继承而来的同名方法所遮蔽。
当一个声明遮蔽了另一个声明时,简单名称就爱那个引用到遮蔽声明中的实体,即本身就属于某个范围的成员在该范围内与静态导入相比具有优先权。
结论:
明白了上述程序问题是有java遮蔽引起之后,解决起来就比较容易了,使用普通导入声明代替静态导入,代码如下:
- import java.util.Arrays;
- public class Test{
- public static void main(String[] args){
- printArgs(1, 2, 3, 4, 5);
- }
- static void printArgs(Object... args){
- System.out.println(Arrays.toString(args));
- }
- }
遮蔽:一个声明只能遮蔽类型相同的另一个声明:一个类型声明可以遮蔽另一个类型声明;一个方法声明可以遮蔽另一个方法声明;一个变量声明可以遮蔽另一个变量声明、
遮掩:变量声明可以遮掩类型和包声明;类型声明可以遮掩包声明。
4.条件操作符:
问题:
下面的程序演示条件操作符在JDK1.4和JDK1.5之后的变化,代码如下:
- import java.util.Random;
- public class Test{
- private static Random rnd = new Random();
- public static Test flip(){
- return rnd.nextBoolean() ? Heads.INSTANCE : Tails.INSTANCE;
- }
- public static void main(String[] args){
- System.out.println(flip());
- }
- }
- class Heads extends Test{
- private Heads(){}
- public static final Heads INSTANCE = new Heads();
- public String toString(){
- return "heads";
- }
- }
- class Tails extends Test{
- private Tails(){}
- public static final Tails INSTANCE = new Tails();
- public String toString(){
- return "tails";
- }
- }
上述程序没有使用任何JDK1.5的新特性,在JDK1.5之后的版本可以正常编译和运行。
在编译时如果使用“-source 1.4”参数让JDK使用JDK1.4对其进行编译,发现会报编译错误:incompatible types for ?: neither is a subtype of the other......
原因:
条件操作符(?:)的行为在JDK1.5之前是非常受限制的,当第二个和第三个操作数是引用类型时,条件操作符要求它们其中的一个必须是另一个的子类型,由于Heads和Tails彼此都不是对方的子类型,因此产生了第二个和第三个操作数类型不兼容编译错误。
结论:
明白错误的原因之后,想让上述代码在JDK1.4中顺利编译通过可以将其中一个操作数类型转换为公共超类类型,代码如下:
- public static Test flip(){
- return rnd.nextBoolean() ? (Test)Heads.INSTANCE : Tails.INSTANCE;
- }
在JDK1.4和更早版本中条件操作符这种限制带来的问题非常普遍和频繁,经常使用如下的类型安全枚举模式来避免该问题,代码如下:
- import java.util.Random;
- public class Test{
- public static final Test HEADS = new Test("heads");
- public static final Test TAILS = new Test("tails");
- private final String name;
- private Test(String name){
- this.name = name;
- }
- public String toString(){
- return name;
- }
- private static Random rnd = new Random();
- public static Test flip(){
- return rnd.nextBoolean() ? HEADS : TAILS;
- }
- public static void main(String[] args){
- System.out.println(flip());
- }
- }
- import java.util.Random;
- public enum Test{
- HEADS, TAILS;
- public String toString(){
- return name().toLowerCase();
- }
- private static Random rnd = new Random();
- public static Test flip(){
- return rnd.nextBoolean() ? HEADS : TAILS;
- }
- public static void main(String[] args){
- System.out.println(flip());
- }
- }