Think in Java第四版 读书笔记6第12章 异常处理

12.1 概念

异常可以将“在正常时候执行的代码”和“发生错误时的代码”相分离,达到结构清晰的目的。
a.受检查异常checkedException 编译器强制要求我们必须处理的异常,典型:除了RuntimeException及其子类+各种Error,通常我们需要对这些异常进行处理
b.非检查异常uncheckedException我们不进行处理Java不会报错 因为Java虚拟机会对他们进行处理,典型:RuntimeException及其子类+各种Error,通常我们处理不了这些异常,或者处理的代价远大于抛出。
c.Exception可以分为运行时异常(可以不捕获)和非运行时异常(必须捕获)
在这里插入图片描述

12.2 基本异常

异常分为两种
1.当前环境可以处理,那就在当前方法处理
2.当前环境不可以处理,需要将异常抛给方法调用者,让其处理
异常是一个事务(没有理解,是指抛出异常后所有执行无效?)

12.2.1异常参数

异常的创建:两种,一种无参 一种是带一个字符串作为参数
throw可以将异常抛出给调用者,异常抛出时退出当前作用域或方法
Throwable 是异常的基类,错误信息通常包含在异常类名称或保存在异常对象内部,接受异常的地方通常通过这些信息来处理异常

12.3 捕获异常

基本语法

try {
	//可能异常
} catch (ExceptionType1 e1) {
	//处理异常
} catch (ExceptionType2 e2) {
	//处理异常
} catch (ExceptionType3 e3) {
	//处理异常
} finally{
	//必定执行的代码段
}

终止与恢复
前面我们提到异常可以抛出给调用者(终止模型)也可以在方法内部处理,使其回归到正常状态(恢复模型)
虽然恢复模型看起来很有诱惑力,但是实际执行的时候可能处理错误的代码非常难以维护,所以通常在这种方式的选择上需要一定的平衡

12.4 自定义异常

public class SimpleException extends Exception{

}


public class InheritingExceptions {
	int x = 0;

	public void f() throws SimpleException {
		System.out.println("Throw SimpleException from f()");
		x++;
		if (x == 1) {
			throw new SimpleException();
		}
	}

	public static void main(String[] args) {
		InheritingExceptions sed = new InheritingExceptions();
		try {
			sed.f();
		} catch (SimpleException e) {
			System.out.println("Caught it!");
		}
	}
}
//本例看起来好像没有什么用,但实际应用时这种例子可以覆盖大部分情况,就像之前提到的,异常类的名字很关键

定义接受字符串做参数的构造器

public class MyException extends Exception {
	public MyException() {
	}

	public MyException(String msg){
		//增加含参构造方法,也很简单
		super(msg);
	}
}

public class FullConstructors {
	public static void f() throws MyException{
		System.out.println("Throw MyException from f()");
		throw new MyException();
	}
	
	public static void g() throws MyException{
		System.out.println("Throw MyException from g()");
		throw new MyException("Originated in g()");
	}
	
	public static void main(String[] args) {
		try {
			f();
		} catch (MyException exception) {
			//exception.printStackTrace();//输出到标准错误流
			exception.printStackTrace(System.out);
		}
		
		try {
			g();
		} catch (MyException exception) {
			exception.printStackTrace(System.out);
		}
	}

}
/*输出
Throw MyException from f()
ex12d4.MyException
	at ex12d4.FullConstructors.f(FullConstructors.java:6)
	at ex12d4.FullConstructors.main(FullConstructors.java:16)
Throw MyException from g()
ex12d4.MyException: Originated in g()
	at ex12d4.FullConstructors.g(FullConstructors.java:11)
	at ex12d4.FullConstructors.main(FullConstructors.java:22) 
 **/

12.4.1 异常与记录日志
抛出异常

import java.util.logging.*;
import java.io.*;

class LoggingException extends Exception {
  private static Logger logger =
    Logger.getLogger("LoggingException");
  public LoggingException() {
    StringWriter trace = new StringWriter();
    printStackTrace(new PrintWriter(trace));
    logger.severe(trace.toString());//利用输入输出流向logger对象写入错误信息
    
  }
}

