简述:
《Java 解惑》 第五章 异常之谜 - 笔记
内容:
谜题36: try中的return不会影响finally中方法执行
package 异常之谜.优柔寡断;
public class Indecisive {
public static void main(String[] args){
System.out.println(decision());
}
static boolean decision() {
try{
return true;
} finally {
return false;
}
}
}
结果输出了false ,
原因:
无论try语句块是正常结束的,还是意外结束的,在一个try-finally语句中,finally语句块总是在控制权离开try语句块时执行
警告:不要使用return、break、continue或throw来退出finally语句块,并且不要让受检查的异常传播到finally语句之外
谜题37:受检查的异常不能随意捕获
1)
说明: try语句中没有声明会抛出任何受检查异常,而在catch字句中要捕获一个类型为E的受检查异常
这就是一个编译期错误
2)
package 异常之谜.极端不可思议;
public class Arcane2 {
public static void main(String[] args) {
try {
// do nothing
} catch (Exception e) {
System.out.println("exception!!");
}
}
}
说明:捕获Exception或Throwable的catch子句是合法的,不管其相对应的try字句的内容为何
3)
Type1.java
public interface Type1 {
void f() throws CloneNotSupportedException;
}
Type2.java
public interface Type2 {
void f() throws InterruptedException;
}
Type3.java
public interface Type3 extends Type1, Type2 {
}
Arcane3.java
public class Arcane3 implements Type3 {
public void f() {
System.out.println("Hello World ");
}
public static void main(String[] args) {
Type3 t3 = new Arcane3();
t3.f();
}
}
谜题38:final 字段只有在的确未赋过值的地方才能被赋值
解释:
Java编译器规定,final字段只有在他的确未赋过值的地方才可以被赋值,而本例中try方法中可能会对USER_ID作赋值
编译器谨慎起见组织了catch中的赋值
解决方式,重构静态语句块中代码为一个辅助方法
package 异常之谜.不受欢迎的宾客;
public class UnwelcomeGuest {
public static final long GUEST_USER_ID = -1;
private static final long USER_ID = getUserIdOrGuest();
private static long getUserIdOrGuest(){
try {
return getUserIdFromEnvironment();
} catch (IdUnavailableException e) {
System.out.println("Logging in as guest");
return GUEST_USER_ID;
}
}
private static long getUserIdFromEnvironment()
throws IdUnavailableException {
throw new IdUnavailableException();
}
public static void main(String[] args){
System.out.println("User ID: " + USER_ID);
}
}
class IdUnavailableException extends Exception {
IdUnavailableException(){}
}
谜题39:System.exit 的中断
package 异常之谜.您好_再见;
public class HelloGoodbye {
public static void main(String[] args) {
try {
System.out.println("Hello World");
System.exit(0);
} finally {
System.out.println("Goodbye World!");
}
}
}
解释: 当调用System.exit时, 虚拟机(VM)在关闭前要执行两项清理工作。首先它执行所有的关闭挂钩操作,这些挂钩已经注册到Runtime.addShutdownHook上。这对释放VM之外的资源很有帮助。 务必要为那些必须在VM退出之前发生的行为关闭挂钩。
package 异常之谜.您好_再见;
public class HelloGoodbye {
public static void main(String[] args) {
System.out.println("Hello World!");
Runtime.getRuntime().addShutdownHook(
new Thread() {
@Override
public void run() {
System.out.println("Goodbye World!");
}
});
System.exit(0); //停止所有程序县城,在停止VM之前会执行关闭挂钩操作
}
}
输出:
谜题40:构造器抛出的异常
package 异常之谜.不情愿的构造器;
public class Reluctant {
private Reluctant internalInstance = new Reluctant();
public Reluctant() throws Exception {
throw new Exception("I'm not coming out");
}
public static void main(String[] args){
try {
Reluctant b = new Reluctant();
System.out.println("Surprise!");
} catch (Exception ex){
System.out.println("I told you so");
}
}
}
输出:
说明:
本程序包含了一个无线递归。当你调用一个构造器时,实例变量的初始化操作将先于构造器的程序体而运行。
实例变量的初始化操作将先于构造器的程序体而运行,本例中,internalInstance变量的初始化操作递归调用
了构造器,而该构造器通过再次调用Reluctant构造器而初始化该变量自己的internalInstance字段,而无限递归下去
谜题41:finally中关闭流也要考虑异常
package 异常之谜.字段和流;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class Test {
static void copy(String src, String dest) throws IOException {
InputStream in = null;
OutputStream out = null;
try {
in = new FileInputStream(src);
out = new FileOutputStream(dest);
byte[] buf = new byte[2014];
int n;
while ((n = in.read(buf)) >= 0){
out.write(buf, 0, n);
}
} finally {
if (in != null)
in.close();
if(out != null)
out.close();
}
}
public static void main(String[] args) throws IOException {
String src = "in.txt";
String dest = "dest.txt";
copy(src, dest);
}
}
说明:
finally中的close方法也可能跑出IOException异常,如果这个正好发生在in.close被调用的时候,那么就会阻止out.close被调用
修改方式, 自定义finally中关闭的方法,从5.0开始,stream中都实现了closeable接口
package 异常之谜.字段和流;
import java.io.Closeable;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class Test {
static void copy(String src, String dest) throws IOException {
InputStream in = null;
OutputStream out = null;
try {
in = new FileInputStream(src);
out = new FileOutputStream(dest);
byte[] buf = new byte[2014];
int n;
while ((n = in.read(buf)) >= 0){
out.write(buf, 0, n);
}
} finally {
closeIgnoringException(in);
closeIgnoringException(out);
}
}
public static void main(String[] args) throws IOException {
String src = "in.txt";
String dest = "dest.txt";
copy(src, dest);
}
private static void closeIgnoringException(Closeable c){
if(c != null){
try {
c.close();
} catch(IOException ex){
// do if it fails
}
}
}
}
谜题42:不要用异常终止循环
package 异常之谜.异常为循环而抛;
public class Loop {
private static boolean thirdElementIsThree(int[] a) {
return a.length>=3 && a[2]==3;
}
public static void main(String[] args) {
int[][] tests = {
{6,5,4,3,2,1}, {1,2}, {1,2,3}, {1,2,3,4}, {1}
};
int successCount = 0;
try {
int i = 0;
while(true){
if(thirdElementIsThree(tests[i++]))
successCount ++;
}
} catch(ArrayIndexOutOfBoundsException e) {
// No more tests to process
}
System.out.println(successCount);
}
}
说明:
不要使用异常控制循环,应该只为异常条件而使用异常
修改为:
package 异常之谜.异常为循环而抛;
public class Loop {
private static boolean thirdElementIsThree(int[] a) {
return a.length>=3 && a[2]==3;
}
public static void main(String[] args) {
int[][] tests = {
{6,5,4,3,2,1}, {1,2}, {1,2,3}, {1,2,3,4}, {1}
};
int successCount = 0;
for (int[] test : tests) {
if(thirdElementIsThree(test))
successCount++;
}
System.out.println(successCount);
}
}
谜题43:实现throw语句要做的事情,但是它绕过了编译器所有异常检查操作
package 异常之谜.异常地危险;
public class Test {
public static void sneakyThrow(Throwable t){
Thread.currentThread().stop();
}
public static void main(String[] args) {
sneakyThrow(new Exception("超级异常"));
System.out.println("execute after throw!");
}
}
运行结束后,控制台没有输出什么东西,看来是被异常终止了
下面用Class.newInstance方法
该方法将传播从空的构造器所抛出的任何异常,包括受检查的异常,使用这个方法,可以有效地绕开在其他情况下都会执行的编译器异常检查
package 异常之谜.异常地危险;
public class Thrower {
private static Throwable t;
private Thrower() throws Throwable {
throw t;
}
public static synchronized void sneakyThrow(Throwable t){
Thrower.t = t;
try {
Thrower.class.newInstance();
} catch (InstantiationException e) {
throw new IllegalArgumentException();
} catch (IllegalAccessException e) {
throw new IllegalArgumentException();
} finally {
Thrower.t = null; // Avoid Memory Leak
}
}
}
备注:finally语句块中赋值为空,是防止内存泄露
解释:Class.newInstance的文档描述,Constructor。newInstance方法通过将构造器抛出的任何异常都包装在一个(受检查的)InvocationTargetException异常中而避免了这个问题
谜题44:删除类
Strange1.java
package 异常之谜.删除类;
public class Strange1 {
public static void main(String[] args) {
try {
Missing m = new Missing();
} catch (java.lang.NoClassDefFoundError ex) {
System.out.println("Got it!");
}
}
}
package 异常之谜.删除类;
public class Strange2 {
public static void main(String[] args) {
Missing m;
try {
m = new Missing();
} catch (java.lang.NoClassDefFoundError ex) {
System.out.println("Got it!");
}
}
}
Missing.java
package 异常之谜.删除类;
public class Missing {
Missing() {}
}
运行Strange1和Strange2之前删除Missing.class 文件,就会发现这两个程序的行为有所不同。
其中一个跑出了一个未被捕获的NoClassDefFoundError异常,而另一个却打印了Got it!
Strange1 运行结果:
Strange2 运行结果:
编写一个能够探测类丢失的程序,用反射来实现
package 异常之谜.删除类;
public class Strange {
public static void main(String[] args) throws Exception {
try {
Object m = Class.forName("Missing").newInstance();
} catch (ClassNotFoundException ex ) {
System.out.println("Missing.class lost !");
}
}
}
谜题45:无限函数递归
package 异常之谜.令人疲惫不堪的测验;
public class Workout {
public static void main(String[] args) {
workHard();
System.out.println("It's nap time.");
}
private static void workHard() {
try {
workHard();
} finally {
workHard();
}
}
}