第七章异常处理
异常概述
Error:
Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况。比如:StackOverflowError和OOM。一般不编写针对性的代码进行处理。
出现Error就改代码。
Exception:
其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。例如:
- 空指针访问
- 试图读取不存在的文件
- 网络连接中断
- 数组角标越界
一、异常体系结构
- java.lang.Error:一般不编写针对性的代码进行处理。
- java.lang.Exception:可以进行异常的处理
- 编译时异常(checked)
- IOException
- FileNotFoundException
- ClassNotFoundException
- IOException
- 运行时异常(unchecked,RuntimeException)
- NullPointerException
- ArrayIndexOutOfBoundsException
- ClassCastException
- NumberFormatException
- InputMismatchException
- ArithmeticException
- 编译时异常(checked)
面试题:常见的异常有哪些?举例说明
异常处理方式
一、异常的处理:抓抛模型
过程一:“抛”:程序在正常执行的过程中,一旦出现异常,就会在异常代码处生成一个对应异常类的对象。并将此对象抛出。一旦抛出对象以后,其后的代码就不再执行。
过程二:“抓”:可以理解为异常的处理方式:①try-catch-finally②throws
二、try-catch-finally的使用
try{
//可能出现异常的代码
}catch(异常类型1 变量名1){
//处理异常的方式1
}catch(异常类型2 变量名2){
//处理异常的方式2
}catch(异常类型3 变量名3){
//处理异常的方式3
}
.....
finally{
//一定会执行的代码
}
说明:
-
finally是可选的。
-
使用try将可能出现异常代码包装起来,在执行过程中,一旦出现异常,就会生成一个对应异常类的对象,根据此对象的类型,去catch中进行匹配。
-
一旦try中的异常匹配到某一个catch时,就进入catch中进行异常的处理。一旦处理完成,就跳出当前的try-catch结构(在没有写finally的情况)。继续执行其后的代码。
-
catch中的异常类型如果没有子父类关系,则谁声明在上,谁声明在下无所谓。catch中的异常类型如果满足子父类关系,则要求子类一定声明在父类的上面。否则编译报错
-
常用的异常对象处理的方式:①String getMessage() (输出错误信息)
②pringStackTrace() (输出所有的错误信息,包括出错的位置,更加常用)
-
在try结构中声明的变量,再出了try结构以后,就不能再被调用
体会1:使用try-catch-finally处理编译时异常,使得程序在编译时就不再报错,但是运行时仍可能报错。
相当于我们使用try-catch-finally将一个编译时可能出现的异常,延迟到运行时出现。
@Test
public void test1(){
String str = "123";
str = "abc";
try {
int num = Integer.parseInt(str);
} catch (NumberFormatException e){
System.out.println("出现数值转换异常,重写输入");
String s = e.getMessage();
System.out.println(s);
e.printStackTrace();
}catch (Exception e){
System.out.println("出现异常");
}
}
try-catch-finally中finally的使用
- finally是可选的
- finally中声明的是一定会被执行的代码,即使catch中又出现异常了,try中有return语句,catch中有return语句等情况。也都会一定执行finally
- 像数据库连接、输入输出流、网络编程Socket等资源,JVM是不能自动的回收的,我们需要自己手动的进行资源的释放,此时的资源释放就需要声明在finally中。(因为如果出现异常或其他原因导致程序中断,但是在关闭前需要释放资源,此时finally就可以做到一定被执行)
举例
@Test
public void test2(){
FileInputStream fis = null;
try {
File file = new File("Hello.txt");
fis = new FileInputStream(file);
int data = fis.read();
while (data != -1){
System.out.println((char) data);
data = fis.read();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fis != null)//不然可能会报空指针
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
体会2:开发中,由于运行时异常比较常见,所以我们通常就不针对运行时异常编写try-catch-finally了。针对于编译时异常,我们说一定要考虑异常的处理。
异常处理的方式二:throws + 异常类型
-
“throws + 异常类型” 写在方法的声明处。指明此方法执行时,可能会抛出的异常类型。一旦当方法体执行时,出现异常,仍会在异常代码处生成一个异常类的对象,此对象满足throws后异常类型时,就会被抛出,异常代码后续的代码,就不再执行!
-
体会:try-catch-finally:真正的将异常给处理掉了。
throws的方式只是将异常抛给了方法的调用者。并没有真正将异常处理掉。
RunTimeException(运行时异常)可以不用throws,因为编译可以通过。
举例:
public class ExceptionTest {
public static void main(String[] args) {
//可以将异常一直调用者的方向抛,但是当抛到main方法时已经到末尾了,没有地方抛了,必须进行处理
ExceptionTest exceptionTest = new ExceptionTest();
try {
exceptionTest.method1();
} catch (FileNotFoundException e){
e.printStackTrace();
}catch (IOException e) {
e.printStackTrace();
}//如果在try-catch中针对不同类型异常的处理不同,那么就不要省略
}
public void method1() throws FileNotFoundException, IOException{
//FileNotFoundException是IOException的子类,可以省略
//如果在try-catch中针对不同类型异常的处理不同,那么就不要省略
readFile();
}
public void readFile() throws FileNotFoundException, IOException {
File file = new File("Hello.txt");
FileInputStream fis = new FileInputStream(file);
int data = fis.read();
while (data != -1) {
System.out.println((char) data);
data = fis.read();
}
fis.close();
}
}
-
开发中如何选择使用try-catch-finally,还是使用throws?
我们知道子类重写父类的方法时抛出的异常类型不大于父类方法抛出的异常。(因为我们传入的都是父类的对象(多态性),try-catch处理的也是父类throws的异常,如果子类抛出的类型比父类大,那么就处理不了)
3.1 如果父类中被重写的方法没有throws方式处理异常,则子类重写的方法也不能使用throws,意味着如果子类重写的方法中有异常,必须使用try-catch-finally方式处理
3.2 执行的方法a中,先后又调用了另外的几个方法,这几个方法是递进关系执行的。我们建议这几个方法使用throws的方式进行处理。而执行的方法a可以考虑使用try-catch-finally方式进行处理。(这几个方法一旦出现异常都将异常抛给a,让a用try-catch处理)
过程一:“抛”:程序在正常执行的过程中,一旦出现异常,就会在异常代码处生成一个对应异常类的对象。并将此对象抛出。一旦抛出对象以后,其后的代码就不再执行。
关于异常对象的产生:①系统自动生成的异常对象
②手动的生成一个异常对象,并抛出(throw)
注意区别throw是在抛,是在手动生成异常;而throws是在"抓",即处理异常
过程二:“抓”:可以理解为异常的处理方式:①try-catch-finally②throws
throw是生成异常,throws是处理异常
手动抛出异常举例:(只能抛throw异常结构,非异常结构如String不能抛)
public class StudentTest {
public static void main(String[] args) {
try {
Student student = new Student();
student.register(-1001);
System.out.println(student);
} catch (Exception e) {
// e.printStackTrace();
System.out.println(e.getMessage());
}
}
}
class Student{
private int id;
public void register(int id) throws Exception{
if (id > 0){
this.id = id;
}else {
// System.out.println("输入非法");
//手动生成(抛出)一个异常
throw new Exception("输入数据非法!");
// throw new RuntimeException("输入数据非法!");
}
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
'}';
}
}
如何自定义异常类?(类比源码中RunTimeException、Exception是如何定义的)
- 继承于现有的异常结构:RunTimeException、Exception
- 提供全局常量:serialVersionUID
- 提供重载的构造器
如果自定义类继承于RunTimeException运行时异常,可以不用抛出(throws)异常,因为我们的try-catch只处理编译时异常,而如果自定义类继承于Exception则必须抛出(throws)异常。
//自定义异常类
public class MyException extends Exception{
static final long serialVersionUID = -3387516993124228948L;
public MyException() {
}
public MyException(String message) {
super(message);
}
}
//测试类
public class StudentTest {
public static void main(String[] args) {
try {
Student student = new Student();
student.register(-1001);
System.out.println(student);
} catch (Exception e) {
e.printStackTrace();
}
// System.out.println(e.getMessage());
}
}
class Student{
private int id;
public void register(int id) throws Exception{
//抛出Exception(父类)也行,抛出MyException(自定义类)也行
if (id > 0){
this.id = id;
}else {
// System.out.println("输入非法");
//手动生成一个异常
// throw new Exception("输入数据非法!");
// throw new RuntimeException("输入数据非法!");
//使用自定义的异常类
throw new MyException("输入数据非法!");
/*
如果自定义类继承于RunTimeException运行时异常,可以不用抛出异常,因为我们的try-catch只处理编译时异常
,而如果自定义类继承于Exception则必须抛出异常
*/
}
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
'}';
}
}
练习:编写应用程序EcmDef.java,接收命令行的两个参数,要求不能输入负数,计算两数相除。对 数 据 类 型 不 一 致 (NumberFormatException) 、 缺 少 命 令 行 参 数(ArrayIndexOutOfBoundsException、除0(ArithmeticException)及输入负数(EcDef 自定义的异常)进行异常处理。
//自定义异常类
public class EcDef extends Exception{
static final long serialVersionUID = 7818375828146090165L;
public EcDef() {
}
public EcDef(String message) {
super(message);
}
}
public class EcmDef {
public static void main(String[] args) {
try {
//从命令行获取的是字符串格式的
//字符串转成基本数据类型
int i = Integer.parseInt(args[0]);
int j = Integer.parseInt(args[1]);
int result = ecm(i,j);
} catch (EcDef e) {
e.printStackTrace();
}catch (NumberFormatException e){
System.out.println("数据类型不一致");
}catch (ArrayIndexOutOfBoundsException e){
System.out.println("缺少命令行参数");
}catch (ArithmeticException e){
System.out.println("除0");
}
}
//完成两数相除
public static int ecm(int i,int j) throws EcDef {
if (i < 0 || j < 0){
throw new EcDef("分子或分母是负数");
}else {
return i / j;
}
}
}
面试题:
final、finally、finalize三者的区别?
类似(长的相似的,这些放在题里一般区别很大,一点关系也没有):
throw 和 throws
Collection 和 Collections
String、StringBuffer、StringBuilder
ArrayList 、 LinkedList
HashMap、LinkedHashMap
重写、重载
结构不相似的(长得不像的放在题里一般都有相似点):
抽象类、接口
== 、 equals()
sleep() 、 wait()
如throw 和 throws 的区别:
throw 表示抛出一个异常类的对象,生成异常对象的过程,声明在方法体内。
throws属于异常处理的一种方式,声明在方法的声明处。