public class LoggingExceptions {
  public static void main(String[] args) {
    try {
      throw new LoggingException();//抛出异常
    } catch(LoggingException e) {
      System.err.println("Caught " + e);
    }
    try {
      throw new LoggingException();
    } catch(LoggingException e) {
      System.err.println("Caught " + e);
    }
  }
} /* Output: 
Jun 18, 2019 8:57:36 AM ex12d4d1.LoggingException <init>
SEVERE: ex12d4d1.LoggingException
	at ex12d4d1.LoggingExceptions.main(LoggingExceptions.java:20)

Caught ex12d4d1.LoggingException
Jun 18, 2019 8:57:36 AM ex12d4d1.LoggingException <init>
SEVERE: ex12d4d1.LoggingException
	at ex12d4d1.LoggingExceptions.main(LoggingExceptions.java:25)

Caught ex12d4d1.LoggingException
*///:~

捕获异常

import java.util.logging.*;
import java.io.*;

public class LoggingExceptions2 {
  private static Logger logger =
    Logger.getLogger("LoggingExceptions2");
  static void logException(Exception e) {
    StringWriter trace = new StringWriter();
    e.printStackTrace(new PrintWriter(trace));
    logger.severe(trace.toString());
  }
  public static void main(String[] args) {
    try {
      throw new NullPointerException();
    } catch(NullPointerException e) {
      logException(e);//捕获异常
    }
  }
} /* Output:
Jun 18, 2019 9:22:13 AM ex12d4d1.LoggingExceptions2 logException
SEVERE: java.lang.NullPointerException
	at ex12d4d1.LoggingExceptions2.main(LoggingExceptions2.java:16)

*///:~

自定义异常

//自定义异常
class MyException2 extends Exception {
  private int x;
  public MyException2() {}
  public MyException2(String msg) { super(msg); }
  public MyException2(String msg, int x) {
    super(msg);
    this.x = x;
  }
  public int val() { return x; }
  public String getMessage() {//看起来该方法初始化时会被父类调用
    return "Detail Message: "+ x + " "+ super.getMessage();
  }
}

public class ExtraFeatures {
  public static void f() throws MyException2 {//抛出无参自定义异常
    System.out.println("Throwing MyException2 from f()");
    throw new MyException2();
  }
  public static void g() throws MyException2 {//抛出包含string的自定义异常
    System.out.println("Throwing MyException2 from g()");
    throw new MyException2("Originated in g()");
  }
  public static void h() throws MyException2 {//抛出包含string和int值的自定义异常
    System.out.println("Throwing MyException2 from h()");
    throw new MyException2("Originated in h()", 47);
  }
  public static void main(String[] args) {
    try {
      f();
    } catch(MyException2 e) {
      e.printStackTrace(System.out);
    }
    try {
      g();
    } catch(MyException2 e) {
      e.printStackTrace(System.out);
    }
    try {
      h();
    } catch(MyException2 e) {
      e.printStackTrace(System.out);
      System.out.println("e.val() = " + e.val());
    }
  }
} /* Output:
Throwing MyException2 from f()
ex12d4d1.MyException2: Detail Message: 0 null
	at ex12d4d1.ExtraFeatures.f(ExtraFeatures.java:22)
	at ex12d4d1.ExtraFeatures.main(ExtraFeatures.java:34)
Throwing MyException2 from g()
ex12d4d1.MyException2: Detail Message: 0 Originated in g()
	at ex12d4d1.ExtraFeatures.g(ExtraFeatures.java:26)
	at ex12d4d1.ExtraFeatures.main(ExtraFeatures.java:39)
Throwing MyException2 from h()
ex12d4d1.MyException2: Detail Message: 47 Originated in h()
	at ex12d4d1.ExtraFeatures.h(ExtraFeatures.java:30)
	at ex12d4d1.ExtraFeatures.main(ExtraFeatures.java:44)
e.val() = 47
*///:~

12.5 异常说明

