谜题46:令人混淆的构造器案例
下面的程序打印出什么呢?甚至它是否是合法的呢?public class Confusing {
private Confusing(Object o) {
System.out.println("Object");
}
private Confusing(double[] dArray) {
System.out.println("double array");
}
public static void main(String[] args) {
new Confusing(null);
}
}
这类题有一个通用的解法, 即会调用最不会产生起义, 类型最精确的那个构造器.
谜题47:啊呀!我的猫变成狗了
下面的程序使用了一个Counter类来跟踪每一种家庭宠物叫唤的次数。那么该程序会打印出什么呢?class Counter {
private static int count = 0;
public static final synchronized void increment() {
count++;
}
public static final synchronized int getCount() {
return count;
}
}
class Dog extends Counter {
public Dog() { }
public void woof() { increment(); }
}
class Cat extends Counter {
public Cat() { }
public void meow() { increment(); }
}
public class Ruckus {
public static void main(String[] args) {
Dog dogs[] = { new Dog(), new Dog() };
for (int i = 0; i < dogs.length; i++)
dogs[i].woof();
Cat cats[] = { new Cat(), new Cat(), new Cat() };
for (int i = 0; i < cats.length; i++)
cats[i].meow();
System.out.print(Dog.getCount() + " woofs and ");
System.out.println(Cat.getCount() + " meows");
}
}
我们听到两声狗叫和三声猫叫——肯定是好一阵喧闹——因此,程序应该打印2 woofs and 3 meows,不是吗?不:它打印的是5 woofs and 5 meows。
问题在于Dog和Cat都从其共同的超类那里继承了count域,而count又是一个静态域。每一个静态域在声明它的类及其所有子类中共享一份单一的拷贝,因此Dog和Cat使用的是相同的count域。
谜题48:我所得到的都是静态的
下面的程序,会输出什么?class Dog {
public static void bark() {
System.out.print("woof ");
}
}
class Basenji extends Dog {
public static void bark() {}
}
public class Bark {
public static void main(String args[]) {
Dog woofer = new Dog();
Dog nipper = new Basenji();
woofer.bark();
nipper.bark();
}
}
这个问题简单的说就是子类的静态方法"覆盖"了父类的静态方法,我打引号的意思是,其实没有子类的静态方法覆盖父类的静态方法一说,从根本上讲,静态方法是虚拟机的。
该程序输出的是woof woof. 即最终都是调用了Dog的方法,因为woofer和nipper都是Dog类型。该程序说明,静态方法通过类名来调用才靠谱。
谜题49:比生命更大
下面的程序说起来很简单,就是计算一下腰带尺寸beltSize,也就是CURRENT_YEAR - 1930,也就是今年和1930年之间差多少年。这个程序出了问题,是和初始化顺序有关,你能看出来吗?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.");
}
}
再来回忆一下类的初始化顺序:父类静态变量和父类静态代码块—>子类静态变量和子类静态代码块—>父类非静态变量和父类非静态代码块—>父类构造方法—>子类非静态变量和子类非静态代码块—>子类构造方法。
其中要说明的是,对于静态成员,先他们都获得一个初始值,即0,null之类。然后调用<clinit>()执行类构造器,它包含静态成员的赋值语句和静态初始化块。静态成员的赋值语句和静态初始化块的执行顺序是他们声明的先后顺序。
对于非静态成员,他们也会在实例化的时候获得一个初始值,即0,null之类。然后调用<init>()执行实例构造器,它包含非静态成员赋值语句,非静态初始化块和构造函数的语句。非静态成员赋值语句,非静态初始化块和构造函数的执行顺序为: 构造函数排在最后,非静态成员的赋值语句和非静态初始化块按照声明的顺序。
这个程序输出-1930,自己分析原因吧。
谜题50:不是你的类型
本谜题要测试你对Java的两个最经典的操作符:instanceof和转型的理解程度。下面的三个程序每一个都会做些什么呢?public class Type1 {
public static void main(String[] args) {
String s = null;
System.out.println(s instanceof String);
}
}
public class Type2 {
public static void main(String[] args) {
System.out.println(new Type2() instanceof String);
}
}
public class Type3 {
public static void main(String args[]) {
Type3 t3 = (Type3) new Object();
}
}
第一个程序很有现实意义,java规定,对null做instanceof都返回false,因为如果返回true,你下一步就可能真的去执行转型。
第二个程序也有现实意义,java规定,instanceof左操作数的类型和右边的类型应该必须是满足一方是另一方的子类。否则编译通不过。
第三个程序能通过编译,但是运行会报ClassCastException,暴漏的问题是,编译器无法在编译器确认Object这种根类型的实例不能转为其他类型。
谜题51:那个点是什么?
下面程序有两个类,第一个类用整数坐标来表示平面上的一个点,第二个类在此基础上添加了一点颜色。程序会打印出什么?class Point {
protected final int x, y;
private final String name; // Cached at construction time
Point(int x, int y) {
this.x = x;
this.y = y;
name = makeName();
}
protected String makeName() {
return "[" + x + "," + y + "]";
}
public final String toString() {
return name;
}
}
public class ColorPoint extends Point {
private final String color;
ColorPoint(int x, int y, String color) {
super(x, y);
this.color = color;
}
protected String makeName() {
return super.makeName() + ":" + color;
}
public static void main(String[] args) {
System.out.println(new ColorPoint(4, 2, "purple"));
}
}
这个程序一眼看去,似乎应该打印出[4,2]:purple,但实际上打印的是[4,2]:null, 这个题和49题有相似之处,只是49关系到类的初始化顺序,而这个题关系到实例的初始化。
这个题说明,当父类的构造器调用了一个可能会被覆写的方法时,极有可能会出问题。
谜题52:合计数的玩笑
这个程序,简单的说就是算出从1到n的整数总和n(n+1)/2。它会打印出什么?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());
}
}
看起来这个程序打印出99*100/2=4950似乎没有什么问题,但是它实际上打印出了9900,刚好2倍。
这个题恰恰就考察了静态成员的赋值语句和静态初始化块的执行顺序问题。如同我在49题中说的那样,静态成员最初的时候都会获得一个默认的初始值,然后会执行<clinit>(),它包含了静态成员的赋值和静态初始化快。静态成员的赋值和静态初始化快是按照他们出现的顺序执行的。这样就导致了initialized最开始=false,执行静态初始化块孩子后=true,然后执行静态成员的赋值语句之后又变成false,sum的计算被执行了2次。
谜题53:按你的意愿行事
该题没意思,跳过。谜题54:Null与Void
该题在前面出现过,也跳过。谜题55:特创论
下面的程序中,Creature类有一个静态成员,可以跟踪该类创建了多少个实例。请问它输出什么?public class Creator {
public static void main(String[] args) {
for (int i = 0; i < 100; i++)
Creature creature = new Creature();
System.out.println(Creature.numCreated());
}
}
class Creature {
private static long numCreated = 0;
public Creature() {
numCreated++;
}
public static long numCreated() {
return numCreated;
}
}
该程序不能通过编译,出的问题可能你从来没有遇到过:Creature creature = new Creature();Java语言规范不允许一个本地变量声明语句作为一条语句在for、while或do循环中重复执行。
这个问题也好解决,把Creature creature = new Creature();放到花括号里,因为java有块作用域。还有就是干脆不需要声明变量,直接调用构造函数。
这个题目还有一个问题,就是它不是线程安全的。