8.2 异常类型
3、Java异常的类型
(1)异常的根类型:java.lang.Throwable
Throwable 类是 Java 语言中所有错误或异常的超类。
只有当对象是此类(或其子类之一)的实例时,才能通过 Java 虚拟机或者 Java throw 语句抛出。
类似地,只有此类或其子类之一才可以是 catch (捕获)子句中的参数类型。
两个子类的实例,Error 和 Exception,通常用于指示发生了异常情况。
通常,这些实例是在异常情况的上下文中新近创建的,因此包含了相关的信息(比如堆栈跟踪数据)。
(2)错误类型:java.lang.Error
用于指示合理的应用程序不应该试图捕获的严重问题。
例如:VirtualMachineError(虚拟机错误)
它的子类:OutOfMemoryError(堆内存溢出错误),
StackOverflowError(栈内存溢出错误)。
(3)异常类型:java.lang.Exception
指出了合理的应用程序想要捕获的条件。
异常情况应该怎么处理呢?
A:能避免的尽量避免,例如:空指针异常,下标越界异常
B:不能避免的,要通过try...catch处理
(4)Exception又可以分为两大类
A:受检异常/编译时异常:编译器可以提前“预警”的异常类型。
这个异常不一定发生,只是可能发生。
但是编译器不放心,提前预警了,
并且要求你必须先编写好“处理”代码,才能编译通过。
比喻:
消防检查,要求必须有灭火器等各种装置。
B:非受检异常/运行时异常:编译器不给你任意“预警”信息,只有运行时发生异常才知道。
所有非受检异常都是RuntimeException的子类。
例如:
@Test
public void test1(){
try {
Thread.sleep(10000);//休眠,让当前程序先休眠10000毫秒=10秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("休眠结束");
}
//上面代码中 InterruptedException类型就属于受检异常类型。
例如:
@Test
public void test2(){
Scanner input = new Scanner(System.in);
System.out.print("请输入被除数:");
int a = input.nextInt();
System.out.print("请输入除数:");
int b = input.nextInt();
System.out.println("a/b=" + a/b);
input.close();
}
运行时可能发生ArithmeticException(算术异常),它是非受检异常
InputMismatchException(输入不匹配异常),它是非受检异常
import org.junit.Test;
import java.util.Scanner;
public class TestExceptionType {
@Test
public void test1(){
try {
Thread.sleep(10000);//休眠,让当前程序先休眠10000毫秒=10秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("休眠结束");
}
@Test
public void test2(){
Scanner input = new Scanner(System.in);
System.out.print("请输入被除数:");
int a = input.nextInt();
System.out.print("请输入除数:");
int b = input.nextInt();
System.out.println("a/b=" + a/b);
input.close();
}
}
我们平常说的异常就是指Exception,根据代码的编写编译阶段,编译器是否会警示当前代码可能发生xx异常,并督促程序员提前编写处理它的代码为依据,可以将异常分为:
- 编译时期异常(即checked异常、受检异常):在代码编译阶段,编译器就能明确警示当前代码可能发生(不是一定发生)xx异常,并督促程序员提前编写处理它的代码。如果程序员不听话,没有编写对应的异常处理代码,则编译器就会发威,直接判定编译失败,从而程序无法执行。通常,这类异常的发生不是由程序员的代码引起的,或者不是靠加简单判断就可以避免的,例如:FileNotFoundException(文件找不到异常)。
- 运行时期异常(即runtime异常、unchecked非受检异常):即在代码编译阶段,编译器完全不做任何检查,无论该异常是否会发生,编译器都不给出任何提示。只有等代码运行起来并确实发生了xx异常,它才能被发现。通常,这类异常是由程序员的代码编写不当引起的,只要稍加判断,或者细心检查就可以避免的。例如:ArrayIndexOutOfBoundsException数组下标越界异常,ClassCastException类型转换异常。
8.3 异常处理
8.3.1 try...catch
(1)try...catch
语法格式:
try{
可能发生异常的代码
}catch(异常类型1 异常对象名){
异常处理的代码
}catch(异常类型2 异常对象名){
异常处理的代码
}...
快捷键:选择可能发生异常的代码,按Ctrl + Alt + T,选择try...catch
处理异常的代码:
A:根据业务需求来定
B:可以输出打印这个异常信息,便于后期的跟踪,优化
标准的,默认的异常打印语句 e.printStackTrace();
例如:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0
at com.atguigu.exception.TestTryCatch.main(TestTryCatch.java:8)
针对受检异常,编译器提示我们可能发生什么异常,就try...catch什么异常就可以了。
针对非受检异常,编译器不提示,只能靠我们经验,靠测试,和看API或源码知道可能发生什么异常。
执行特点:
①如果try{}中没有发生异常,那么所有的catch都不会执行。
②如果try{}中发生了异常,首先try{}中这句代码下面的语句不会执行了,
然后根据异常对象的类型,去匹配对应catch分支,从上往下匹配,
匹配上了,就执行这个catch分支,剩下的catch分支也不会执行。即不会同时进入两个并列的catch分支。
如果所有的catch分支都不匹配,就相当于这个异常对象没有被捕获,就会导致程序挂了。
说明:如果有多个catch分支,catch的异常类型有父子类关系,
那么要么只写父类的catch分支,要么先写子类catch分支,再写父类的catch分支。(子上父下)
public class TestTryCatch {
public static void main(String[] args) {
/*
从命令行接收2个整数,求它们的商
*/
try {
int a = Integer.parseInt(args[0]);
int b = Integer.parseInt(args[1]);
System.out.println("商:" + a/b);
System.out.println("~~~~");
}catch (ArrayIndexOutOfBoundsException e) {
e.printStackTrace();
System.out.println("至少输入两个整数");
}catch(NumberFormatException e){
e.printStackTrace();
System.out.println("必须输入整数,不能是其他类型");
}catch(ArithmeticException e){
e.printStackTrace();
System.out.println("除数不能为0");
}catch(Exception e){
e.printStackTrace();
System.out.println("其他异常");
}
try {
/*
程序休眠5秒之后,输出一句话:可以休息了
*/
Thread.sleep(5000);
//写完这句代码,编译器立刻给我们“预警”,提示可能发生InterruptedException
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("可以休息了");
}
}
8.3.2 try..catch..finally
(2)try...catch...finally
语法格式:
try{
可能发生异常的代码
}catch(异常类型1 异常对象名){
异常处理的代码
}catch(异常类型2 异常对象名){
异常处理的代码
}...
finally{
必须执行的语句,
即无论try{}是否发生异常,
也不管catch能不能捕获异常,
finally都要执行。
就算try{}或catch{}中有return语句,finally仍然要执行。
}
唯一finally不会执行的场景就是遇到了System.exit(0);退出JVM的语句。
import org.junit.Test;
public class TestTryCatchFinally {
@Test
public void test1(){
try {
System.out.println(1/0);//一定发生异常,捕获
} catch (Exception e) {
System.out.println("捕获了一个异常:" + e);
}finally {
System.out.println("finally");//执行
}
System.out.println("end");//执行
}
@Test
public void test2(){
try {
System.out.println(9/3);//一定不会发生异常
} catch (Exception e) {
e.printStackTrace();
}finally {
System.out.println("finally");//执行
}
System.out.println("end");//执行
}
@Test
public void test3(){
try {
System.out.println(9/0);//一定会发生异常ArithmeticException
} catch (ArrayIndexOutOfBoundsException e) {//无法捕获对应的异常
e.printStackTrace();
}finally {
System.out.println("finally");//执行
}
System.out.println("end");//不执行
}
@Test
public void test4(){
try {
System.out.println(9/3);
System.exit(0);//exit退出,退出JVM
} catch (Exception e) {
e.printStackTrace();
}finally{
System.out.println("finally");//不执行
}
System.out.println("~~~~");//不执行
}
@Test
public void test5(){
try {
System.out.println(9/3);
return;
} catch (Exception e) {
e.printStackTrace();
}finally{
System.out.println("finally");//执行
}
System.out.println("end");//不执行
}
}
8.3.3 throws
(3)throws
throws使用位置,是在方法的签名中,在方法声明的()后面。
【修饰符】 返回值类型 方法名(【形参列表】) throws 异常类型列表{
}
throws的作用:声明这个方法可能在方法体{}中发生xx类型的异常,
并且当前方法没有处理,调用者需要注意,
可能需要处理这个异常。
throws后面如果跟的是 受检异常类型,那么调用这个方法时,编译器就会强制你处理这个异常,
哪怕这个异常不一定发生。
throws后面如果跟的是 非受检异常类型,那么调用这个方法时,编译器不会强制你处理这个异常,
哪怕这个异常一定发生,程序员自己决定是否处理这个异常。
但是要注意,不管是受检异常类型,还是非受检异常类型,
一旦发生异常,如果没有处理,就会导致程序“挂掉”。
结论:
处不处理:
如果受检异常没有处理,编译不通过的,无法运行。
如果非受检异常没有处理,编译通过,运行不安全。
如果受检异常编写处理代码了,那么编译通过,运行也安全。
如果非受检异常编写处理代码了,也是编译通过,运行也安全。
不管是受检异常还是非受检异常,都应该处理,才能保证编译通过,运行也安全。
import org.junit.Test;
public class TestThrows {
@Test
public void test1(){
try {
Thread.sleep(5000);
/*
public static native void sleep(long millis) throws InterruptedException;
*/
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("休眠结束");
}
@Test
public void test2(){
String str = "张三";
int num = Integer.parseInt(str);
//public static int parseInt(String s) throws NumberFormatException
System.out.println("num = " + num);
}
}
8.3.4 方法重写要求补充
5、补充方法的重写要求
(1)方法名:必须相同
(2)形参列表:必须相同
(3)返回值类型:
基本数据类型和void:必须相同
引用数据类型:<=
(4)权限修饰符:>=
不能是private,跨包不能是缺省
(5)其他修饰符:
不允许是final和static
(6)throws 异常列表
如果父类/父接口被重写方法,没有throws受检异常类型,那么重写时,不能throws受检异常类型。
如果父类/父接口被重写方法,throws受检异常类型,那么重写时,可以不throws受检异常类型,
如果要throws,只能throws该异常类型或其子类异常。
对于非受检异常,重写没有限制。
import java.io.IOException;
public class Father {
public void m1(){
//...
}
public void m2()throws IOException {
}
}
class Son extends Father{
/* @Override
public void m1() throws Exception{
//错误的原因,被重写方法,没有throws受检异常类型,那么重写时,不能throws受检异常类型。
//...
}*/
/*@Override
public void m2() throws FileNotFoundException {//可以FileNotFoundException<IOException
//...
}*/
/*@Override
public void m2() throws Exception {//报错,因为Exception > IOException
//...
}*/
/*@Override
public void m2() throws IOException {//可以,throws一样
//...
}*/
@Override
public void m2() {//可以,不throws异常
//...
}
}
8.3.5 克隆方法重写和调用
1、java.lang.Cloneable接口
在java.lang.Object类中有一个方法:
protected Object clone() throws CloneNotSupportedException
所有子类都会继承这个方法。
所有子类可以重写这个方法。
如果子类没有实现java.lang.Cloneable接口,就算重写clone(),也会发生CloneNotSupportedException。
public class Employee implements Cloneable{
private String name;
private double salary;
private int age;
public Employee(String name, double salary, int age) {
this.name = name;
this.salary = salary;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", salary=" + salary +
", age=" + age +
'}';
}
/*@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}*/
//权限修饰符可以更大,使得clone方法可以跨包非子类使用
//返回值类型修改了,从Object修改为了Employee, Employee < Object
@Override
public Employee clone() throws CloneNotSupportedException {
return (Employee) super.clone();
}
}
public class TestClone {
public static void main(String[] args) {
Employee e = new Employee("张三",20000,23);
System.out.println("原对象:" + e);
try {
Object clone = e.clone();
System.out.println("克隆成功:" + clone);
System.out.println("克隆对象的姓名:" + ((Employee) clone).getName());//不方便,这里还需要向下转型
} catch (CloneNotSupportedException ex) {
ex.printStackTrace();
}
try {
Employee cloneEmp = e.clone();
System.out.println("克隆对象的姓名:" + cloneEmp.getName());
} catch (CloneNotSupportedException ex) {
ex.printStackTrace();
}
}
}
8.3.6 throw
(4)throw
throw是用在方法体(代码块、构造器的方法体,成员方法的方法体)里面,即用在可以写语句的地方。
作用:主动/手动抛出一个异常对象。
格式: throw 异常对象;
throw语句会代替return语句,结束当前方法,并带回一个异常对象。
无论是JVM帮我们抛出的异常对象,还是我们自己throw的异常对象,一旦对象被抛出,都表示异常发生了,
都需要通过try...catch进行处理。
对比:throws 和 throw
A:使用位置不同
throws用在方法()后面,
throw用在方法体{}里面,
B:作用不同
throws表示告诉编译器当前方法没有处理xx异常, 让调用者处理xx异常,throws 异常类型,表示声明该方法可能发生xx异常
throw作用是明确抛出一个异常的对象
C:后面跟得内容不同
throws后面跟的是异常的类型名
throw后面跟的是异常的对象
public class MyArrayList {
private Object[] all = new Object[5];
private int total;
public void add(Object element){
if(total >= all.length){
// System.out.println("数组已满无法添加");
// return;
//用throw语句代码return语句
//可以提前结束方法,但是会带回一个异常对象
throw new IndexOutOfBoundsException("数组已满无法添加");
//因为IndexOutOfBoundsException是非受检异常,
//编译器没有给出警示信息,
//但是要保证运行安全(不会挂掉),同样要么当前方法加try...catch,
//要么调用者加try...catch
}
all[total++] = element;
}
public void tianJia(Object element)throws Exception{
if(total >= all.length){
//因为Exception是受检异常,编译器认为当前方法可能发生xxx异常,
//要么当前方法加try...catch,要么当前方法先throws,然后调用者在try...catch
throw new Exception("数组已满无法添加");
}
all[total++] = element;
}
public Object[] getAll(){
Object[] result = new Object[total];
for(int i=0; i<total; i++){
result[i] = all[i];
}
return result;
}
}
public class MyArrayListTest {
public static void main(String[] args) {
MyArrayList list = new MyArrayList();
try {
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
list.add(6);
} catch (Exception e) {
e.printStackTrace();
}
Object[] all = list.getAll();
for (int i = 0; i < all.length; i++) {
System.out.println(all[i]);
}
}
}
8.4 自定义异常
6、自定义异常
要求:
(1)必须继承Throwable及其子类,
通常继承Exception或RuntimeException
继承Exception:该自定义异常是受检异常
继承RuntimeException:该自定义异常是非受检异常
(2)自定义异常对象必须用throw语句抛出
(3)建议自定义异常保留几个构造器
无参构造、带message形参的构造器
自定义异常的目的:为了在异常信息描述时更加的“见名知意”
演示:
Account账户类型,
withdraw(double money)取款方法
save(double money)存款方法
public class Account {
private String id;
private double balance;
public Account(String id, double balance) {
this.id = id;
this.balance = balance;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
@Override
public String toString() {
return "Account{" +
"id='" + id + '\'' +
", balance=" + balance +
'}';
}
public boolean withdraw(double money) throws MoneyCanNotNegativeException, MoneyNotEnoughException {
if(money < 0){
// return false;
// throw new Exception("取款金额不能为负数");
// throw new MoneyCanNotNegativeException("取款金额不能为负数");
throw new MoneyCanNotNegativeException();
}else if(money > balance){
// return false;
// throw new Exception("余额不足");
// throw new MoneyNotEnoughException("余额不足");
throw new MoneyNotEnoughException();
}
balance -= money;
return true;
}
public boolean save(double money) throws MoneyCanNotNegativeException{
if(money < 0){
throw new MoneyCanNotNegativeException("存款金额不能为负数");
}
balance += money;
return true;
}
}
public class TestAccount {
public static void main(String[] args) {
Account a = new Account("11111",5000);
try {
a.withdraw(-500);
} catch (MoneyCanNotNegativeException e) {
e.printStackTrace();
} catch (MoneyNotEnoughException e) {
e.printStackTrace();
}
try {
a.withdraw(60000);
} catch (MoneyCanNotNegativeException e) {
e.printStackTrace();
} catch (MoneyNotEnoughException e) {
e.printStackTrace();
}
}
}