在方法中告知调用者可能抛出的异常就是异常说明
比如FileWriter的append方法可能会抛出异常,它的方法是这样的

   /**
     * Appends the specified character sequence to this writer.
     *
     * <p> An invocation of this method of the form <tt>out.append(csq)</tt>
     * behaves in exactly the same way as the invocation
     *
     * <pre>
     *     out.write(csq.toString()) </pre>
     *
     * <p> Depending on the specification of <tt>toString</tt> for the
     * character sequence <tt>csq</tt>, the entire sequence may not be
     * appended. For instance, invoking the <tt>toString</tt> method of a
     * character buffer will return a subsequence whose content depends upon
     * the buffer's position and limit.
     *
     * @param  csq
     *         The character sequence to append.  If <tt>csq</tt> is
     *         <tt>null</tt>, then the four characters <tt>"null"</tt> are
     *         appended to this writer.
     *
     * @return  This writer
     *
     * @throws  IOException
     *          If an I/O error occurs
     *
     * @since  1.5
     */
    public Writer append(CharSequence csq) throws IOException {
        if (csq == null)
            write("null");
        else
            write(csq.toString());
        return this;
    }

12.6 捕获所有异常

catch (Exception e) {
			// TODO: handle exception
		}

这样的写法可以捕获所有异常,因为Exception是所有异常的基类
但是这要写在捕获异常的末端,因为如果写在前面就会提前捕获,而子类exception的信息会更加详细
比如这样

try {
	object.toString();
} catch (NullPointerException exception) {

} catch (Exception e) {
	// TODO: handle exception
}

例子:打印异常状态的方法
常用方法有这几个

	e.getMessage();
	e.getLocalizedMessage();
	e.toString();
	
	e.printStackTrace();
	e.printStackTrace(PrintStream);
	e.printStackTrace(PrintWriter);

例子

    // Demonstrating the Exception Methods.
    
    public class ExceptionMethods {
      public static void main(String[] args) {
        try {
          throw new Exception("My Exception");
        } catch(Exception e) {
          System.out.println("1 Caught Exception");
          System.out.println("2 getMessage():" + e.getMessage());
          System.out.println("3 getLocalizedMessage():" +
            e.getLocalizedMessage());
          System.out.println("4 toString():" + e);
          System.out.print("5 printStackTrace():");
          e.printStackTrace(System.out);
        }
      }
    } /* Output:
    1 Caught Exception
    2 getMessage():My Exception
    3 getLocalizedMessage():My Exception
    4 toString():java.lang.Exception: My Exception
    5 printStackTrace():java.lang.Exception: My Exception
    	at ex12d4.ExceptionMethods.main(ExceptionMethods.java:7)
    *///:~

可以发现他们的输出信息 越来越详细。

12.6.1 栈轨迹

// Programmatic access to stack trace information.

public class WhoCalled {
	static void methoedF() {
		// Generate an exception to fill in the stack trace
		try {
			throw new Exception();
		} catch (Exception e) {
			// e.getStackTrace() 是一个StackTraceElement[]数组 存储了调用堆栈
			// 可以打印方法的调用链
			 for(StackTraceElement ste : e.getStackTrace())
			 System.out.println(ste.getMethodName());
		}
	}

	static void methoedG() {
		methoedF();
	}

	static void methoedH() {
		methoedG();
	}

	public static void main(String[] args) {
		methoedF();
		System.out.println("--------------------------------");
		methoedG();
		System.out.println("--------------------------------");
		methoedH();
	}
} /*
 * Output:
methoedF
main
--------------------------------
methoedF
methoedG
main
--------------------------------
methoedF
methoedG
methoedH
main
 */// :~

把catch中的代码改为e.printStackTrace(); 输出如下

java.lang.Exception
	at ex12d6.WhoCalled.methoedF(WhoCalled.java:9)
	at ex12d6.WhoCalled.main(WhoCalled.java:28)
java.lang.Exception--------------------------------

	at ex12d6.WhoCalled.methoedF(WhoCalled.java:9)
	at ex12d6.WhoCalled.methoedG(WhoCalled.java:20)
--------------------------------
	at ex12d6.WhoCalled.main(WhoCalled.java:30)
