`

[转]java 解惑你知道多少 2

    博客分类:
  • JAVA
阅读更多
36. 属性只能被隐藏
Java代码
1.class P {  
2. public String name = "P";  
3.}  
4. 
5.class S extends P {  
6. // 隐藏父类的name域,而不像方法属于重写  
7. private String name = "S";  
8.}  
9. 
10.public class Test {  
11. public static void main(String[] args) {  
12.  // !! S.name is not visible  
13.  // !! System.out.println(new S().name);  
14.  // 属性不能被重写,只是被隐藏,所以不具有多态性为  
15.  System.out.println(((P) new S()).name);// p  
16. }  
17.} 
class P {
public String name = "P";
}

class S extends P {
// 隐藏父类的name域,而不像方法属于重写
private String name = "S";
}

public class Test {
public static void main(String[] args) {
  // !! S.name is not visible
  // !! System.out.println(new S().name);
  // 属性不能被重写,只是被隐藏,所以不具有多态性为
  System.out.println(((P) new S()).name);// p
}
}属性的调用与静态方式的调用一样,只与前面引用类型相关,与具体的实例没有任何关系。

当你在声明一个域、一个静态方法或一个嵌套类型时,如果其名与基类中相对应的某个可访问的域、方法或类型相同

时,就会发生隐藏。


37. 属性对嵌套类的遮掩
Java代码
1.class X {  
2. static class Y {  
3.  static String Z = "Black";  
4. }  
5. static C Y = new C();  
6.}  
7.class C {  
8. String Z = "White";  
9.}  
10.public class T {  
11. public static void main(String[] args) {  
12.  System.out.println(X.Y.Z);// White  
13.  System.out.println(((X.Y) null).Z);// Black  
14. }  
15.} 
class X {
static class Y {
  static String Z = "Black";
}
static C Y = new C();
}
class C {
String Z = "White";
}
public class T {
public static void main(String[] args) {
  System.out.println(X.Y.Z);// White
  System.out.println(((X.Y) null).Z);// Black
}
}当一个变量和一个类型具有相同的名字,并且它们位于相同的作用域时,变量名具有优先权。变量名将遮掩类型名。

相似地,变量名和类型名可以遮掩包名。


38. 不能重写不同包中的defualt访问权限方法
Java代码
1.package click;  
2.public class P {  
3. public void f() {  
4.  //因为子类没有重写该方法,所以调用的还是父类中的方法  
5.  prt();  
6. }  
7. void prt() {  
8.  System.out.println("P");  
9. }  
10.}  
11. 
12.package hack;  
13.import click.P;  
14.public class T {  
15. private static class S extends P {  
16.  // 这里没有重写父类的方法,因为父类方法不可见  
17.  void prt() {  
18.   System.out.println("S");  
19.  }  
20. }  
21. public static void main(String[] args) {  
22.  new S().f();// P  
23. }  
24.} 
package click;
public class P {
public void f() {
  //因为子类没有重写该方法,所以调用的还是父类中的方法
  prt();
}
void prt() {
  System.out.println("P");
}
}

package hack;
import click.P;
public class T {
private static class S extends P {
  // 这里没有重写父类的方法,因为父类方法不可见
  void prt() {
   System.out.println("S");
  }
}
public static void main(String[] args) {
  new S().f();// P
}
}一个包内私有(default)的方法不能被位于另一个包中的某个方法直接重写。



关于私有方法,还有属性能否被重写,请参考 方法能覆写(重写),属性能覆写吗?

39. 重写、隐藏、重载、遮蔽、遮掩
重写:一个实例方法可以重写在其超类中可访问到的具有相同签名的所有实例方法,从而能动态分派,换句话说,VM

将基于实例的运行期类型来选择要调用的重写方法。重写是面向对象编程技术的基础。

Java代码
1.public class P{  
2. public void f(){}  
3.}  
4.class S extends P{  
5. public void f(){}//重写  
6.} 
public class P{
public void f(){}
}
class S extends P{
public void f(){}//重写
}

重写时异常要求:
 如果父类方法抛出的是捕获型异常,则子类也只能抛出同类的捕获型异常或其子类,或不抛出。
 父类抛出捕获型异常,子类却抛出运行时异常,这是可以,因为抛出运行时就相当于没有抛出任何异常。
 如果父类抛出的是非捕获型异常,则子类可以抛出任意的非捕获型异常,没有扩大异常范围这一问题。
 如果父类抛出的是非捕获异常,子类也可以不用抛出,这与父类为捕获型异常是一样的。
 如果父类抛出的是非捕获异常,子类就不能抛出任何捕获型异常,因为这样会扩大异常的范围。



返回类型的协变:从Java SE5开始子类方法可以返回比它重写的基类方法更具体的类型,但是这在早先的Java版本是

不允许——重写时子类的返回类型一定要与基类相同。但要注意的是:子类方法返回类型要是父类方法返回类型的子

类,而不能反过来。



方法参数类型协变:如果父子类同名方法的参数类型为父子关系,则为参数类型协变,此时不属于重写,而是方法的

重载,以前版本就是这样。



如果父类的方法为private时,子类同名的方法的方法名前可以使用任何修饰符来修饰。我们可以随意地添加一个新的

私有成员(方法、域、类型),或都是修改和删除一个旧的私有成员,而不需要担心对该类的客户造成任何损害。换

而言之,私有成员被包含它们的类完全封装了。



父与子类相同签名方法不能一静一动的,即父类的方法是静态的,而子类不是,或子类是静态的,而父类不是,编译

时都不会通过。



父与子相同签名方法都是静态的方法时,方法名前的修饰符与非静态方法重写的规则一样,但不属于重写,因为静态

方法根本就不具有多态性。



最后,属于成员也不具有多态特性,相同名的域属于隐藏,而不管域前面的修饰符为什么:

Java代码
1.class P {  
2. public static final String str = "P";  
3.}  
4.class S extends P {  
5. //编译能通过。可以是final,这里属于隐藏  
6. public static final String str = "S";  
7. public static void main(String[] args) {  
8.  System.out.println(S.str);//s  
9. }  
10.} 
class P {
public static final String str = "P";
}
class S extends P {
//编译能通过。可以是final,这里属于隐藏
public static final String str = "S";
public static void main(String[] args) {
  System.out.println(S.str);//s
}
}

隐藏:一个域、静态方法或成员类型可以分别隐藏在其超类中可访问到的具有相同名字(对方法而言就是相同的方法

签名)的所有域、静态方法或成员类型。隐藏一个成员将阻止其被继承。

Java代码
1.public class P{  
2. public static void f(){}  
3.}  
4.class S extends P{  
5. //隐藏,不会继承P.f()  
6. public static void f(){}  
7.} 
public class P{
public static void f(){}
}
class S extends P{
//隐藏,不会继承P.f()
public static void f(){}
}

重载:在某个类中的方法可以重载另一个方法,只要它们具有相同的名字和不同的签名。由调用所指定的重载方法是

在编译期选定的。

Java代码
1.public class T{  
2. public static void f(int i){}  
3. public static void f(String str){}//重载  
4.} 
public class T{
public static void f(int i){}
public static void f(String str){}//重载
}

遮蔽:一个变量、方法或类型可以分别遮蔽在一个闭合的文本范围内的具有相同名字的所有变量、方法或类型。如果

一个实体被遮蔽了,那么你用它的简单名是无法引用到它的;根据实体的不同,有时你根本就无法引用到它。

Java代码
1.public class T {  
2. private static String str = "feild";  
3. public static void main(String[] args) {  
4.  String str = "local";// 遮蔽  
5.  System.out.println(str);// local  
6.  // 可以通过适当的方式来指定  
7.  System.out.println(T.str);// feild  
8. }  
9.}  
10. 
11.public class T {  
12. private final int size;  
13. // 参数属于方法局部范围类变量,遮蔽了同名成员变量  
14. public T(int size) {  
15.  //使用适当的引用来指定  
16.  this.size = size;  
17. }  
18.} 
public class T {
private static String str = "feild";
public static void main(String[] args) {
  String str = "local";// 遮蔽
  System.out.println(str);// local
  // 可以通过适当的方式来指定
  System.out.println(T.str);// feild
}
}

public class T {
private final int size;
// 参数属于方法局部范围类变量,遮蔽了同名成员变量
public T(int size) {
  //使用适当的引用来指定
  this.size = size;
}
}

遮掩:一个变量可以遮掩具有相同名字的一个类型,只要它们都在同一个范围内:如果这个名字被用于变量与类型都

被许可的范围,那么它将引用到变量上。相似地,一个变量或一个类型可以遮掩一个包。遮掩是唯一一种两个名字位

于不同的名字空间的名字重用形式,这些名字空间包括:变量、包、方法或类型。如果一个类型或一个包被遮掩了,

那么你不能通过其简单名引用到它,除非是在这样一个上下文环境中,即语法只允许在其名字空间中出现一种名字:

Java代码
1.public class T {  
2. static String System;  
3. public static void main(String[] args) {  
4.  // !!不能编译,遮掩 java.lang.System  
5.  // !! System.out.println("Hello");  
6.  // 可明确指定  
7.  java.lang.System.out.println("Hello");  
8. }  
9.} 
public class T {
static String System;
public static void main(String[] args) {
  // !!不能编译,遮掩 java.lang.System
  // !! System.out.println("Hello");
  // 可明确指定
  java.lang.System.out.println("Hello");
}
}

40. 构造器中静态常量的引用问题
Java代码
1.class T {  
2. // 先于静态常量t初始化,固可以在构造器中正常使用  
3. private static final int y = getY();  
4. /* 
5.  * 严格按照静态常量声明的先后顺来初始化:即t初始 
6.  * 化完后,才初始化后面的静态常量j,所以构造器中 
7.  * 引用后面的静态常量j时,会是0,即内存清零时的值 
8.  */ 
9. public static final T t = new T();  
10. // 后于静态常量t初始化,不能在构造器中正常使用  
11. private static final int j = getJ();  
12. private final int i;  
13. 
14. static int getY() {  
15.  return 2;  
16. }  
17. 
18. static int getJ() {  
19.  return 2;  
20. }  
21. 
22. // 单例  
23. private T() {  
24.  i = y - j - 1;  
25.//为什么j不是2  
26.  System.out.println("y=" + y + " j=" + j);// y=2 j=0  
27. }  
28. 
29. public int getI() {  
30.  return i;  
31. }  
32. 
33. public static void main(String[] args) {  
34.  System.out.println(T.t.getI());// 1  
35.  System.out.println(T.j);// 2  
36. }  
37.} 
class T {
// 先于静态常量t初始化,固可以在构造器中正常使用
private static final int y = getY();
/*
  * 严格按照静态常量声明的先后顺来初始化:即t初始
  * 化完后,才初始化后面的静态常量j,所以构造器中
  * 引用后面的静态常量j时,会是0,即内存清零时的值
  */
public static final T t = new T();
// 后于静态常量t初始化,不能在构造器中正常使用
private static final int j = getJ();
private final int i;

static int getY() {
  return 2;
}

static int getJ() {
  return 2;
}

// 单例
private T() {
  i = y - j - 1;
//为什么j不是2
  System.out.println("y=" + y + " j=" + j);// y=2 j=0
}

public int getI() {
  return i;
}

public static void main(String[] args) {
  System.out.println(T.t.getI());// 1
  System.out.println(T.j);// 2
}
}

该程序所遇到的问题是由类初始化顺序中的循环而引起的:初始化t时需调用构造函数,而调用构造函数前需初始化所

有静态成员,此时又包括对t的再次初始化。



T类的初始化是由虚拟机对main方法的调用而触发的。首先,其静态域被设置缺省值,其中y、j被初始化为0,而t被初始化为null。接下来,静态域初始器按照其声明的顺序执行赋值动作。第一个静态域是y,它的值是通过调用getY获取的,赋值操作完后结果为2。第一个初始化完成后,再进行第二个静态域的赋值操作,第二个静态域为t,它的值是通过调用T()构造函数来完成的。这个构造器会用二个涉及静态域y、j来初始化非静态域i。通常,读取一个静态域是会引起一个类被初始化,但是我们又已经在初始化T类。JavaVM规范对递归的初始化尝试会直接被忽略掉(按理来说在创建出实例前需初始化完所有的静态域后再来创建实例),这样就导致在静态域被初始化之前就调用了构造器,后面的静态域j将得不到正常的初始化前就在构造器中被使用了,使用时的值为内存分配清零时的,即0。当t初始化完后,再初始化j,此时j得到的值为2,但此时对i的初始化过程来说已经晚了。



在final类型的静态域被初始化之前,存在着读取其值的可能,而此时该静态域包含的还只是其所属类型的缺省值。这

是与直觉想违背的,因为我们通常会将final类型的域看作是常量,但final类型的域只有在其初始化表达式是字面常

量表达式时才是真正的常量。



再看看另一程序:

Java代码
1.class T {  
2. private final static int i = getJ();  
3. private final static int j;  
4. static {  
5.  j = 2;  
6. }  
7. static int getJ() {  
8.  return j;  
9. }  
10. public static void main(String[] args) {  
11.  System.out.println(T.j);// 2  
12.  /* 
13.   * 因为上面的语句已经初使完T类,所以下面语句是 
14.   * 不 会 再引起类的初始化,这里的结果用的是第一 
15.   *  次( 即上面语句)的初始化结果 
16.   */ 
17.  System.out.println(T.i);// 0  
18. }  
19.} 
class T {
private final static int i = getJ();
private final static int j;
static {
  j = 2;
}
static int getJ() {
  return j;
}
public static void main(String[] args) {
  System.out.println(T.j);// 2
  /*
   * 因为上面的语句已经初使完T类,所以下面语句是
   * 不 会 再引起类的初始化,这里的结果用的是第一
   *  次( 即上面语句)的初始化结果
   */
  System.out.println(T.i);// 0
}
}为什么第二个输出是0而不是2呢?这就是因为VM是严格按照你声明的顺序来初始化静态域的,所以前面的引用后面的

静态域时,基本类型就是0,引用类型就会是null。



所以要记住:静态域,甚至是final类型的静态域,可能会在它们被初始化之前,被读走其缺省值。



另,类初始化规则请参考《惰性初始化》一节











34. 参数兼容的方法重载
Java代码
1.public class Confusing {  
2. private Confusing(Object o) {  
3.  System.out.println("Object");  
4. }  
5. private Confusing(double[] dArr) {  
6.  System.out.println("double array");  
7. }  
8. public static void main(String[] args) {  
9.  new Confusing(null);  
10. }  
11.} 
public class Confusing {
private Confusing(Object o) {
  System.out.println("Object");
}
private Confusing(double[] dArr) {
  System.out.println("double array");
}
public static void main(String[] args) {
  new Confusing(null);
}
}上面的程序打印的是“double array”,为什么?



null可代表任何非基本类型对象。



Java的重载解析过程是分两阶段运行的。第一阶段选取所有可获得并且可应用的方法或构造器。第二阶段在第一阶段

选取的方法或构造器中选取最精确的一个。如果一个方法或构造器可以接受传递给另一个方法或构造器的任何参数,

那么我们就说第一个方法比第二个方法缺乏精确性,调用时就会选取第二个方法。



使用上面的规则来解释该程序:构造器Confusing(Object o)可以接受任何传递Confusing(double[] dArr)的参数,因

此Confusing(Object o)相对缺乏精确性,所以Confusing(null)会调用Confusing(double[] dArr)构造器。



如果想强制要求编译器选择一个自己想要的重载版本,需要将实参强制转型为所需要的构造器或方法的参数类型:如

这里要调用Confusing(Object o)本版,则这样调用:Confusing((Object)null)。



如果你确实进行了重载,那么请确保所有的重载版本所接受的参数类型都互不兼容,这样,任何两个重载版本都不会

同时是可应用的。


35. 静态方法不具有多态特性
Java代码
1.class A1 {  
2. public static void f() {  
3.  System.out.println("A1.f()");  
4. }  
5.}  
6.class A2 extends A1 {  
7. public static void f() {  
8.  System.out.println("A2.f()");  
9. }  
10.}  
11.class T {  
12. public static void main(String[] args) {  
13.  A1 a1 = new A1();  
14.  A1 a2 = new A2();  
15.  // 静态方法不具有多态效果,它是根据引用声明类型来调用  
16.  a1.f();// A1.f()  
17.  a2.f();// A1.f()  
18. }  
19.} 
class A1 {
public static void f() {
  System.out.println("A1.f()");
}
}
class A2 extends A1 {
public static void f() {
  System.out.println("A2.f()");
}
}
class T {
public static void main(String[] args) {
  A1 a1 = new A1();
  A1 a2 = new A2();
  // 静态方法不具有多态效果,它是根据引用声明类型来调用
  a1.f();// A1.f()
  a2.f();// A1.f()
}
}对静态方法的调用不存在任何动态的分派机制。当一个程序调用了一个静态方法时,要被调用的方法都是在编译时就

被选定的,即调用哪个方法是根据该引用被声明的类型决定的。上面程序中a1与a2引用的类型都是A1类型,所以调用

的是A1中的f()方法。


36. 属性只能被隐藏
Java代码
1.class P {  
2. public String name = "P";  
3.}  
4. 
5.class S extends P {  
6. // 隐藏父类的name域,而不像方法属于重写  
7. private String name = "S";  
8.}  
9. 
10.public class Test {  
11. public static void main(String[] args) {  
12.  // !! S.name is not visible  
13.  // !! System.out.println(new S().name);  
14.  // 属性不能被重写,只是被隐藏,所以不具有多态性为  
15.  System.out.println(((P) new S()).name);// p  
16. }  
17.} 
class P {
public String name = "P";
}

class S extends P {
// 隐藏父类的name域,而不像方法属于重写
private String name = "S";
}

public class Test {
public static void main(String[] args) {
  // !! S.name is not visible
  // !! System.out.println(new S().name);
  // 属性不能被重写,只是被隐藏,所以不具有多态性为
  System.out.println(((P) new S()).name);// p
}
}属性的调用与静态方式的调用一样,只与前面引用类型相关,与具体的实例没有任何关系。

当你在声明一个域、一个静态方法或一个嵌套类型时,如果其名与基类中相对应的某个可访问的域、方法或类型相同

时,就会发生隐藏。


37. 属性对嵌套类的遮掩
Java代码
1.class X {  
2. static class Y {  
3.  static String Z = "Black";  
4. }  
5. static C Y = new C();  
6.}  
7.class C {  
8. String Z = "White";  
9.}  
10.public class T {  
11. public static void main(String[] args) {  
12.  System.out.println(X.Y.Z);// White  
13.  System.out.println(((X.Y) null).Z);// Black  
14. }  
15.} 
class X {
static class Y {
  static String Z = "Black";
}
static C Y = new C();
}
class C {
String Z = "White";
}
public class T {
public static void main(String[] args) {
  System.out.println(X.Y.Z);// White
  System.out.println(((X.Y) null).Z);// Black
}
}当一个变量和一个类型具有相同的名字,并且它们位于相同的作用域时,变量名具有优先权。变量名将遮掩类型名。

相似地,变量名和类型名可以遮掩包名。


38. 不能重写不同包中的defualt访问权限方法
Java代码
1.package click;  
2.public class P {  
3. public void f() {  
4.  //因为子类没有重写该方法,所以调用的还是父类中的方法  
5.  prt();  
6. }  
7. void prt() {  
8.  System.out.println("P");  
9. }  
10.}  
11. 
12.package hack;  
13.import click.P;  
14.public class T {  
15. private static class S extends P {  
16.  // 这里没有重写父类的方法,因为父类方法不可见  
17.  void prt() {  
18.   System.out.println("S");  
19.  }  
20. }  
21. public static void main(String[] args) {  
22.  new S().f();// P  
23. }  
24.} 
package click;
public class P {
public void f() {
  //因为子类没有重写该方法,所以调用的还是父类中的方法
  prt();
}
void prt() {
  System.out.println("P");
}
}

package hack;
import click.P;
public class T {
private static class S extends P {
  // 这里没有重写父类的方法,因为父类方法不可见
  void prt() {
   System.out.println("S");
  }
}
public static void main(String[] args) {
  new S().f();// P
}
}一个包内私有(default)的方法不能被位于另一个包中的某个方法直接重写。



关于私有方法,还有属性能否被重写,请参考 方法能覆写(重写),属性能覆写吗?

39. 重写、隐藏、重载、遮蔽、遮掩
重写:一个实例方法可以重写在其超类中可访问到的具有相同签名的所有实例方法,从而能动态分派,换句话说,VM

将基于实例的运行期类型来选择要调用的重写方法。重写是面向对象编程技术的基础。

Java代码
1.public class P{  
2. public void f(){}  
3.}  
4.class S extends P{  
5. public void f(){}//重写  
6.} 
public class P{
public void f(){}
}
class S extends P{
public void f(){}//重写
}

重写时异常要求:
 如果父类方法抛出的是捕获型异常,则子类也只能抛出同类的捕获型异常或其子类,或不抛出。
 父类抛出捕获型异常,子类却抛出运行时异常,这是可以,因为抛出运行时就相当于没有抛出任何异常。
 如果父类抛出的是非捕获型异常,则子类可以抛出任意的非捕获型异常,没有扩大异常范围这一问题。
 如果父类抛出的是非捕获异常,子类也可以不用抛出,这与父类为捕获型异常是一样的。
 如果父类抛出的是非捕获异常,子类就不能抛出任何捕获型异常,因为这样会扩大异常的范围。



返回类型的协变:从Java SE5开始子类方法可以返回比它重写的基类方法更具体的类型,但是这在早先的Java版本是

不允许——重写时子类的返回类型一定要与基类相同。但要注意的是:子类方法返回类型要是父类方法返回类型的子

类,而不能反过来。



方法参数类型协变:如果父子类同名方法的参数类型为父子关系,则为参数类型协变,此时不属于重写,而是方法的

重载,以前版本就是这样。



如果父类的方法为private时,子类同名的方法的方法名前可以使用任何修饰符来修饰。我们可以随意地添加一个新的

私有成员(方法、域、类型),或都是修改和删除一个旧的私有成员,而不需要担心对该类的客户造成任何损害。换

而言之,私有成员被包含它们的类完全封装了。



父与子类相同签名方法不能一静一动的,即父类的方法是静态的,而子类不是,或子类是静态的,而父类不是,编译

时都不会通过。



父与子相同签名方法都是静态的方法时,方法名前的修饰符与非静态方法重写的规则一样,但不属于重写,因为静态

方法根本就不具有多态性。



最后,属于成员也不具有多态特性,相同名的域属于隐藏,而不管域前面的修饰符为什么:

Java代码
1.class P {  
2. public static final String str = "P";  
3.}  
4.class S extends P {  
5. //编译能通过。可以是final,这里属于隐藏  
6. public static final String str = "S";  
7. public static void main(String[] args) {  
8.  System.out.println(S.str);//s  
9. }  
10.} 
class P {
public static final String str = "P";
}
class S extends P {
//编译能通过。可以是final,这里属于隐藏
public static final String str = "S";
public static void main(String[] args) {
  System.out.println(S.str);//s
}
}

隐藏:一个域、静态方法或成员类型可以分别隐藏在其超类中可访问到的具有相同名字(对方法而言就是相同的方法

签名)的所有域、静态方法或成员类型。隐藏一个成员将阻止其被继承。

Java代码
1.public class P{  
2. public static void f(){}  
3.}  
4.class S extends P{  
5. //隐藏,不会继承P.f()  
6. public static void f(){}  
7.} 
public class P{
public static void f(){}
}
class S extends P{
//隐藏,不会继承P.f()
public static void f(){}
}

重载:在某个类中的方法可以重载另一个方法,只要它们具有相同的名字和不同的签名。由调用所指定的重载方法是

在编译期选定的。

Java代码
1.public class T{  
2. public static void f(int i){}  
3. public static void f(String str){}//重载  
4.} 
public class T{
public static void f(int i){}
public static void f(String str){}//重载
}

遮蔽:一个变量、方法或类型可以分别遮蔽在一个闭合的文本范围内的具有相同名字的所有变量、方法或类型。如果

一个实体被遮蔽了,那么你用它的简单名是无法引用到它的;根据实体的不同,有时你根本就无法引用到它。

Java代码
1.public class T {  
2. private static String str = "feild";  
3. public static void main(String[] args) {  
4.  String str = "local";// 遮蔽  
5.  System.out.println(str);// local  
6.  // 可以通过适当的方式来指定  
7.  System.out.println(T.str);// feild  
8. }  
9.}  
10. 
11.public class T {  
12. private final int size;  
13. // 参数属于方法局部范围类变量,遮蔽了同名成员变量  
14. public T(int size) {  
15.  //使用适当的引用来指定  
16.  this.size = size;  
17. }  
18.} 
public class T {
private static String str = "feild";
public static void main(String[] args) {
  String str = "local";// 遮蔽
  System.out.println(str);// local
  // 可以通过适当的方式来指定
  System.out.println(T.str);// feild
}
}

public class T {
private final int size;
// 参数属于方法局部范围类变量,遮蔽了同名成员变量
public T(int size) {
  //使用适当的引用来指定
  this.size = size;
}
}

遮掩:一个变量可以遮掩具有相同名字的一个类型,只要它们都在同一个范围内:如果这个名字被用于变量与类型都

被许可的范围,那么它将引用到变量上。相似地,一个变量或一个类型可以遮掩一个包。遮掩是唯一一种两个名字位

于不同的名字空间的名字重用形式,这些名字空间包括:变量、包、方法或类型。如果一个类型或一个包被遮掩了,

那么你不能通过其简单名引用到它,除非是在这样一个上下文环境中,即语法只允许在其名字空间中出现一种名字:

Java代码
1.public class T {  
2. static String System;  
3. public static void main(String[] args) {  
4.  // !!不能编译,遮掩 java.lang.System  
5.  // !! System.out.println("Hello");  
6.  // 可明确指定  
7.  java.lang.System.out.println("Hello");  
8. }  
9.} 
public class T {
static String System;
public static void main(String[] args) {
  // !!不能编译,遮掩 java.lang.System
  // !! System.out.println("Hello");
  // 可明确指定
  java.lang.System.out.println("Hello");
}
}

40. 构造器中静态常量的引用问题
Java代码
1.class T {  
2. // 先于静态常量t初始化,固可以在构造器中正常使用  
3. private static final int y = getY();  
4. /* 
5.  * 严格按照静态常量声明的先后顺来初始化:即t初始 
6.  * 化完后,才初始化后面的静态常量j,所以构造器中 
7.  * 引用后面的静态常量j时,会是0,即内存清零时的值 
8.  */ 
9. public static final T t = new T();  
10. // 后于静态常量t初始化,不能在构造器中正常使用  
11. private static final int j = getJ();  
12. private final int i;  
13. 
14. static int getY() {  
15.  return 2;  
16. }  
17. 
18. static int getJ() {  
19.  return 2;  
20. }  
21. 
22. // 单例  
23. private T() {  
24.  i = y - j - 1;  
25.//为什么j不是2  
26.  System.out.println("y=" + y + " j=" + j);// y=2 j=0  
27. }  
28. 
29. public int getI() {  
30.  return i;  
31. }  
32. 
33. public static void main(String[] args) {  
34.  System.out.println(T.t.getI());// 1  
35.  System.out.println(T.j);// 2  
36. }  
37.} 
class T {
// 先于静态常量t初始化,固可以在构造器中正常使用
private static final int y = getY();
/*
  * 严格按照静态常量声明的先后顺来初始化:即t初始
  * 化完后,才初始化后面的静态常量j,所以构造器中
  * 引用后面的静态常量j时,会是0,即内存清零时的值
  */
public static final T t = new T();
// 后于静态常量t初始化,不能在构造器中正常使用
private static final int j = getJ();
private final int i;

static int getY() {
  return 2;
}

static int getJ() {
  return 2;
}

// 单例
private T() {
  i = y - j - 1;
//为什么j不是2
  System.out.println("y=" + y + " j=" + j);// y=2 j=0
}

public int getI() {
  return i;
}

public static void main(String[] args) {
  System.out.println(T.t.getI());// 1
  System.out.println(T.j);// 2
}
}

该程序所遇到的问题是由类初始化顺序中的循环而引起的:初始化t时需调用构造函数,而调用构造函数前需初始化所

有静态成员,此时又包括对t的再次初始化。



T类的初始化是由虚拟机对main方法的调用而触发的。首先,其静态域被设置缺省值,其中y、j被初始化为0,而t被初始化为null。接下来,静态域初始器按照其声明的顺序执行赋值动作。第一个静态域是y,它的值是通过调用getY获取的,赋值操作完后结果为2。第一个初始化完成后,再进行第二个静态域的赋值操作,第二个静态域为t,它的值是通过调用T()构造函数来完成的。这个构造器会用二个涉及静态域y、j来初始化非静态域i。通常,读取一个静态域是会引起一个类被初始化,但是我们又已经在初始化T类。JavaVM规范对递归的初始化尝试会直接被忽略掉(按理来说在创建出实例前需初始化完所有的静态域后再来创建实例),这样就导致在静态域被初始化之前就调用了构造器,后面的静态域j将得不到正常的初始化前就在构造器中被使用了,使用时的值为内存分配清零时的,即0。当t初始化完后,再初始化j,此时j得到的值为2,但此时对i的初始化过程来说已经晚了。



在final类型的静态域被初始化之前,存在着读取其值的可能,而此时该静态域包含的还只是其所属类型的缺省值。这

是与直觉想违背的,因为我们通常会将final类型的域看作是常量,但final类型的域只有在其初始化表达式是字面常

量表达式时才是真正的常量。



再看看另一程序:

Java代码
1.class T {  
2. private final static int i = getJ();  
3. private final static int j;  
4. static {  
5.  j = 2;  
6. }  
7. static int getJ() {  
8.  return j;  
9. }  
10. public static void main(String[] args) {  
11.  System.out.println(T.j);// 2  
12.  /* 
13.   * 因为上面的语句已经初使完T类,所以下面语句是 
14.   * 不 会 再引起类的初始化,这里的结果用的是第一 
15.   *  次( 即上面语句)的初始化结果 
16.   */ 
17.  System.out.println(T.i);// 0  
18. }  
19.} 
class T {
private final static int i = getJ();
private final static int j;
static {
  j = 2;
}
static int getJ() {
  return j;
}
public static void main(String[] args) {
  System.out.println(T.j);// 2
  /*
   * 因为上面的语句已经初使完T类,所以下面语句是
   * 不 会 再引起类的初始化,这里的结果用的是第一
   *  次( 即上面语句)的初始化结果
   */
  System.out.println(T.i);// 0
}
}为什么第二个输出是0而不是2呢?这就是因为VM是严格按照你声明的顺序来初始化静态域的,所以前面的引用后面的

静态域时,基本类型就是0,引用类型就会是null。



所以要记住:静态域,甚至是final类型的静态域,可能会在它们被初始化之前,被读走其缺省值。



另,类初始化规则请参考《惰性初始化》一节
分享到:
评论

相关推荐

    java 解惑 java 解惑 java 解惑

    java 解惑 java 解惑 java 解惑 java 解惑 java 解惑 java 解惑

    java解惑(+Java 解惑你知多少)

    你认为自己了解Java多少?你是个爱琢磨的代码侦探吗?你是否曾经花费数天时间去追踪一个由Java或其类库的陷阱和缺陷而导致的bug?你喜欢智力测验吗?本书正好适合你!.. Bloch和Gafter继承了Effective Jaya一书的传统,...

    Java解惑(中文版)_java_java解惑_solve65p_

    《Java解惑(中文版)》是一本专为Java初学者设计的学习资料,旨在帮助读者解答在学习Java过程中遇到的各种困惑。"solve65p"可能代表这本书包含65个问题或主题,每个都深入浅出地进行了讲解,旨在解决初学者在编程...

    JAVA 解惑 java经典

    "JAVA解惑"的主题针对的是Java学习过程中遇到的一些常见问题和难点,旨在帮助开发者深入理解和解决这些问题。以下是基于这个主题和描述可能涵盖的一些关键知识点: 1. **Java基础**:这可能包括变量、数据类型、...

    Java PUZZLE Java 解惑

    Java PUZZLE Java 解惑 Java PUZZLE Java 解惑 Java PUZZLE Java 解惑Java PUZZLE Java 解惑 Java PUZZLE Java 解惑 Java PUZZLE Java 解惑

    Java解惑 中文版

    《Java解惑中文版》是一本专为Java程序员设计的指南,旨在帮助读者解决在编程过程中遇到的各种问题,提升程序的健壮性。本书深入浅出地探讨了Java语言的核心概念、常见疑惑以及最佳实践,旨在使开发者能够编写出更...

    Java解惑.pdf

    这份“Java解惑.pdf”文档很可能包含了解决Java开发者在编程过程中遇到的常见问题和困惑的详细解答。以下是可能涵盖的一些Java相关知识点: 1. **基础语法**:Java的基础语法包括变量、数据类型、运算符、流程控制...

    java解惑 PDF版

    文档《java解惑 PDF版》中列举了95个这样的谜题,每个谜题都旨在帮助开发者理解并纠正一些常见的错误理解。以下是根据提供的部分内容解析的几个相关知识点。 ### 表达式谜题与取余操作符(%)的行为 在Java中,...

    "java解惑" PDF版本

    "java解惑" PDF版本

    JAVA解惑.pdf

    《JAVA解惑》这本书主要针对Java编程中遇到的各种常见问题和困惑进行了解答,旨在帮助开发者深入理解Java语言,提高编程技巧。以下是一些关键的知识点解析: 1. **异常处理**:Java中的异常处理是通过try-catch-...

    Java解惑 布洛克 著;陈昊鹏 译

    《Java解惑》 布洛克 著;陈昊鹏 译 扫描清晰带目录,仅供参阅,请支持正版

    java解惑java解惑java解惑

    "Java解惑"这个主题,显然旨在帮助开发者解决他们在学习和实践中遇到的问题。在Java的世界里,疑惑可能涵盖语法、类库、框架、并发、内存管理等多个方面。下面,我们将深入探讨一些常见的Java解惑知识点。 1. **...

    4,JAVA解惑 高清PDF 下载

    《JAVA解惑》是Java开发者领域的一本经典著作,它被广大...总之,《JAVA解惑》是一本涵盖了Java核心知识点、实战技巧和高级特性的宝典,无论你是Java新手还是老手,都能从中受益匪浅,解决你在Java编程中的种种疑惑。

    java解惑 for all javaer

    讲述如何在程序中避免程序缺陷和程序陷阱的,解惑的过程中,介绍了一些Java编程语言中许多不易被掌握的知识点,其阅读价值非常高,适合具有Java知识的学习者和有编程经验的Java程序员阅读。

    JAVA解惑(JAVA谜题) 中文版(PDF)

    ### JAVA解惑中的关键知识点解析 #### 谜题1:奇数性的判断 在《JAVA解惑》一书中,作者通过一系列实例介绍了Java编程语言中的一些不易掌握的知识点。其中一个例子是关于如何正确判断一个整数是否为奇数。 **原始...

    Java解惑(中文).pdf

    《Java解惑(中文)》是一本专门为Java开发者编写的指南书籍,旨在解决在实际编程过程中遇到的各种疑惑和难题。本书以中文语言呈现,使得国内的Java程序员能够更轻松地理解并应用其中的知识。通过阅读这本书,读者...

    java解惑(包括pdf和答案)

    "java解惑"这个主题旨在帮助初学者理解和解决在学习Java过程中遇到的问题,通过实例来深入浅出地讲解Java的基础知识,同时也强调了实用技巧和注意事项。 "Java解惑"的资料可能包含了两部分:`.chm`和`.pdf`格式的...

Global site tag (gtag.js) - Google Analytics