六. 异常
-
程序执行中出现异常,JVM会把异常信息打印到控制台,以便修改,增强程序的健壮性
-
异常在java 中以类的形式存在,每一个异常类都可以创建对象
NumberFormatException nfe = new NumberFormatException("数字格式化异常");
System.out.println(nfe); //输出java.lang.NumberFormatException: 数字格式化异常
NullPointerException npe = new NullPointerException("空指针异常");
System.out.println(npe); //输出java.lang.NullPointerException: 空指针异常
int a = 10;
int b = 0;
//程序执行到此处时会创建异常对象:new ArithmeticException("/ by zero");
int c = a / b;
System.out.println(c); //输出异常java.lang.ArithmeticException: / by zero
-
UML:是一种统一建模语言,图标式语言(画图)
在UML图中描述类和类之间的关系、程序执行流程、对象状态等
-
Error、Exception都是继承Throwable类,Throwable继承Object。Exception的直接子类和RuntimeException继承Exception类
Throwable:错误和异常都是可抛出的
Error:发生错误只能终止程序运行,退出JVM,错误无法处理
Exception:可处理的
所有Exception的直接子类都叫做编译时异常(编译时异常表示必须在编写程序时预先对这种异常进行处理,否则编译器报错),又被称为受检异常(CheckedException)、受控异常
RuntimeException:所有RuntimeException及其子类都属于运行时异常(运行时异常在编写程序时可以处理也可以不处理),又被称为未受检异常(UnCheckedException)、非受控异常
-
编译时异常和运行时异常都发生在运行阶段,编译阶段不会发生异常
编译时异常一般发生概率较高,对于一些发生概率较高的异常,需要在运行前进行预处理
运行时异常一般发生概率较低
-
Java语言中对异常处理的方式:
- 在方法声明的位置上使用throws关键字,抛给上一级(谁调用我就抛给谁),异常发生后如果选择上抛,上一级对这个异常也有两种处理方式
- 使用try…catch语句进行异常的捕捉
-
Java中异常发生后如果一直上抛,最终抛给main方法,main方法抛给调用者JVM,最终只能终止java程序的执行
/*
程序执行到此处发生ArithmeticException异常,底层创建异常对象,抛给调用者main方法。main方法没有处理,将这个异常自动抛给JVM,JVM终止程序的运行
ArithmeticException继承RuntimeException,属于运行时异常
*/
public static void main(String[] args) {
System.out.println(10/0);
System.out.println("hello world"); //这行代码没有执行
}
- 方法声明位置上使用了throws ClassNotFoundException,是编译时异常,需要在编写代码时处理,否则报错
public static void main(String[] args) {
//报错信息:未报告的异常错误java.lang.ClassNotFoundException; 必须对其进行捕获或声明以便抛出
doSome(); //调用doSome()方法时必须对ClassNotFoundException异常进行预处理
}
public static void doSome() throws ClassNotFoundException{
System.out.println("doSome");
}
//解决方法:
//1. 继续使用throws完成异常的继续上抛(上抛类似于推卸责任)
public static void main(String[] args) throws ClassNotFoundException{
doSome();
}
//2. 使用try...catch进行捕捉(捕捉等于把异常拦下,异常解决,调用者不知道异常发生)
public static void main(String[] args) {
try {
doSome();
}catch (ClassNotFoundException e){ //e是引用
e.printStackTrace();
}
}
- 在方法声明的位置上使用throws关键字抛出,抛给上一级/调用者
import java.io.FileInputStream;
import java.io.FileNotFoundException;
public class ExceptionTest03 {
//不建议在main方法上使用throws,抛给JVM后只能终止程序,建议使用try...catch捕捉
public static void main(String[] args) throws FileNotFoundException {
System.out.println("main begin");
m1();
System.out.println("main end");
}
/*
继续上抛,只能抛调用方法的异常或者其父类,多个异常可以用逗号分开
比如m1()方法可以throws FileNotFoundException, ClassNotFoundException
也可以throws IOExpection,或者throws Exception
*/
private static void m1() throws FileNotFoundException {
System.out.println("m1 begin");
m2();
System.out.println("m1 end");
}
//调用FileInputStream()方法,创建输入流对象,该流指向一个文件
private static void m2() throws FileNotFoundException {
//该方法声明时抛出FileNotFoundException,该异常父类是IOException
//IOException父类是Exception,所以是编译时异常
new FileInputStream("C:\\Users\\10434\\Desktop\\成绩单.JPG");
}
}
- 程序执行顺序分析
public static void main(String[] args) {
System.out.println("main begin"); //1
try {
m1();
//m1()方法出现异常,结束try语句块执行catch语句块
System.out.println("hello"); //这里不执行
}catch (FileNotFoundException e){
System.out.println("文件不存在"); //3
}
System.out.println("main end"); //4
}
public static void m1() throws FileNotFoundException {
System.out.println("m1 begin"); //2
m2();
System.out.println("m1 end");
}
private static void m2() throws FileNotFoundException {
new FileInputStream("C:\\Users\\1043\\Desktop\\成绩单.JPG"); //错误路径
System.out.println("hello"); //方法体中代码出现异常,若采用上报方式,该方法结束
}
- try…catch语法深入
2.1. catch后的异常类型可以是具体异常类型,也可以是其父类型
try {
new FileInputStream("C:\\Users\\10434\\Desktop\\成绩单.JPG");
} catch (IOException e){ //多态:IOException e = new FileNotFoundException();
System.out.println("文件不存在");
}
2.2. catch可以写多个,建议catch时针对具体异常一个一个处理
try {
FileInputStream fis = new FileInputStream("C:\\Users\\10434\\Desktop\\成绩单.JPG");
fis.read();
} catch (FileNotFoundException e) {
System.out.println("文件不存在");
} catch (IOException e) {
System.out.println("读文件报错");
}
2.3. 有多个catch时,从上到下必须遵守从小到大(父类在上子类在下会报错)
try {
FileInputStream fis = new FileInputStream("C:\\Users\\10434\\Desktop\\成绩单.JPG");
fis.read();
} catch (Exception e) {
System.out.println("文件不存在");
} catch (IOException e) { //编译报错,IOException是Exception的子类
System.out.println("读文件报错");
}
try {
FileInputStream fis = new FileInputStream("C:\\Users\\10434\\Desktop\\成绩单.JPG");
fis.read();
System.out.println(10 / 0);
} //JDK8新特性,允许多种异常使用"|"连接
catch (FileNotFoundException | ArithmeticException e) {
System.out.println("文件不存在或算数异常");
}
- 异常对象的getMessage()方法:获取简单的异常描述信息
NullPointerException e = new NullPointerException("空指针异常");
String msg = e.getMessage(); //msg就是上面构造方法的String参数
System.out.println(msg); //输出“空指针异常”
-
异常对象的printStackTrace()方法:打印异常追踪的堆栈信息
Java后台打印异常堆栈信息时,采用异步线程方式(输出先后顺序可能有变化)
public static void main(String[] args) {
try {
m1();
} catch (FileNotFoundException e) {
e.printStackTrace();
/*
查看方法:从上往下(SUN的代码不会有错,只看自己的)
java.io.FileNotFoundException: C:\Users\1043\Desktop\成绩单.JPG (系统找不到指定的路径。)
at java.io.FileInputStream.open0(Native Method)
at java.io.FileInputStream.open(FileInputStream.java:195)
at java.io.FileInputStream.<init>(FileInputStream.java:138)
at java.io.FileInputStream.<init>(FileInputStream.java:93)
at exception.ExceptionTest06.m2(ExceptionTest06.java:21)
at exception.ExceptionTest06.m1(ExceptionTest06.java:17)
at exception.ExceptionTest06.main(ExceptionTest06.java:10)
21行错误 --> (导致)17行错误 --> (导致)10行错误,21行是错误的根源
*/
}System.out.println("hello"); //这行代码依然可以执行,程序健壮
}
private static void m1() throws FileNotFoundException {
m2();
}
private static void m2() throws FileNotFoundException {
new FileInputStream("C:\\Users\\1043\\Desktop\\成绩单.JPG");
}
-
try…catch中的finally子句
- 在finally子句中的代码一定会执行,即使try语句块中的代码出现异常;且是最后执行的。finally子句必须和try一起出现,不能单独使用
- 通常在finally语句块中完成资源的释放/关闭,比较有保障
public static void main(String[] args) {
FileInputStream fis = null;
try {
fis = new FileInputStream("C:\\Users\\10434\\Desktop\\成绩单.JPG");
String s = null;
s.toString();
System.out.println("real"); //发生异常,这里不执行
//fis.close(); 流占用资源,使用完要关闭。放在这里如果出现异常就无法关闭
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (NullPointerException e){
e.printStackTrace();
} finally {
System.out.println("heisenberg"); //输出顺序:1
if (fis != null) { //避免空指针异常
try {
fis.close();
} catch (IOException e) { //close()方法抛出IOException
e.printStackTrace();
}
}
}System.out.println("hello"); //2
}
- try和finally可以连用(可以没有catch)
//以下代码执行顺序:先执行try...再执行finally...最后执行return;
public static void main(String[] args) {
try {
System.out.println("try...");
return; //return;语句只要执行,方法必然结束
} finally {
System.out.println("finally...");
}
System.out.println("hello"); //编译报错:无法访问的语句
}
- 退出JVM会使finally语句不执行
public static void main(String[] args) {
try {
System.out.println("try..."); //只输出try...
System.exit(0); //退出JVM
} finally {
System.out.println("finally..."); //不执行
}
}
- finally与return的组合
/*
java中亘古不变的语法:
1. 方法体中的代码必须遵守自上而下的顺序执行
2. return语句一旦执行,整个方法必须结束
*/
public static void main(String[] args) {
System.out.println(m()); //输出100
}
public static int m(){
int i = 100;
try {
return i;
} finally {
i ++;
}
}
//反编译后的代码:
public static int m(){
int i = 100;
int j = i;
i ++;
return j;
}
-
final、finally、finalize的区别
- final是一个关键字,表示最终的
- finally也是一个关键字,在异常处理机制中和try联合使用
- finalize()是Object类中的方法(这个方法是由GC负责调用的),所以finalize是标识符
-
自定义异常
- 编写一个类继承Exception(编译时异常)或RuntimeException(运行时异常)
- 提供两个构造方法:一个无参,一个带有String参数
public class MyException extends Exception{ //编译时异常
public MyException() {
}
public MyException(String message) {
super(message);
}
}
public class MyExceptionTest01 {
public static void main(String[] args) {
MyException me = new MyException("XX异常");
me.printStackTrace(); //打印异常追踪堆栈信息
String s = me.getMessage();
System.out.println(s); //输出“XX异常”
}
}
public class MyException extends RuntimeException{} //运行时异常
-
子类重写后的方法不能比父类方法抛出更多(更宽泛)(编译时)异常,可以更少
子类可以抛更多运行时异常,编译不报错
class Animal{
public void doSome(){}
public void doOther() throws Exception{}
}
class Cat extends Animal{
public void doSome() throws FileNotFoundException{} //编译报错
//public void doSome() throws NullPointerException{} 运行时异常,编译通过
public void doOther() throws FileNotFoundException{} //编译通过
}
-
throws和throw:
throws:在方法声明位置使用,给调用者上报异常信息
throw:手动抛出异常,配合throws使用
public void doSome () throws FileNotFoundException {
//不能用try..catch,没有意义
throw new FileNotFoundException("XX异常");
}
- 题目:模拟用户注册,用户名或密码必须在[6,14]之间
public class ExceptionTest11 {
public static void main(String[] args) {
UserService u = new UserService();
System.out.print("请输入用户名:");
Scanner s = new Scanner(System.in);
String usn = s.next();
System.out.print("请输入密码:");
String pwd = s.next();
try {
u.register(usn, pwd);
} catch (StringException e) {
System.out.println(e.getMessage());
}
}
}
class UserService {
public void register(String username, String password) throws StringException{
if(username == null || username.length() < 6 || username.length() > 14){ //防止空指针异常
//username == null最好写成null == username,防止少敲一个等号变成赋值语句
throw new StringException("用户名长度异常!");
}else if (password == null || password.length() < 6 || password.length() > 14){
throw new StringException("密码长度异常!");
}
System.out.println("注册成功,用户名:" + username + "\n密码:" + password);
}
}
class StringException extends Exception{ //编译时异常
public StringException() {}
public StringException(String message) {
super(message);
}
}