java.lang.Exception
	at ex12d6.WhoCalled.methoedF(WhoCalled.java:9)
	at ex12d6.WhoCalled.methoedG(WhoCalled.java:20)
	at ex12d6.WhoCalled.methoedH(WhoCalled.java:24)
	at ex12d6.WhoCalled.main(WhoCalled.java:32)

12.6.2 重新抛出异常

在catch语句中捕获到异常之后仍然可以继续将异常抛出给上级调用者
比如这样

catch(MyException2 e) {
e.printStackTrace(System.out);
throw e;
}

直接通过throw将异常抛给上一级将会使得异常和起初的异常信息保持一致 而通过e.fillInStackTrace();抛出,则会打印新的调用者的相关异常信息

// Demonstrating fillInStackTrace()

public class Rethrowing {
  public static void f() throws Exception {
    System.out.println("originating the exception in f()");
    throw new Exception("thrown from f()");
  }
  public static void g() throws Exception {
    try {
      f();
    } catch(Exception e) {
      System.out.println("Inside g(),e.printStackTrace()");
      e.printStackTrace(System.out);
      throw e;
    }
  }
  public static void h() throws Exception {
    try {
      f();
    } catch(Exception e) {
      System.out.println("Inside h(),e.printStackTrace()");
      e.printStackTrace(System.out);
      throw (Exception)e.fillInStackTrace();
    }
  }
  public static void main(String[] args) {
    try {
      g();
    } catch(Exception e) {
      System.out.println("main: printStackTrace()");
      e.printStackTrace(System.out);
    }
    System.out.println("=================================");
    try {
      h();
    } catch(Exception e) {
      System.out.println("main: printStackTrace()");
      e.printStackTrace(System.out);
    }
  }
} /* Output:
originating the exception in f()
Inside g(),e.printStackTrace()
java.lang.Exception: thrown from f()
	at ex12d4d1.Rethrowing.f(Rethrowing.java:7)
	at ex12d4d1.Rethrowing.g(Rethrowing.java:11)
	at ex12d4d1.Rethrowing.main(Rethrowing.java:29)
main: printStackTrace()
java.lang.Exception: thrown from f()
	at ex12d4d1.Rethrowing.f(Rethrowing.java:7)
	at ex12d4d1.Rethrowing.g(Rethrowing.java:11)
	at ex12d4d1.Rethrowing.main(Rethrowing.java:29)
=================================
originating the exception in f()
Inside h(),e.printStackTrace()
java.lang.Exception: thrown from f()
	at ex12d4d1.Rethrowing.f(Rethrowing.java:7)
	at ex12d4d1.Rethrowing.h(Rethrowing.java:20)
	at ex12d4d1.Rethrowing.main(Rethrowing.java:36)
main: printStackTrace()
java.lang.Exception: thrown from f()
	at ex12d4d1.Rethrowing.h(Rethrowing.java:24)
	at ex12d4d1.Rethrowing.main(Rethrowing.java:36)
*///:~

16.3异常链

概念:在捕获一个异常后抛出另一个异常,并且保留原始的异常信息。
JDK1.4之前要程序员自己编写程序保留原始信息,现在可以通过Throwable子类构造器接受一个cause参数来保留,cause保存的就是原始信息,这样就可以传给新的异常了,这样一级级追踪,形成异常链
Throwable的三个字类提供了带cause参数的构造器,他们是Error Exception RuntimeException。要把其他类型异常链连接起来要使用initCause方法而不是构造器
如何链接其他异常的例子:

class DynamicFieldsException extends Exception {
}

public class DynamicFields {
	private Object[][] fields;// 存值的二维数组

	public DynamicFields(int initialSize) {// 二维数组固定为initialSize*2
		fields = new Object[initialSize][2];// 可以想象成大小为initialSize的map
		for (int i = 0; i < initialSize; i++)
			fields[i] = new Object[] { null, null }; // 数组内容初始化为null
	}

	public String toString() {// 遍历二维数组 将每一个元素输出
		StringBuilder result = new StringBuilder();
		for (Object[] obj : fields) {
			result.append(obj[0]);
			result.append(": ");
			result.append(obj[1]);
			result.append("\n");
		}
		return result.toString();
	}

