0
点赞
收藏
分享

微信扫一扫

Java学习笔记(十四)

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类型转换异常。

Java学习笔记(十四)_try...catch

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();
        }
    }
}

Java学习笔记(十四)_Test_02

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();
        }
    }
}


举报

相关推荐

0 条评论