异常
一、异常概述
-
什么是异常?
程序执行过程中的不正常情况。
-
异常机制有什么用?
增强程序的健壮性。
二、Java中异常以类和对象形式存在
package exception;
public class ExceptionTest01 {
public static void main(String[] args) {
//通过“异常类”实例化“异常对象”
NumberFormatException n = new NumberFormatException("数字格式化异常");
System.out.println(n);
}
}
-
异常在java中以类的形式存在,每一个异常类都可以创建异常对象。实际上,JVM在执行到异常处会new异常对象,并且JVM将new的异常对象抛出打印输出信息到控制台。
-
异常对应的现实生活中是怎样的?
火灾(异常类):
xxxx年xx月xx日 A家发生火灾(异常对象)
xxxx年xx月xx日 B家发生火灾(异常对象)
xxxx年xx月xx日 C家发生火灾(异常对象)
三、java异常处理机制
-
异常的继承结构。
我们可以使用UML图来描述一下继承结构。(UML是一种建模语言,画UML的工具有例如:Rational Rose,starUML……)
Object
Object下有Throwable(可抛出的)
Throwable下有两个分支:Error(不可处理的,直接退出JVM)和Excepion(可处理的)
Excepion下有两个分支:
Excepion的直接子类:编译时异常。(要求程序员在编写程序阶段必须预先对这些异常进行处理,如果不处理编译器报错, 因此得名编译时异常。)
编译时异常又可以称为受检异常:CheckedException 受控异常
RuntimeExcepion:运行时异常。(在编写程序阶段程序员可以预先处理,也可以不管,都行。)
运行时异常又可以称为未受检异常:UnCheckedException 非受控异常
-
编译时异常和运行时异常,都是发生在运行阶段。编译阶段异常是不会发生的。
所有异常都是在运行阶段发生的。因为只有程序运行阶段才可以new对象。因为异常的发生就是new异常对象。
-
编译时异常和运行时异常的区别
编译时异常一般发生的概率比较高。对于一些发生概率较高的异常,需要在运行之前对其进行预处理。
运行时异常一般发生的概率比较低。
假设java中没有对异常进行划分,没有分为:编译时异常和运行时异常,
所有的异常都需要在编写程序阶段对其进行预处理,首先,如果这样的话,程序肯定是绝对的安全的。
但是程序员编写程序太累,代码到处都是处理异常的代码。package exception; public class ExceptionTest02 { public static void main(String[] args) { /* 程序执行到此处发生了ArithmeticException异常,底层new了一个ArithmeticException异常对象, 然后抛出了,由于是main方法调用了100 / 0,所以这个异常ArithmeticException抛给了main方法, main方法没有处理,将这个异常自动抛给了JVM。JVM最终终止程序的执行。 ArithmeticException 继承 RuntimeException,属于运行时异常。 在编写程序阶段不需要对这种异常进行预先的处理。 */ System.out.println(100 / 0); // 这里的HelloWorld没有输出,没有执行。 System.out.println("Hello World!"); } }
-
异常的两种处理方式
第一种方式:在方法声明的位置上,使用throws关键字,抛给上一级。谁调用我,我就抛给谁。抛给上一级。
第二种方式:使用try…catch语句进行异常的捕捉。这件事发生了,谁也不知道,因为我给抓住了。
【举个例子】:
我是某集团的一个销售员,因为我的失误,导致公司损失了1000元,
“损失1000元”这可以看做是一个异常发生了。我有两种处理方式,
第一种方式:我把这件事告诉我的领导【异常上抛】
第二种方式:我自己掏腰包把这个钱补上。【异常的捕捉】**注意:**异常发生之后,如果我选择了上抛,抛给了我的调用者,调用者需要对这个异常继续处理,那么调用者处理这个异常同样有两种处理方式。Java中异常发生之后如果一直上抛,最终抛给了main方法,main方法继续向上抛,抛给了调用者JVM,JVM知道这个异常发生,只有一个结果。终止java程序的执行。
只要异常没有捕捉,采用上报的方式,此方法的后续代码不会执行。另外需要注意,try语句块中的某一行出现异常,该行后面的代码不会执行,try…catch捕捉异常之后,后续代码可以执行。
-
try catch的深入
(1)catch后面的小括号中的类型可以是具体的异常类型,也可以是该异常类型的父类。
(2)catch可以写多个。建议catch的时候,精确地一个一个处理。这样有利于程序的调试。
(3)catch写多个的时候,从上到下,必须遵守从小到大。
package exception;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class ExceptionTest03 {
public static void main(String[] args) throws Exception, FileNotFoundException, NullPointerException {
try {
//创建输入流
FileInputStream fis = new FileInputStream("D:\\Java\\007-API文档");
//读文件
fis.read();
}catch(FileNotFoundException e) {
System.out.println("文件不存在!");
} catch(IOException e){
System.out.println("读文件报错了!");
}
// 编译报错。
/*
try {
//创建输入流
FileInputStream fis = new FileInputStream("D:\Java\007-API文档");
//读文件
fis.read();
} catch(IOException e){
System.out.println("读文件报错了!");
} catch(FileNotFoundException e) {
System.out.println("文件不存在!");
}*/
}
}
-
JDK8新特性
package exception; import java.io.FileInputStream; import java.io.FileNotFoundException; public class ExceptionTest04 { public static void main(String[] args) { try { //创建输入流 FileInputStream fis = new FileInputStream("D:\\Java\\007-API文档"); // 进行数学运算 System.out.println(100 / 0); // 这个异常是运行时异常,编写程序时可以处理,也可以不处理。 } catch(FileNotFoundException | ArithmeticException | NullPointerException e) { System.out.println("文件不存在?数学异常?空指针异常?都有可能!"); } } }
-
上报和捕捉怎么选择
如果希望调用者来处理,选择throws。其它情况使用try…catch
-
异常对象的常用方法getMessage和printStackTrace();
(1)String msg = exception.getMessage() 获取异常简单的描述信
(2)exception.printStackTrace(); 打印异常追踪的堆栈信息
package exception; public class ExceptionTest05 { public static void main(String[] args) { NullPointerException e = new NullPointerException("空指针异常"); System.out.println(e.getMessage()); e.printStackTrace(); //这行代码会执行 java后台打印异常堆栈追踪信息的时候,采用了异步线程的方式打印的 System.out.println("Hello world!"); } }
查看异常的追踪信息,从上往下一行一行看,sun公司不用看,看自己写的代码就行。
-
四、finally
-
在finally子句中的代码是最后执行的,并且是一定会执行的,即使try语句块中的代码出现了异常。
finally子句必须和try一起出现,不能单独编写。
-
finally语句通常在finally语句块中完成资源的释放/关闭。因为finally中的代码比较有保障。
package exception; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; public class ExceptionTest06 { public static void main(String[] args) { FileInputStream fis = null; try { //创建输入流对象 fis = new FileInputStream("D:\\Java\\007-API文档"); //开始读文件 String s = null; //这里一定会出现空指针异常 s.toString(); //流使用完需要关闭,因为流是占用资源的 //即使以上程序出现异常,流也必须关闭,在这close也许关不掉,所以不能在这里关闭 //fis.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (NullPointerException e){ e.printStackTrace(); } finally{ //流的关闭放在这里比较保险 if(fis != null){//fis 前面也许new对象不成功 避免空指针异常 try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
-
try和finally没有catch也可以, try不能单独使用。
try finally可以联合使用。package exception; public class ExceptionTest07 { public static void main(String[] args) { /* 以下代码的执行顺序: 先执行try... 再执行finally... 最后执行 return (return语句只要执行方法必然结束。) */ try { System.out.println("try..."); return; } finally { // finally中的语句会执行。能执行到。 System.out.println("finally..."); } // 这里不能写语句,因为这个代码是无法执行到的。 //System.out.println("Hello World!"); } }
-
退出JVM finally语句不执行
package exception; public class ExceptionTest08 { public static void main(String[] args) { try { System.out.println("try..."); // 退出JVM System.exit(0); // 退出JVM之后,finally语句中的代码就不执行了! } finally { System.out.println("finally..."); } } }
-
finally面试题
package exception; public class ExceptionTest09 { public static void main(String[] args) { int result = m(); System.out.println(result);//思考此时输出结果i的值是多少? } public static int m(){ int i = 100; try{ return i; }finally { i++; } } }
以上输出结果是100。
java语法规则:
java中有一条这样的规则:
方法体中的代码必须遵循自上而下顺序依次逐行执行(亘古不变的语法!)
java中还有一条语法规则:
return语句一旦执行,整个方法必须结束(亘古不变的语法!)return i出现在int i = 100后面,所以最终结果必须返回100.
//反编译之后的效果 public static int m(){ int i = 100; int j = i; i++; return j; }
-
final finally finalize的区别
final 关键字
final修饰的类无法继承
final修饰的方法无法覆盖
final修饰的变量不能重新赋值。finally 关键字
和try一起联合使用。
finally语句块中的代码是必须执行的。finalize 标识符
是一个Object类中的方法名。
这个方法是由垃圾回收器GC负责调用的。 -
自定义异常
步骤:
第一步:编写一个类继承Exception或者RuntimeException。
第二步:提供两个构造方法,一个无参数的,一个带有String参数的。
package exception; public class ExceptionTest10 { public static void main(String[] args) { //只是new了异常对象,并没有手动抛出 MyException e = new MyException("用户名为空异常!"); //打印异常堆栈信息 e.printStackTrace(); //获取异常简单描述信息 System.out.println(e.getMessage()); } }
-
异常在实际开发中的作用
改良之前的代码
package exception; public class MyStackOperationException extends Exception{ public MyStackOperationException() { } public MyStackOperationException(String s) { super(s); } }
package exception; public class MyStack { private Object[] elements; private int index; public MyStack() { this.elements = new Object[10]; this.index = -1; } /** * 压栈的方法 * @param obj 被压入的元素 */ public void push(Object obj) throws MyStackOperationException { if(index >= elements.length - 1){ throw new MyStackOperationException("压栈失败,栈已满!"); //在这里不用捕捉的方式解决异常,异常是我们自己抛的,又自己捕捉没有意义,而且这个异常的信息我们需要传递出去让外界知道。 } index++; elements[index] = obj; System.out.println("压栈" + obj + "元素成功,栈帧指向" + index); } /** * 弹栈的方法,从数组中往外取元素。每取出一个元素,栈帧向下移动一位。 * @return */ public void pop() throws MyStackOperationException { if(index < 0){ throw new MyStackOperationException("弹栈失败,栈已空!"); } System.out.print("弹栈" + elements[index] + "元素成功,"); index--; System.out.println("栈帧指向" + index); } public Object[] getElements() { return elements; } public void setElements(Object[] elements) { this.elements = elements; } public int getIndex() { return index; } public void setIndex(int index) { this.index = index; } }
package exception; public class MyStackExceptionTest { public static void main(String[] args) { //创建栈对象 MyStack stack = new MyStack(); //压栈 try { stack.push(new Object()); stack.push(new Object()); stack.push(new Object()); stack.push(new Object()); stack.push(new Object()); stack.push(new Object()); stack.push(new Object()); stack.push(new Object()); stack.push(new Object()); stack.push(new Object()); stack.push(new Object()); stack.push(new Object()); } catch (MyStackOperationException e) { System.out.println(e.getMessage()); } //弹栈 try { stack.pop(); stack.pop(); stack.pop(); stack.pop(); stack.pop(); stack.pop(); stack.pop(); stack.pop(); stack.pop(); stack.pop(); stack.pop(); stack.pop(); } catch (MyStackOperationException e) { System.out.println(e.getMessage()); } } }
-
异常与方法覆盖
重写之后的方法不能比重写之前的方法抛出更多(更宽泛)的异常,可以更少。
-
异常相关测试题
编写程序模拟用户注册:
①程序开始执行时,提示用户输入“用户名”和“密码”信息。
②输入信息之后,后台java程序模拟用户注册。
③注册时用户名要求长度在[6-14]之间,小于或者大于都表示异常。 注意:
完成注册的方法放到一个单独的类中。
异常类自定义即可。 class UserService {
public void register(String username,String password){
//这个方法中完成注册!
}
} 编写main方法,在main方法中接收用户输入的信息,在main方法
中调用UserService的register方法完成注册。package exception.Test; public class UserService { /** * 用户注册 * @param username 用户名 * @param password 密码 * @throws MyException2 当用户名为null,或者用户名格式不正确,会出现该异常! */ public void register(String username,String password) throws MyException2 { //null == username 这个判断最好放在所有条件的前面,并且这么写比username == null 更保险 if(null == username || username.length() < 6 || username.length() > 14){ throw new MyException2("用户名不合法,长度必须在6-14之间!"); } System.out.println("注册成功,欢迎【" + username + "】"); } }
package exception.Test; public class MyException2 extends Exception{ public MyException2() { } public MyException2(String message) { super(message); } }
package exception.Test; public class Test2 { public static void main(String[] args) { UserService userService = new UserService(); try { userService.register("jack","123"); } catch (MyException2 e) { System.out.println(e.getMessage()); } } }