	private int hasField(String id) {// 判断“key”集合是否包含指定字符串
		for (int i = 0; i < fields.length; i++)
			if (id.equals(fields[i][0]))
				return i;
		return -1;
	}

	private int getFieldNumber(String id) throws NoSuchFieldException {
		// 调用hasField方法
		// 判断“key”集合是否包含指定字符串,如果包含,返回下标
		// 否则
		// 抛出NoSuchFieldException(NoSuchFieldException是java异常的一种)
		int fieldNum = hasField(id);
		if (fieldNum == -1)
			throw new NoSuchFieldException();
		return fieldNum;
	}

	private int makeField(String id) {
		// 遍历二维数组 如果指定key是空,给所有为空的地方赋值为id并返回index
		for (int i = 0; i < fields.length; i++)
			if (fields[i][0] == null) {
				fields[i][0] = id;
				return i;
			}
		// 走到这里说明没有空字段了 创建一个新的二维数组,行数比原来+1
		Object[][] tmp = new Object[fields.length + 1][2];
		for (int i = 0; i < fields.length; i++)
			// copy旧值给新的数组
			tmp[i] = fields[i];
		for (int i = fields.length; i < tmp.length; i++)
			// 新增的空间赋值为null, null
			tmp[i] = new Object[] { null, null };
		fields = tmp;
		// Recursive call with expanded fields: 递归调用
		return makeField(id);//下一次调用给最后一个元素key赋值并返回最后一个元素的下标
	}

	public Object getField(String id) throws NoSuchFieldException {
		return fields[getFieldNumber(id)][1];
	}

	public Object setField(String id, Object value)//给指定key值的地方赋值为value 如果没有指定key(但是有key是null的,使用空位),没有空位 则新加一个
			throws DynamicFieldsException {
		if (value == null) {//如果值为空 抛出空指针异常
			// Most exceptions don't have a "cause" constructor.
			// In these cases you must use initCause(),
			// available in all Throwable subclasses.
			DynamicFieldsException dfe = new DynamicFieldsException();
			dfe.initCause(new NullPointerException());
			throw dfe;
		}
		int fieldNumber = hasField(id);//找到包含指定id的位置
		if (fieldNumber == -1)//如果没有  新加一个
			fieldNumber = makeField(id);
		Object result = null;
		try {
			result = getField(id); // Get old value
		} catch (NoSuchFieldException e) {
			// Use constructor that takes "cause":
			throw new RuntimeException(e);
		}
		fields[fieldNumber][1] = value;
		return result;
	}

	public static void main(String[] args) {
		DynamicFields df = new DynamicFields(3); //想象成长度为3的map
		System.out.println(df);
		try {
			df.setField("d", "A value for d");
			df.setField("number", 47);
			df.setField("number2", 48);
			System.out.println(df);
			df.setField("d", "A new value for d");
			df.setField("number3", 11);
			System.out.println("df: " + df);
			System.out.println("df.getField(\"d\") : " + df.getField("d"));
			Object field = df.setField("d", null); // 尝试给key为“d”的“map”赋值空值 报错
		} catch (NoSuchFieldException e) {
			e.printStackTrace(System.out);
		} catch (DynamicFieldsException e) {
			e.printStackTrace(System.out);
		}
	}
} /*
 * Output:
null: null
null: null
null: null

d: A value for d
number: 47
number2: 48

df: d: A new value for d
number: 47
number2: 48
number3: 11

df.getField("d") : A new value for d
ex12d6.DynamicFieldsException
	at ex12d6.DynamicFields.setField(DynamicFields.java:77)
	at ex12d6.DynamicFields.main(DynamicFields.java:107)
Caused by: java.lang.NullPointerException
	at ex12d6.DynamicFields.setField(DynamicFields.java:78)
	... 1 more
 */// :~

12.7 Java 标准异常

Java通常有Error和Exception 但程序员通常只关心exception
Java的异常通常可以从名字看出关键问题发生在什么地方
Java异常分类概要图
引用来自https://blog.csdn.net/claram/article/details/48133727
的异常分类图
在这里插入图片描述
对比我之前的图片更适合理解
在这里插入图片描述

