1、
Java 虚拟机对栈的深度限制到了某个预设的水平。当超过这个水平时,VM 就抛出StackOverflowError。
2、
Java 的重载解析过程是以两阶段运行的。第一阶段选取所有可获得并且可应用的方法或构造器。第二阶段在第一阶段选取的方法或构造器中选取最精确的一个。如果一个方法或构造器可以接受传递给另一个方法或构造器的任何参数,那么我们就说第一个方法比第二个方法缺乏精确性。
要想用一个null 参数来调用Confusing(Object)构造器,你需要这样写代码:new Confusing((Object)null)。这可以确保只有Confusing(Object)是可应用的。更一般地讲,要想强制要求编译器选择一个精确的重载版本,需要将实际的参数转型为形式参数所声明的类型。
3、
每一个静态域在声明它的类及其所有子类中共享一份单一的拷贝。如果你需要让每一个子类都具有某个域的单独拷贝,那么你必须在每一个子类中声明一个单独的静态域。如果每一个实例都需要一个单独的拷贝,那么你可以在基类中声明一个非静态域
在设计一个类的时候,如果该类构建于另一个类的行为之上,那么你有两种选择:一种是继承,即一个类扩展另一个类;另一种是组合,即在一个类中包含另一个类的一个实例
4、
对静态方法的调用不存在任何动态的分派机制。当一个程序调用了一个静态方法时,要被调用的方法都是在编译时刻被选定的,而这种选定是基于修饰符的编译期类型而做出的,修饰符的编译期类型就是我们给出的方法调用表达式中圆点左边部分的名字。
当你在阅读一个Java 程序时,你会期望类被用作为静态方法的修饰符,这些静态方法都是被静态分派的,而表达式被用作为实例方法的修饰符,这些实例方法都是被动态分派的。通过耦合类和变量的不同的命名规范,我们可以提供一个很强的可视化线索,用来表明一个给定的方法调用是动态的还是静态的。
5、
public class Elvis {
public static final Elvis INSTANCE = new Elvis();
private final int beltSize;
private static final int CURRENT_YEAR =
Calendar.getInstance().get(Calendar.YEAR);
private Elvis() {
beltSize = CURRENT_YEAR - 1930;
}
public int beltSize() {
return beltSize;
}
public static void main(String[] args) {
System.out.println("Elvis wears a size " +
INSTANCE.beltSize() + " belt.");
}
}
在final 类型的静态域被初始化之前,存在着读取它的值的可能,而此时该静态域包含的还只是其所属类型的缺省值。要想订正一个类初始化循环,需要重新对静态域的初始器进行排序,使得每一个初始器都出现在任何依赖于它的初始器之前。
类的初始化是由虚拟机对其main 方法的调用而触发的。首先,其静态域被设置为缺省值,其中INSTANCE 域被设置为null,CURRENT_YEAR 被设置为0。接下来,静态域初始器按照其出现的顺序执行。第一个静态域是INSTANCE,它的值是通过调用Elvis()构造器而计算出来的。这个构造器会用一个涉及静态域CURRENT_YEAR 的表达式来初始化beltSize。通常,读取一个静态域是会引起一个类被初始化的事件之一,但是我们已经在初始化Elvis 类了。递归的初始化尝试会直接被忽略掉[JLS 12.4.2, 第3 步]。因此,CURRENT_YEAR 的值仍旧是其缺省值0。这就是为什么Elvis 的腰带尺寸变成了-1930 的原因。最后,从构造器返回以完成Elvis 类的初始化,假设我们是在2006 年运行该程序,那么我们就将静态域CURRENT_YEAR 初始化成了2006。遗憾的是,这个域现在所具有的正确值对于向Elvis.INSTANCE.beltSize 的计算施加影响来说已经太晚了,beltSize 的值已经是-1930 了。
在一个final 类型的实例域被赋值之前,存在着取用其值的可能,而此时它包含的仍旧是其所属类型的缺省值。循环的类初始化是无法避免的灾难,但是循环的实例初始化总是可以且总是应该避免的。
无论何时,只要一个构造器调用了一个已经被其子类覆写了的方法,那么该问题就会出现,因为以这种方式被调用的方法总是在实例被初始化之前执行。要想避免这个问题,就千万不要在构造器中调用可覆写的方法,直接调用或间接调用都不行。
6、
instanceof 操作符被定义为在其左操作数为null 时返回false,如果两个操作数的类型都是类,其中一个必须是另一个的子类型。
7、
class Cache {
static {
initializeIfNecessary();
}
private static int sum;
public static int getSum() {
initializeIfNecessary();
return sum;
}private static boolean initialized = false;
private static synchronized void initializeIfNecessary() {
if (!initialized) {
for (int i = 0; i < 100; i++)
sum += i;
initialized = true;
}
}
}
public class Client {
public static void main(String[] args) {
System.out.println(Cache.getSum());
}
}
类初始化是按照静态初始器在源代码中出现的顺序去执行这些初始器的。Cache 类有两个静态初始器:在类顶端的一个static 语句块,以及静态域initialized 的初始化。静态语句块是先出现的,它调用了方法initializeIfNecessary,该方法将测试initialized 域。因为该域还没有被赋予任何值,所以它具有缺省的布尔值false。与此类似,sum 具有缺省的int 值0。因此,initializeIfNecessary 方法执行的正是你所期望的行为,将4,950添加到了sum 上,并将initialized 设置为true。在静态语句块执行之后,initialized 域的静态初始器将其设置回false,从而完成Cache 的类初始化。遗憾的是,sum 现在包含的是正确的缓存值,但是initialized 包含的却是false:Cache 类的两个关键状态并未同步。改用积极初始化:
class Cache {
private static final int sum = computeSum();
private static int computeSum() {
int result = 0;
for (int i = 0; i < 100; i++)
result += i;
return result;
}
public static int getSum() {
return sum;
}
}
我们使用了一个助手方法来初始化sum。助手方法通常都优于静态语句块,因为它让你可以对计算命名。