解惑六、ABC
1public class SolutionTwelve { 2 3 public static void main(String[] args) { 4 5 String letters = "ABC"; 6 char[] numbers = {'1','2','3'}; 7 // 能够直接输出 8 System.out.println(letters + " easy as " + numbers); 9 10 } 11}
你猜上述程序输出的是什么? 是 ABC easy as 123 吗?
你执行了输出操作,你才发现输出的是 ABC easy as [C@6e8cf4c6 ,这么一串丑陋的数字是什么鬼?
实际上我们知道字符串与任何数值的相加都会变为字符串,上述事例也不例外,
numbers输出其实实际上是调用了Object.toString()方法,让numbers转变为'[c' + '@' + 无符号的十六进制数。
改观
那么既然知道了原因,该如何去解决这个问题才是我们应该考虑的问题,恰好String 类为我们提供了String.valueOf(char[] c)把char数组变为字符串
1 // 修改程序 2 System.out.println(letters + " easy as " + String.valueOf(numbers));
实际上,对于上述程序,如果你想打印出char数组,应该重写toString() 方法,使其返回数组中包含的字符串
解惑七、动物庄园
1public class SolutionThirteen { 2 3 public static void main(String[] args) { 4 final String pig = "length: 10"; 5 final String dog = "length: " + pig.length(); 6 7 System.out.println("pig = " + pig + " dog = " + dog); 8 System.out.println("Animal are equal: " + pig == dog); 9 10 } 11}
对于上面的程序会输出什么?你可以能以为第二个输出语句会输出 Animal are equal: true
但是当你了输出语句的时候,你会发现输出的是Animal are equal: false
我相信大部分人都是理解这个意思的,因为 == 比较的是两个值的引用是否相同,那么如何改变呢?
改观
- 我们都知道a.equals(b) 可以用来判断两个字符串的值是否相等
1 final String pig = "length: 10"; 2 final String dog = "length: " + pig.length(); 3 System.out.println("Animal are equal: " + pig.equals(dog));
- 可以使用字符串常量来处理pig 和 dog 的引用问题
1 static final String pig = "length: 10"; 2 static final String dog = "length: 10"; 3 System.out.println("Animal are equal: " + pig == dog);
解惑八
1public class Indecisive { 2 3 public static boolean descritpion(){ 4 try { 5 return true; 6 }finally { 7 return false; 8 } 9 } 10 11 public static void main(String[] args) { 12 System.out.println(descritpion()); 13 } 14}
以上程序输出什么? description() 方法同时返回了两个值,而我们知道一个表达式是不能同时返回两个值的,所以必定有一个返回值是无效的。
原因就是try-finally语句中,finally语句块总是在控制权离开try语句块时执行,无论try语句块是正常结束的,还是非正常结束的,都是如此
解惑九
1public class SolutionThirtyNine { 2 3 4 public static void description(){ 5 try { 6 System.out.println("Hello World"); 7 System.exit(0); 8 }finally { 9 System.out.println("GoodBye World"); 10 } 11 } 12 13 public static void main(String[] args) { 14 description(); 15 } 16}你一定知道finally 在块语句结束前一定会执行,但是你猜上面的程序输出的是什么?会是Hello World,GoodBye World吗?你执行一下才会发现,输出的语句只有一条,Hello World,为什么呢? 正如你说知道的一样,finally语句块确实是会执行,但是执行过程中发生了一件重要的事情,System.exit(0)方法将停止当前线程和所有其他当场死亡的线程。finally语句块并不能给予线程继续执行的权限。也不能将线程恢复继续执行。所以finally语句块并不是没有执行,而是Dead...
改观
1public class SolutionThirtyNineChange { 2 3 public static void main(String[] args) { 4 System.out.println("Hello World"); 5 Runtime.getRuntime().addShutdownHook(new Thread(){ 6 7 public void run(){ 8 System.out.println("GoodBye World"); 9 } 10 }); 11 System.exit(0); 12 } 13
当System.exit()方法被执行时,虚拟机(VM)在关闭前需要进行两项清理工作。首先,需要执行所有的关闭挂钩操作,这些挂钩已经注册到Runtime.addShutdownHook上,这对于释放VM之外的资源很有帮助,务必要为那些必须在VM退出之前发生的行为关闭挂钩
在 System. Exit 被调用时 VM 执行的第二个清理任务与终结器有关。如果 System. RunFinalizersOnExit 或它的魔鬼双胞胎 Runtime.RunFinalizersOnExit 被调用了,那么 VM 将在所有还未终结的对象上调用终结器。这些方法很久以前就已经过时了,而且其原因也很合理。无论什么原因,永远不要调用 System. RunFinalizersOnExit 和 Runtime.RunFinalizersOnExit:它们属于 Java 类库中最危险的方法【ThreadStop】。调用这些方法导致的结果是,终结器会在那些其他线程正在并发操作的对象上运行,从而导致不确定的行为或死锁。
总之,System. Exit 将立即停止所有的程序线程,它并不会使 finally 语句块得到调用,但是它在停止VM 之前会执行关闭挂钩操作。当 VM 被关闭时,请使用关闭挂钩来终止外部资源。通过调用 System.Halt 可以在不执行关闭挂钩的情况下停止 VM,但是很少使用这个方法。
解惑十
1public class SolutionFourty { 2 3 private SolutionFourty internalInstance = new SolutionFourty(); 4 5 public SolutionFourty() throws Exception{ 6 System.out.println("I'm not Coming out"); 7 } 8 9 public static void main(String[] args) { 10 try { 11 SolutionFourty b = new SolutionFourty(); 12 System.out.println("Surprise!"); 13 }catch (Exception e){ 14 System.out.println("I told you so"); 15 } 16 } 17}
main方法调用了SolutionFourty()的构造器,它将抛出一个异常。你可能希望catch子句能够捕获这个异常,并且打印I told you so语句。但是仔细观察后会发现,SolutionFourty实例内部还会包含另外一个SolutionFourty实例,它的构造器也抛出一个异常。无论哪个抛出异常,看起来catch子句都应该捕获它,但是当你运行之后你会发现根本没有捕获异常,它抛出了StackOverflowError异常,为什么呢?
与大多数StackOverflowError异常一样,本程序也包含了一个无限递归,当你调用一个构造器时,实例变量的初始化操作将先于构造器的程序体运行,上述例子中,internalInstance变量的初始化操作递归调用了构造器,而该构造器通过再次调用SolutionFourty构造器而初始化自己的internalInstance域,如此递归循环下去。因为是StackOverflowError,所以程序无法捕获
再来看下面一个例子
1public class Car { 2 3 private static Class engineClass; 4 private Engine engine = (Engine)engineClass.newInstance(); 5 public Car(){} 6} 7public class Engine {}
你觉得这个例子正确吗?
事实上尽管上面的Car中的engineClass.newInstance() 会抛出两个异常InstantiationException 和 IllegalAccessException,但是却没有捕获这两个异常,所以上述例子不会经过编译
改观
1public class Car { 2 3 private static Class engineClass; 4 private static Engine engine = new Engine(); 5 6 public static Engine methodThrowException(){ 7 try { 8 return (Engine)engineClass.newInstance(); 9 } catch (IllegalAccessException e) { 10 throw new AssertionError(); 11 } catch (InstantiationException e) { 12 throw new AssertionError(); 13 } 14 } 15 16 public Car(){} 17}总之,实例变量的初始化操作是先于构造器的程序体运行的。实例初始化操作抛出的异常都会传播给构造器,如果初始化操作抛出的是受检查的异常,那么构造器也必须声明抛出这些异常,但是不应该这样做,以免造成混乱。 想了解更多类的初始化操作,请搜索类的加载顺序来了解更多