12.7.1 RuntimeException

一个简单的例子

if (args == null) {
	throw new NullPointerException();
}

如果我们需要为每一个方法参数都进行空指针判断 那就太冗余了,而且代码会显得混乱。其实java本身会检测标准运行时异常,这些异常会被Java抛出(但是你仍然可以写自己代码捕获异常以规避空指针异常)
运行时异常RuntimeException是基类,会被java检测并抛出(NullPointerException属于运行时异常),他们也被称为“不受检查异常”(可以不用接受程序的代码检测,仅靠Java来自动捕获)
如果没有捕获异常而发生了异常,会怎么样?

// Ignoring RuntimeExceptions.
// {ThrowsException}

public class NeverCaught {
  static void f() {
    throw new RuntimeException("From f()");
  }
  static void g() {
    f();
  }
  public static void main(String[] args) {
    g();
  }
} ///:~
/*
Exception in thread "main" java.lang.RuntimeException: From f()
at ex12d6.NeverCaught.f(NeverCaught.java:7)
at ex12d6.NeverCaught.g(NeverCaught.java:10)
at ex12d6.NeverCaught.main(NeverCaught.java:13)
*/

12.8 使用finally清理

finally语句中的代码都是会被执行,finally常被用于进行状态恢复

class FourException extends Exception {
}

public class AlwaysFinally {
	public static void main(String[] args) {
		System.out.println("Entering first try block");
		try {
			System.out.println("Entering second try block");
			try {
				throw new FourException();
			} finally {
				System.out.println("finally in 2nd try block");
			}
		} catch (FourException e) {
			System.out.println("Caught FourException in 1st try block");
		} finally {
			System.out.println("finally in 1st try block");
		}
		//即使发生异常 finally总会执行
	}
} /*
 * Output: 
Entering first try block
Entering second try block
finally in 2nd try block
Caught FourException in 1st try block
finally in 1st try block
*/// :~

12.8.2 在return中使用finally

public class MultipleReturns {
  public static void f(int i) {
    System.out.println("Initialization that requires cleanup");
    try {
      System.out.println("Point 1");
      if(i == 1) return;
      System.out.println("Point 2");
      if(i == 2) return;
      System.out.println("Point 3");
      if(i == 3) return;
      System.out.println("End");
      return;
    } finally {
      System.out.println("Performing cleanup");
    }
  }
  public static void main(String[] args) {
    for(int i = 1; i <= 4; i++)
      f(i);
  }
} /* Output:
Initialization that requires cleanup
Point 1
Performing cleanup
Initialization that requires cleanup
Point 1
Point 2
Performing cleanup
Initialization that requires cleanup
Point 1
Point 2
Point 3
Performing cleanup
Initialization that requires cleanup
Point 1
Point 2
Point 3
End
Performing cleanup
*///:~

由于finally有必定执行的特征,在有多个返回点的程序中可以在finally中执行清理工作 而不会被遗漏

12.8.3 缺憾:异常丢失

//多个异常发生时 只会捕获一个异常,其他异常会被忽略。异常的优先级与与位置有关,优先级如下
// finally语句中>外层try catch中捕获的异常>同级异常先后发生的顺序
//在finally中直接进行return返回将会忽略所有异常。
class VeryImportantException extends Exception {
  public String toString() {
    return "A very important exception!";
  }
}

class HoHumException extends Exception {
  public String toString() {
    return "A trivial exception";
  }
}

public class LostMessage {
  void f() throws VeryImportantException {
    throw new VeryImportantException();
  }
  void dispose() throws HoHumException {
    throw new HoHumException();
  }
  public static void main(String[] args) {
    try {
      LostMessage lm = new LostMessage();
      try {
    	  lm.f();
      } finally {
    	  lm.dispose();
      }
    } catch(Exception e) {
      System.out.println(e);
    }
  }
} /* Output:
A trivial exception
*///:~

12.9 异常的限制

