首页 > 编程语言 >《java解惑》——续类谜题

《java解惑》——续类谜题

时间:2023-11-09 12:04:50浏览次数:33  
标签:java String 谜题 public static Test class 续类


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){  
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){  
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){  
new Derived().getClassName());  
    }  
}


此时程序的打印结果为Derived。

java中允许在程序中隐藏域,成员类型,甚至是静态方法,但是最好不要这么做,因为隐藏带来的问题通常让人混乱(被隐藏的域会被阻止继承),同时隐藏也违反了程序设计原则中的里氏替换原则。

2.java中的遮掩(Obscure):

问题:

下面的小程序用来演示java中的遮掩,代码如下:



class X{  
static class Y{  
static String Z = "Black";  
    }  
static C Y = new C();  
}  
  
class C{  
"White";  
}  
  
public class Test{  
      
public static void main(String[] args){  
        System.out.println(X.Y.Z);  
    }  
}


对于程序运行到底打印输出Black还是White,相信很多人都无法确定,通常编译器会拒绝模棱两可的程序,改程序看起来模棱两可,因此应该被编译器拒绝,但是真实情况是该程序可以正常运行,打印输出结果为White。

原因:

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{  
"White";  
}  
  
public class Test{  
      
public static void main(String[] args){  
        System.out.println(Ex.Why.Z);  
    }  
}


遵循java命名规范是解决遮掩的最好方式,但是除了该方式以外, 如果不允许修改X,Y和C这3个类型名,且不允许使用反射也可以使用如下3种反射打印输出Black:

方法1:

巧用类型转换,代码如下:


class X{  
static class Y{  
static String Z = "Black";  
    }  
static C Y = new C();  
}  
  
class C{  
"White";  
}  
  
public class Test{  
      
public static void main(String[] args){  
null).Z);  
    }  
}


由于在类型转换的时候转型表达式对象前面的只能是类型不能是变量或对象,因此编译器会自动做出合适的选择,但是该例子中的该方法只能对要访问的静态域生效,有一定局限性。

方法2:

使用继承,代码如下:


class X{  
static class Y{  
static String Z = "Black";  
    }  
static C Y = new C();  
}  
  
class C{  
"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{  
"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){  
1, 2, 3, 4, 5);  
    }  
      
static void printArgs(Object... args){  
        System.out.println(toString(args));  
    }  
}


上述代码中通过静态导入java.util.Arrays类的toString(Object[])方法,期望打印输出给定的数组,所以应该期望程序打印输出[1, 2, 3, 4, 5]。

但是真实情况是程序编译报错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){  
1, 2, 3, 4, 5);  
    }  
      
static void printArgs(Object... args){  
        System.out.println(Arrays.toString(args));  
    }  
}


java中遮蔽(shadow)和遮掩(obscure)非常类似,很多人经常搞混淆,二者的区别如下:

遮蔽:一个声明只能遮蔽类型相同的另一个声明:一个类型声明可以遮蔽另一个类型声明;一个方法声明可以遮蔽另一个方法声明;一个变量声明可以遮蔽另一个变量声明、

遮掩:变量声明可以遮掩类型和包声明;类型声明可以遮掩包声明。

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.5之后,条件操作符在第二个和第三个操作数是引用类型时总是合法的,其结果类型是这两种类型的最小公共超类,即它等效于T choose(T a, T b)。

在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());  
    }  
}


在JDK1.5之后,可以直接使用java的枚举来编写,代码如下:


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());  
    }  
}

标签:java,String,谜题,public,static,Test,class,续类
From: https://blog.51cto.com/u_809530/8275028

相关文章

  • 《java解惑》——循环谜题
    1.byte数值比较:问题:下面的程序循环遍历byte数值,以查找某个特定值,代码如下:publicclassTest{publicstaticvoidmain(String[]args){for(byteb=Byte.MIN_VALUE;b<Byte.MAX_VALUE;b++){if(b==0x90){"Joy!");}}......
  • 《java解惑》——异常谜题
    1.finally语句块中的return:问题:下面的小程序运行结果是什么:publicclassTest{publicstaticvoidmain(String[]args){System.out.println(decision());}staticbooleandecision(){try{returntrue;finally{returnfalse;......
  • 《java解惑》——类谜题
    1.方法重载:问题:下面的程序演示方法重载,代码如下:publicclassTest{publicstaticvoidmain(String[]args){newTest(null);}privateTest(Objecto){"Object");}privateTest(double[]doubleArray){"Doublearray&......
  • 《java解惑》--字符串之谜
    1.字符拼接:问题:程序员几乎在每天编程中都遇到和处理字符串拼接的问题,但是是否对其了解的足够深入,且看下面的程序:publicstaticvoidmain(String[]args){"H"+"a");'H'+'a');}很多人觉得输出结果应是:HaHa,但是真实的程序运行结果是:Ha169。原因:程序第一......
  • JavaScript实现完整的表单验证对邮箱用户名和密码一致性检测并拦截提交-----前端
    完整的表单验证HTML网页使用JS完成用户名密码一致性和邮箱验证<!DOCTYPEhtml><!--这是HTML的注释--><htmllang="en"id="myHtml"> <head> <!--这里不是设置了编码,而是告诉浏览器,用什么编码方式打开文件避免乱码--> <metacharset="UTF-8"> <metaname......
  • 《java解惑》——库谜题
    1.不可变类:问题:下面的程序计算5000+50000+500000值,代码如下:importjava.math.BigInteger;publicclassTest{publicstaticvoidmain(String[]args){newBigInteger("5000");newBigInteger("50000");newBigInteger("500000");......
  • Java-Script 编程
    Java-Script编程目录Java-Script编程一.Js概念1.1简介1.2语法结构二.变量使用2.1定义变量2.2定义常量三.数据类型3.1数值类型(number)3.2字符类型(string)3.3.字符类型常用方法3.4布尔值(boolean)3.5null与undefined3.6数组3.7数组常用的方法3.8运算符四.流程......
  • java ip地址转换成int
    数据库存放,提高数据库使用性能我们将字符串IP转换成int类型保存!java代码如下://将127.0.0.1形式的IP地址转换成十进制整数,这里没有进行任何错误处理publicstaticlongipToLong(StringstrIp){long[]ip=newlong[4];//先找到IP地址字符串中.的......
  • Java类加载机制
    类加载机制将class文件中的二进制数据读取到内存中,并对其进行校验,解析和初始化,将类型数据存放在方法区,实例对象存放在堆,作为方法区该类的数据访问接口。这就是类加载。加载通过全限定名获取二进制字节流将字节流代表的静态存储结构转化为方法区的运行时数据结构在堆中生成......
  • 【深入理解Java虚拟机】内存分配策略
    本文内容来源于《深入理解Java虚拟机》一书,非常推荐大家去看一下这本书。本系列其他文章:【深入理解Java虚拟机】Java内存区域模型、对象创建过程、常见OOM【深入理解Java虚拟机】垃圾回收机制垃圾收集器与内存分配策略Java技术体系中所提倡的自动内存管理最终可以归结为自动......