(参考https://zachary-guo.iteye.com/blog/346102)
这一节书中讲的比较乱,我自己找了一个网址比较清晰
异常处理的继承
part1关于构造方法(只关心父类构造方法抛出的非Runtime异常)
1.父类有无参构造函数且未声明任何异常的抛出
子类默认调用父类无参构造方法,因此子类可以抛出或不抛出异常
2.父类有无参构造函数且声明了非Runtime异常抛出
子类默认调用父类无参构造方法,因此子类必须抛出或者捕获父类声明的异常或者异常的父类,当然子类也可以抛出其他异常
3.父类有无参构造函数且声明了Runtime异常抛出
如果父类声明的是runtime异常,则子类可以不进行抛出或者捕获(Java会自动捕获)
4.父类有有参的构造函数
不管父类的有参构造函数是否抛出异常,子类都不必关心,因为子类只会调用父类的无参构造方法
part2关于普通方法(只关心父类普通方法抛出的非Runtime异常)
5.父类的方法未声明任何异常的抛出
子类覆写此方法时不能声明任何异常(除了 RuntimeException)的抛出。
Parent P = new Child();
/**

  • 由于 Parent 声明调用 hello 方法时不会出现异常,可是 Child 覆写 hello 方法时却声明需要抛出异常。
  • 根据多态,以下的调用将调用 Child 的 hello 方法(需捕捉异常),而调用者却是 Parent,Parent 声明
  • 没有异常。因此暴露給我们的是 Parent 引用,我们不会去捕捉异常,这样就矛盾了,所以子类覆写父类时
  • 不能抛出异常。
    */
    p.hello();
    6.父类的方法声明了异常(RuntimeException)的抛出
    子类可以不做异常抛出的声明,也可以声明任何 RuntimeException 的抛出
    7.父类的方法声明了异常(非 RuntimeException)的抛出
    子类可以不做异常抛出的声明。一旦声明了,那么其异常类型至多和父类声明的异常类型一样,包括可以是父类异常类型的子类。
    part3关于继承父类和实现接口
    8.子类继承父类的同时还实现了某一接口
    若接口和父类有同样的函数签名,声明抛出的非Runtime异常类型应该与父类保持一致,这个与第七点类似。(个人观点)否则在多态的时候无法确认是否捕获了正确的异常

12.10 构造器

// 注意构造器中的异常(构造还没有完成,有些异常需要酌情考虑,finally清理程序不是万能的)
import java.io.*;

public class InputFile {
  private BufferedReader in;
  public InputFile(String fname) throws Exception {//构造器
    try {
      in = new BufferedReader(new FileReader(fname));
      // 其他可能引起异常的code
    } catch(FileNotFoundException e) {
      System.out.println("Could not open " + fname);
      //文件还没有打开,所以不需要close
      throw e;
    } catch(Exception e) {
      // 其他异常,需要close文件
      try {
        in.close();//close操作也会失败,需要继续try catch
      } catch(IOException e2) {
        System.out.println("in.close() unsuccessful");
      }
      throw e; // 重新抛出
    } finally {
      //不应该在这里close文件
    }
  }
  public String getLine() {//返回文件的下一行内容
    String s;
    try {
      s = in.readLine();
    } catch(IOException e) {
      throw new RuntimeException("readLine() failed");
    }
    return s;
  }
  public void dispose() {//释放资源的方法 不要试图在finalize方法关闭资源 而应该告诉调用者
    try {
      in.close();
      System.out.println("dispose() successful");
    } catch(IOException e2) {
      throw new RuntimeException("in.close() failed");
    }
  }
} ///:~

确保正确清理资源的例子 在创建出一个需要释放资源的对象时,立即紧跟上一个finally语句用于清理资源

public class Cleanup {//对于在构造方法中可能有异常的对象的创建,使用嵌套的try catch更安全
  public static void main(String[] args) {
    try {
      InputFile in = new InputFile("Cleanup.java");
      try {
        String s;
        int i = 1;
        while((s = in.getLine()) != null)
          ; // Perform line-by-line processing here...
      } catch(Exception e) {//构建成功的异常处理
        System.out.println("Caught Exception in main");
        e.printStackTrace(System.out);
      } finally {//构造成功必定会清理  //紧跟的资源清理finally
        in.dispose();
      }
    } catch(Exception e) {//构建失败的异常处理
      System.out.println("InputFile construction failed");
    }
  }
} /* Output:
*///:~

每一个需要清理的对象都必须跟着 try-finally

class NeedsCleanup { // 构造器不会抛出异常的类
  private static long counter = 1;
  private final long id = counter++;
  public void dispose() {
    System.out.println("NeedsCleanup " + id + " disposed");
  }
}

class ConstructionException extends Exception {}

class NeedsCleanup2 extends NeedsCleanup {
  // 构造器可能抛出异常的类
  public NeedsCleanup2() throws ConstructionException {}
}

public class CleanupIdiom {
  public static void main(String[] args) {
    // Section 1:
    NeedsCleanup nc1 = new NeedsCleanup();//创建构造器不会抛出异常的类的对象
    try {
      // ...
    } finally {
      nc1.dispose();//finally清理
    }

    // Section 2:
    // If construction cannot fail you can group objects:
    NeedsCleanup nc2 = new NeedsCleanup();//创建构造器不会抛出异常的类的对象
    NeedsCleanup nc3 = new NeedsCleanup();//创建构造器不会抛出异常的类的对象
    try {
      // ...
    } finally {
      nc3.dispose(); //顺序与构造顺序相反(先创建的后释放)
      nc2.dispose();
    }

    // Section 3:
    // 如果构造方法可能抛出异常 则需要处理异常情况和正常情况:
    try {
      NeedsCleanup2 nc4 = new NeedsCleanup2();//
      try {
        NeedsCleanup2 nc5 = new NeedsCleanup2();
        try {
          // ...
        } finally {
          nc5.dispose();//nc5构造成功
        }
      } catch(ConstructionException e) { // nc5 构造异常
        System.out.println(e);
      } finally {//nc5创建异常 但是nc4创建成功 需要清理
        nc4.dispose();
      }
    } catch(ConstructionException e) { // nc4构造发生异常 
      System.out.println(e);
    }
  }
} /* Output:
NeedsCleanup 1 disposed
NeedsCleanup 3 disposed
NeedsCleanup 2 disposed
NeedsCleanup 5 disposed
NeedsCleanup 4 disposed
*///:~

12.11 异常匹配(父类异常与子类异常 及其书写顺序)

class Annoyance extends Exception {}
class Sneeze extends Annoyance {}

public class Human {
  public static void main(String[] args) {
    // 对于一个try catch 只会捕捉最“近”发生的异常
    try {
      throw new Sneeze();//抛出子类异常 
    } catch(Sneeze s) {//捕获子类异常
      System.out.println("Caught Sneeze");
    } catch(Annoyance a) {//捕获父类异常(范围由小到大),把父类异常放到最先位置则会忽略子类异常,因此编译器会提示错误
      System.out.println("Caught Annoyance 1");
    }
    // Catch the base type:
    try {
      throw new Sneeze();
    } catch(Annoyance a) {
      System.out.println("Caught Annoyance 2");
    }
  }
} /* Output:
Caught Sneeze
Caught Annoyance 2
*///:~

12.12 其他可选方式 讨论受检查异常的好处和坏处 作者认为坏处更大

受检查异常的来由:
在早先的其他语言中(比如C),处理异常必须执行条件测试,以确定异常类型,这样会导致程序可读性降低并且可能导致性能下降,所以程序员不愿意处理异常。受检查异常就是为了让程序员解决其他语言中出现的这种问题。

受检查异常的好处和坏处
好处:正常情况的代码和错误处理代码相分离
坏处:
1.强制我们处理异常,即使有时候我们由于条件不全,无法处理相关异常
2.可能导致我们虽然使用try捕获到了异常 但是在catch中没有处理异常的欺骗性代码,导致异常“消失”

个人觉得这一章讲的太详细了 实际使用时很多东西其实用不到,不过详细也是编程思想的特征,如果觉得看不下去可以看精简版的effective Java

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值