JavaSE进阶 | 一文带你吃透Java中的异常处理机制

目录

 一:一文带你吃透Java中的异常处理机制

1、异常概述

2、java中异常以类和对象的形式存在

3、UML及starUML

4、编译时异常和运行时异常区别

5、运行时异常可以不处理 

6、方法声明位置上使用throws

7、异常处理的原理

8、异常捕捉和上报的联合使用

9、try...catch深入理解

10、异常对象的常用方法

11、finally关键字

13、怎么自定义异常(重点)

14、自定义异常在实际开发中的作用(重点)

15、异常与方法覆盖

16、小试牛刀


一:一文带你吃透Java中的异常处理机制

1、异常概述

(1)什么是异常,java提供异常处理机制有什么用?

什么是异常:程序执行过程中的不正常情况;并把异常信息打印输出到控制台

异常的作用:根据异常优化代码,增强程序的健壮性。

(2)以下程序执行控制台出现了:

Exception in thread "main" java.lang.ArithmeticException: / by zero

at com.bjpowernode.javase.exception.ExceptionTest01.main(ExceptionTest01.java:14)

这个信息被我们称为:异常信息;由Java虚拟机(JVM)打印的。

package com.bjpowernode.javase.exception;

public class ExceptionTest01 {
    public static void main(String[] args) {
        int x = 10;
        int y = 0;
        int z = x / y;
        System.out.println(x+"/"+y+"="+z); //java.lang.ArithmeticException: / by zero

        //有了这个异常提示,我们就可以优化代码,增加程序的健壮性
        int x = 10;
        int y = 0;
        // 处理有可能出现的异常
        if(y==0){
            System.out.println("除数不能为0");
            return;
        }
        int z = x / y;
        System.out.println(x+"/"+y+"="+z);

    }
}

2、java中异常以类和对象的形式存在

java语言中异常是以什么形式存在的呢?

(1)异常在java中以类的形式存在,每一个异常类都可以创建异常对象。

(2)异常对应的现实生活中是怎样的?

火灾(异常类):

        2008年8月8日,小明家着火了(异常对象)

        2008年8月9日,小刚家着火了(异常对象)

        2008年9月8日,小红家着火了(异常对象)

(3)类:是模板;对象是:实际存在的个体

        钱包丢了(异常类):

        2008年1月8日,小明的钱包丢了(异常对象)

        2008年1月9日,小芳的钱包丢了(异常对象)

package com.bjpowernode.javase.exception;

public class ExceptionTest02 {
    public static void main(String[] args) {
        //1.通过"异常类"实例化(创建)对象
        NumberFormatException nfe = new NumberFormatException("数字格式化异常");
        System.out.println(nfe); //调用toString方法,java.lang.NumberFormatException: 数字格式化异常

        //2.
        NullPointerException npe = new NullPointerException("空指针异常");
        System.out.println(npe); //java.lang.NullPointerException: 空指针异常

        //3.
        int a = 10;
        int b = 0;
        // 实际上JVM在执行到此处的时候,会new异常对象:new ArithmeticException("/ by zero");
        // 并且JVM将new的异常对象抛出,打印输出信息到控制台了。
        int c = a / b;//这里出现异常
        System.out.println(a+"/"+b+"="+c);

    }
}

3、UML及starUML

(1)什么是UML?有什么用?

UML是一种统一建模语言;一种图标式语言(画图的)

UML不是只有java中使用,只要是面向对象的编程语言,都有UML。

在UML图中可以描述类和类之间的关系,程序执行的流程,对象的状态等。

(2)异常在java中以类和对象的形式存在,那么异常的继承结构是怎样的?

我们可以使用UML图来描述一下继承结构;例如:Object

Object下有Throwable(可抛出的)。

Throwable下有两个分支:Error(不可处理,直接退出JVM)Exception(可处理的)。

(3)重点

Exception的直接子类:编译时异常(编译时异常要求程序员在编写程序阶段必须预先对这些异常进行处理,如果不处理编译器报错,因此得名编译时异常)

RuntimeException:运行时异常(运行时异常在编写程序阶段程序员可以预先处理,也可以不管,都行)

注:实际上编译时异常和运行时异常都是发生在运行阶段的!

异常的继承结构图

JavaSE进阶 | 一文带你吃透Java中的异常处理机制_第1张图片

4、编译时异常和运行时异常区别

1、编译时异常和运行时异常,都是发生在运行阶段编译阶段,异常是不会发生的!
(1)编译时异常因为什么而得名?
因为编译时异常必须在编译(编写)阶段预先处理,如果不处理编译器报错,因此得名
所有异常都是在运行阶段发生的因为只有程序运行阶段才可以new对象;因为异常的发生就是new异常对象。              

2、编译时异常和运行时异常的区别?
(1)编译时异常一般发生的概率比较高
例如:你看到外面下雨了,你出门之前会预料到:如果不打伞,可能会生病(生病是一种异常)。而且这个异常发生的概率很高,所以我们出门之前要拿一把伞。 “拿一把伞”就是对“生病异常”发生之前的一种处理方式。对于一些发生概率较高的异常,需要在运行之前对其进行预处理。
(2)运行时异常一般发生的概率比较低
例如:小明走在大街上,可能会被天上的飞机轮子砸到;被飞机轮子砸到也算一种异常,但是这种异常发生概率较低。在出门之前你没必要提前对这种发生概率较低的异常进行预处理。假设你在出门之前,你把能够发生的异常都预先处理,你这个人会更加的安全,但是你这个人活的很累。             

3、假设java中没有对异常进行划分,没有分为:编译时异常和运行时异常?
所有的异常都需要在编写程序阶段对其进行预处理,将是怎样的效果呢?首先,如果这样的话,程序肯定是绝对的安全的;但是程序员编写程序太累,代码到处都是处理异常的代码。并且不太符合实际。

4、编译时异常又称作:受检异常(CheckedException)、受控异常。
     运行时异常又称作:未受检异常(UnCheckedException)、非受控异常。      

5、Java语言中对异常的处理包括两种方式:
第一种方式在方法声明的位置上,使用throws关键字,抛给上一级;谁调用我,我就抛给谁,抛给上一级。
第二种方式:使用try...catch语句进行异常的捕捉;这件事发生了,谁也不知道,因为我给抓住了,自己处理。

例如:我是某集团的一个销售员,因为我的失误,导致公司损失了1000元, “损失1000元”这可以看做是一个异常发生了;我有两种处理方式,
(1)我把这件事告诉我的领导【异常上抛】
(2)我自己掏腰包把这个钱补上【异常的捕捉】            
思考:异常发生之后,如果我选择了上抛,抛给了我的调用者,调用者需要对这个异常继续处理,那么调用者处理这个异常同样有两种处理方式(上抛或捕捉)。

6、注意:Java中异常发生之后如果一直上抛,最终抛给了main方法,main方法继续向上抛,抛给了调用者JVM(Java虚拟机),JVM知道这个异常发生一旦发生;只有一个结果,就是终止java程序的执行!

5、运行时异常可以不处理 

(1)程序执行到此处发生了ArithmeticException异常,底层实际上new了一个ArithmeticException异常对象,然后抛出了,由于是main方法调用了100 / 0,所以这个异常ArithmeticException抛给了main方法,main方法并没有做任何的处理,将这个异常自动抛给了JVM;JVM最终终止程序的执行。

(2)ArithmeticException 继承 RuntimeException,属于运行时异常;在编写程序阶段不需要对这种异常进行预先的处理。

package com.bjpowernode.javase.exception;

public class ExceptionTest03 {
    public static void main(String[] args) {
        
        //1.这里为什么不进行异常处理?属于运行时异常
        System.out.println(100 / 0);

        //2.这里的HelloWorld没有输出,没有执行
        System.out.println("Hello World!");

    }
}

6、方法声明位置上使用throws

以下代码报错的原因是什么?

答:因为doSome()方法声明位置上使用了:throws ClassNotFoundException而ClassNotFoundException是编译时异常;必须编写代码时处理,没有处理,编译器报错。

package com.bjpowernode.javase.exception;

public class ExceptionTest04 {
    public static void main(String[] args) {
        // main方法中调用doSome()方法
        // 因为doSome()方法声明位置上有:throws ClassNotFoundException
        // 我们在调用doSome()方法的时候必须对这种异常进行预先的处理。
        // 如果不处理,编译器就报错。
        //编译器报错信息: Unhandled exception: java.lang.ClassNotFoundException
        doSome(); //err
    }

    /**
     * doSome方法在方法声明的位置上使用了:throws ClassNotFoundException
     * 这个代码表示doSome()方法在执行过程中,有可能会出现ClassNotFoundException异常。
     * 叫做类没找到异常。这个异常直接父类是:Exception,所以ClassNotFoundException属于编译时异常。
     * @throws ClassNotFoundException
     */
    public static void doSome() throws ClassNotFoundException{ //没有对异常进行处理
        System.out.println("doSome!!!!");
    }

}

7、异常处理的原理

上抛给调用者或者捕捉自行处理!

package com.bjpowernode.javase.exception;

public class ExceptionTest05 {
    // 第一种处理方式:在方法声明的位置上继续使用:throws,来完成异常的继续上抛。
    // 抛给调用者。上抛类似于推卸责任。(继续把异常传递给调用者)
    // main方法上上抛,一旦异常发生,JVM就会终止程序
    public static void main(String[] args) throws ClassNotFoundException {
        doSome();
    }
     

    // 第二种处理方式:try..catch进行捕捉。
    // 捕捉等于把异常拦下了,异常真正的解决了。(调用者是不知道的)
    //直接doSome()然后alt+回车就可以生成异常捕捉的函数
    public static void main(String[] args) {
        try {
            doSome(); //alt+回车自动生成
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    public static void doSome() throws ClassNotFoundException{
        System.out.println("doSome!!!!");
    }

}

8、异常捕捉和上报的联合使用

(1)处理异常的第一种方式:

在方法声明的位置上使用throws关键字抛出,谁调用我这个方法,我就抛给谁,抛给调用者来处理,这种处理异常的态度:上报

(2)处理异常的第二种方式:

使用try..catch语句对异常进行捕捉,这个异常不会上报,自己把这个事儿处理了。异常抛到此处为止,不再上抛了。

(3)注意:只要异常没有捕捉,采用上报(throws)的方式,此方法的后续代码不会执行

    另外需要注意的是try语句块中的某一行出现异常,该行后面的代码不会执行。

    try..catch捕捉异常完成之后,后续代码可以继续执行。

(4)在以后的开发中,处理编译时异常,应该上报还是捕捉呢,怎么选?

如果希望调用者来处理,选择throws上报;其它情况使用捕捉的方式。

(5)一般不建议在main方法上使用throws,因为这个异常如果真正的发生了,一定会抛给JVM,JVM只有终止;异常处理机制的作用就是增强程序的健壮性,怎么能做到,异常发生了也不影响程序的执行,所以一般main方法中的异常建议使用try..catch进行捕捉,main就不要继续上抛了。

(6)采用throws上抛的方式,可以抛当前的异常,也可以抛当前异常类的父类。

(7)方法中有调用关系,一个方法出现抛出异常,它的调用者也要继续throws上抛或者try...catch进行捕捉。

package com.bjpowernode.javase.exception;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;


public class ExceptionTest06 {
    //1.mian方法---------main方法建议使用try...catch捕捉
    public static void main(String[] args) {

        System.out.println("main begin");
        try {
            // try尝试
            m1();
            // 以上代码出现异常,直接进入catch语句块中执行,hello world就不会执行。
            System.out.println("hello world!");
        } catch (FileNotFoundException e){ // 数据类型 变量。
            // 这个分支中可以使用e引用,e引用保存的内存地址是那个new出来异常对象的内存地址。
            // catch是捕捉异常之后走的分支。在catch分支中干什么?处理异常。
            System.out.println("文件不存在,可能路径错误,也可能该文件被删除了!");
            System.out.println(e); 
  //java.io.FileNotFoundException: D:\course\01-课\学习方法.txt (系统找不到指定的路径。)
        }

        // try..catch把异常抓住之后,这里的代码会继续执行。
        System.out.println("main over");
    }

    //2.m1()方法-----------
    private static void m1() throws FileNotFoundException {
        System.out.println("m1 begin");
        m2();
        // 以上代码出异常,这里是无法执行的。
        System.out.println("m1 over");
    }

   
    //----2.1 抛FileNotFoundException的父对象IOException,这样是可以的。
    //private static void m2() throws IOException {

    //-----2.2 这样也可以,因为Exception包括所有的异常。
    //private static void m2() throws Exception{

    //------2.3 throws后面也可以写多个异常,可以使用逗号隔开。
    //private static void m2() throws ClassCastException, FileNotFoundException{

    //3.m2()方法-----------
    private static void m2() throws FileNotFoundException {
        System.out.println("m2 begin");
        m3();
        // 以上如果出现异常,这里是无法执行的!
        System.out.println("m2 over");
    }
    
    //4.m3()方法-----------
    private static void m3() throws FileNotFoundException {
        // 调用SUN jdk中某个类的构造方法。这个类是后期IO流的知识点
        // 创建一个输入流对象,该流指向一个文件。
/*
        编译报错的原因是什么?
            第一:这里调用了一个构造方法:FileInputStream(String name)
            第二:这个构造方法的声明位置上有:throws FileNotFoundException
            第三:通过类的继承结构看到:FileNotFoundException父类是IOException,IOException的父类是Exception,最终得知,FileNotFoundException是编译时异常。
            错误原因?编译时异常要求程序员编写程序阶段必须对它进行处理,不处理编译器就报错。
*/
        new FileInputStream("D:\\course\\01-开课\\学习方法.txt");
        
        // 我们采用第一种处理方式:在方法声明的位置上使用throws继续上抛。
        // 一个方法体当中的代码出现异常之后,如果上报的话,下面的代码不会执行;此方法结束。
        System.out.println("如果以上代码出异常,这里会执行吗??????????????????不会!!!");
    }
}
/*
    最终执行的结果
    main begin
    m1 begin
    m2 begin
    文件不存在,可能路径错误,也可能该文件被删除了!
    java.io.FileNotFoundException: D:\course\01-课\学习方法.txt (系统找不到指定的路径。)
    main over
*/

9、try...catch深入理解

(1)catch后面的小括号中的类型可以是具体的异常类型,也可以是该异常类型的父类型。

(2)catch可以写多个,也可以只写一个catch,采用异或的方式建议catch的时候,精确的一个一个处理,这样有利于程序的调试。

3、catch写多个的时候,异常范围从上到下,必须遵守从小到大的原则。

package com.bjpowernode.javase.exception;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class ExceptionTest07 {

    //1.对于throws我们可以抛出当前异常类或者它的父类,也可以抛出多个异常
    public static void main(String[] args) throws Exception,                 
   FileNotFoundException,NullPointerException {
    }

    //2.采用try...catch注意:catch里面写的不是当前类FileInputStream
    // 而是当前类FileInputStream抛出的异常FileNotFoundException
    public static void main(String[] args) {
        try {
            FileInputStream fis = new FileInputStream("C:\\C语言学习\\initial-learning\\1.txt");
            System.out.println("hello");
        } catch (FileNotFoundException e) {
            System.out.println("文件不存在");
        }

        System.out.println("world");
    }
    //如果当前文件存在:结果是:hello、world
    //如果当前文件不存在,出现异常:结果是:文件不存在、world

    //3.catch后面的小括号中的类型可以是具体的异常类型,也可以是该异常类型的父类型
    public static void main(String[] args) {
        try {
            FileInputStream fis = new FileInputStream("C:\\C语言学习\\initial-learning\\1.txt");
            System.out.println("hello");
        } catch (IOException e) {
            //上面不写FileNotFoundException,写IOException也没问题
            //原因:多态IOException = new FileNotFoundException();
            //父类型的引用指向子类型的对象
            //当然写成IOException的父类Exception也是没问题的,多态
            System.out.println("文件不存在");
        }

        System.out.println("world");
    }

   //4.catch可以写多个
    //第一种方法:直接多写结果catch
    public static void main(String[] args) {
        try {
            //创建输入流
            FileInputStream fis = new FileInputStream("C:\\C语言学习\\initial-learning\\1.txt");
            //读文件
            fis.read(); //read会抛出IOException异常
        } catch (FileNotFoundException e) {
            System.out.println("文件不存在");
        }catch (IOException e){
            System.out.println("读文件报错了");
        }

    //第二种方法:写上面两个异常的父类
    public static void main(String[] args) {
        try {
            //创建输入流
            FileInputStream fis = new FileInputStream("C:\\C语言学习\\initial-learning\\1.txt");
            //读文件
            fis.read(); //read会抛出IOException异常
        } catch (Exception e) { //直接写父类,所有的异常都走这个分支
            System.out.println("文件不存在");
        }
    }
    }

     //5.catch写多个的时候,从上到下,必须遵守从小到大。
      //大的写上面就有可能已经捕捉过了,下面在捕捉就会报错
    public static void main(String[] args) {
        try {
            //创建输入流
            FileInputStream fis = new FileInputStream("C:\\C语言学习\\initial-learning\\1.txt");
            //读文件
            fis.read(); //read会抛出IOException异常
        } catch (IOException e) {
            System.out.println("文件不存在");
        }catch (FileNotFoundException e){ //上大下小,err
            System.out.println("读文件报错了");
        }
    
    //6.JDK8的新特性,采用异或的方式
    public static void main(String[] args) {

        try {
            //创建输入流
            FileInputStream fis = new FileInputStream("D:\\curse\\02-JavaSE\\document\\JavaSE进阶讲义\\JavaSE进阶-01-面向对象.pdf");
            // 进行数学运算
            System.out.println(100 / 0); // 这个异常是运行时异常,编写程序时可以处理,也可以不处理。
        } catch(FileNotFoundException | ArithmeticException | NullPointerException e) {
            System.out.println("文件不存在?数学异常?空指针异常?都有可能!");
        }
    }

}

10、异常对象的常用方法

异常对象有两个非常重要的方法:

(1)获取异常简单的描述信息:String msg = exception.getMessage();

(2)打印异常追踪的堆栈信息:exception.printStackTrace(); 并且打印异常追踪的堆栈信息是采用异步的方式进行打印的!

package com.bjpowernode.javase.exception;

public class ExceptionTest08 {
    public static void main(String[] args) {
        NullPointerException e = new NullPointerException("空指针异常");
        //1.直接打印
        System.out.println(e); //java.lang.NullPointerException: 空指针异常
        
        //2.使用getMessage()方法
        // 获取异常简单描述信息:这个信息实际上就是构造方法上面String参数。
        String msg = e.getMessage();
        System.out.println(msg); //空指针异常
        
        //3.使用printStackTrace()方法;打印异常堆栈信息
        // java后台打印异常堆栈追踪信息的时候,采用了异步线程的方式打印的。
        //所谓异步线程就是一个线程负责打印异常信息,另一个线程负责打印Hello World
        //所以,异常信息有可能在Hello World的上面,也有可能在下面
        e.printStackTrace();
        //java.lang.NullPointerException: 空指针异常
        //at com.bjpowernode.javase.exception.ExceptionTest08.main(ExceptionTest08.java:12)

        System.out.println("Hello World");

    }
}

怎么看异常的追踪信息,可以快速的调试程序呢?

   (1)异常信息追踪信息,从上往下一行一行看。但是需要注意的是:SUN写的代码就不用看了(看包名就知道是自己的还是SUN的)。主要的问题是出现在自己编写的代码上。

   (2)例如:下面这个例题,打印的e.printStackTrace();        

   java.io.FileNotFoundException: C:\jetns-agent.jar (系统找不到指定的文件。)
    at java.base/java.io.FileInputStream.open0(Native Method)
    at java.base/java.io.FileInputStream.open(FileInputStream.java:213)
    at java.base/java.io.FileInputStream.(FileInputStream.java:155)
    at java.base/java.io.FileInputStream.(FileInputStream.java:110)
    at com.bjpowernode.javase.exception.ExceptionTest09.m3(ExceptionTest09.java:31)
    at com.bjpowernode.javase.exception.ExceptionTest09.m2(ExceptionTest09.java:27)
    at com.bjpowernode.javase.exception.ExceptionTest09.m1(ExceptionTest09.java:23)
    at com.bjpowernode.javase.exception.ExceptionTest09.main(ExceptionTest09.java:14)

解释:因为31行出问题导致了27行,27行出问题导致23行,23行出问题导致14行,所以应该先查看31行的代码;31行是代码错误的根源。

package com.bjpowernode.javase.exception;

import java.io.FileInputStream;
import java.io.FileNotFoundException;

public class ExceptionTest09 {
    public static void main(String[] args) {
        try {
            m1();
        } catch (FileNotFoundException e) {
            e.printStackTrace();  //在实际的开发中,建议使用这个;这行代码要写上,养成好习惯!
            //使用try...catch默认调用的是printStackTrace()方法打印异常堆栈追踪信息!!!

           /* //当然也可以调用getMessage()方法
            String msg = e.getMessage();
            System.out.println(msg);//C:\test.txt (系统找不到指定的文件。)*/
        }

        // 捕捉以后,这里程序不耽误执行,很健壮。(服务器不会因为遇到异常而宕机)
        System.out.println("Hello World!"); //正常打印 Hello World
    }

    private static void m1() throws FileNotFoundException {
        m2();
    }
    private static void m2() throws FileNotFoundException {
        m3();
    }
    private static void m3() throws FileNotFoundException {
        new FileInputStream("C:\\test.txt");
    }
}

11、finally关键字

关于try..catch中的finally子句:

(1)在finally子句中的代码是最后执行的,并且是一定会执行的,即使try语句块中的代码出现了异常。

(2)finally子句必须和try一起出现,不能单独编写。

(3)finally语句通常使用在哪些情况下呢?

通常在finally语句块中完成资源的释放/关闭。因为finally中的代码比较有保障;即使try语句块中的代码出现异常,finally中代码也会正常执行。

package com.bjpowernode.javase.exception;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

//finally字句的使用
public class ExceptionTest10 {
    public static void main(String[] args) {

        //1.--------------不使用finally关键字的问题
        try {
            //创建输入流
            FileInputStream fis = new FileInputStream("C:\\1.txt");

            //开始读文件.......
            String  s = null;
            s.toString(); //这里出现空指针异常,下面流就不会关闭了
            System.out.println("hello"); //不会执行

            //流使用完需要关闭,因为流是占用资源的。
            //即使以上程序出现异常,流也必须要关闭!
            //放在空指针异常下面,就有可能导致流关不了。
            fis.close(); //调用close()方法会抛出IOException,需要处理

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e){
            e.printStackTrace();
        } catch (NullPointerException e){
            e.printStackTrace();
        }
        System.out.println("world"); //会执行,中间的fis.close()并没有执行

        //-----------------2.使用finally关键字的问题
        FileInputStream fis = null;// 声明位置放到try外面。这样在finally中才能用。
        try {
            //创建输入流
            fis = new FileInputStream("C:\\C语言学习\\initial-learning\\1.txt");

            //开始读文件.......
            String  s = null;
            s.toString(); //这里出现空指针异常,下面流就不会关闭了
            //System.out.println("hello"); //不会执行

            //流使用完需要关闭,因为流是占用资源的。
            //即使以上程序出现异常,流也必须要关闭!
            //放在空指针异常下面,就有可能导致流关不了。
            //fis.close(); //调用close()方法会抛出IOException,需要处理

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e){
            e.printStackTrace();
        } catch (NullPointerException e){
            e.printStackTrace();
        }finally{
            System.out.println("hello"); //执行
            if(fis != null){ //有可能创建输入流失败,判断一下,避免空指针异常
                // 流的关闭放在这里比较保险。
                // finally中的代码是一定会执行的。
                // 即使try中出现了异常!
                try {
                    // close()方法有异常,采用捕捉的方式。
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        System.out.println("world"); //会执行,中间的fis.close()并没有执行

    }
}

(4)finally语句:放在finally语句块中的代码是一定会执行的!

(5)try和finally,可以没有catch; try不能单独使用,try finally可以联合使用。

(6)当return和finally同时出现时,肯定先执行finally;因为一旦先执行return,当前方法就结束了,无法关闭一些资源;所以必然是执行完finally,在执行return!

package com.bjpowernode.javase.exception;

public class ExceptionTest11 {
    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!");
    }
}
/*
运行结果:
try...
finally...
*/

例:虽然finally里面的代码按理说一定会执行,但是如果调用System.exit(0)表示退出JVN,退出java虚拟机(JVM),此时finally就不会再执行!

package com.bjpowernode.javase.exception;

public class ExceptionTest12 {
    public static void main(String[] args) {
        try {
            System.out.println("try...");
            // 退出Java虚拟机(JVM)
            System.exit(0); // 退出JVM之后,finally语句中的代码就不执行了!
        } finally {
            System.out.println("finally...");
        }
    }
}
//执行结果:try...

例:finally经典面试题

java语法规则(有一些规则是不能破坏的,一旦这么说了,就必须这么做!):

(1)java中有一条这样的规则:

方法体中的代码必须遵循自上而下顺序依次逐行执行(亘古不变的语法!)

(2)java中又一条语法规则:

        return语句一旦执行,整个方法必须结束(亘古不变的语法!)

(3)下面的运行结果是出乎意料的100,怎么理解下面这个例题呢?

还是先执行finally里面的i++,在执行return i;根据代码必须遵循自上而下顺序依次逐行执行,i=100出现在return i的前面,而i++出现在return i的后面;所以返回的是i=100的值,而不是i++后i的值

package com.bjpowernode.javase.exception;
import java.sql.SQLOutput;

public class ExceptionTest13 {
    public static void main(String[] args) {
        int result = m();
        System.out.println(result); //100
    }


    public static int m(){
        int i = 100;
        try {
            // 这行代码出现在int i = 100;的下面,所以最终结果必须是返回100
            // return语句还必须保证是最后执行的。一旦执行,整个方法结束。
            return i;
        } finally {
            i++;
        }
    }
}

/*
反编译之后的效果
public static int m(){
    int i = 100;
    int j = i;
    i++;
    return j;
}
 */

final、finally、finalize有什么区别?

(1)final 关键字

final修饰的类无法继承。

final修饰的方法无法覆盖。

final修饰的变量不能重新赋值。

final修饰的引用无法在指向其它引用。

final修饰常量一般与static联合使用。

(2)finally 关键字

和try一起联合使用,可以没有catch。

finally语句块中的代码是必须执行的(除了System.exit(0)退出JVM,finally不会被执行)。

(3)finalize 方法

是一个Object类中的方法名,这个方法是由垃圾回收器GC负责调用的。

例如:一个类重写了finalize方法,如果堆上的垃圾比较多,就会调用这个方法;也可以使用System.gc()方法,建议开启垃圾回收机制,底层也会调用finalize方法。

package com.bjpowernode.javase.exception;

public class ExceptionTest14 {
    public static void main(String[] args) {

        //1、 final是一个关键字。表示最终的。不变的。
        final int i = 100;

        //2、 finally也是一个关键字,和try联合使用,使用在异常处理机制中
        // 在fianlly语句块中的代码是一定会执行的。
        try {

        } finally {
            System.out.println("finally....");
        }

        //3、 finalize()是Object类中的一个方法。作为方法名出现。
        // 所以finalize是标识符。
        // finalize()方法是JVM的GC垃圾回收器负责调用。
        
    }
}

//1、 final修饰的类无法继承
final class A {
    // 常量。
    public static final double MATH_PI = 3.1415926;
}
class B {
    // final修饰的方法无法覆盖
    public final void doSome(){

    }
}

13、怎么自定义异常(重点)

(1)SUN提供的JDK内置的异常肯定是不够的用的;在实际的开发中,有很多业务,这些业务出现异常之后,JDK中都是没有的,是和业务挂钩的;那么异常类就需要程序员可以自己定义异常,自己调用。

(2)Java中怎么自定义异常呢?两步:

第一步:编写一个类继承Exception(编译时异常)或者RuntimeException(运行时异常);但都是发生在运行阶段的。

第二步:提供两个构造方法,一个无参数的,一个带有String参数的。

package com.bjpowernode.javase.exception;

public class MyException extends Exception{ // 编译时异常
    public MyException(){

    }
    public MyException(String s){
        super(s); // 调用父类的构造方法
    }
}


public class MyException extends RuntimeException{ // 运行时异常

}
package com.bjpowernode.javase.exception;

public class ExceptionTest15 {
    public static void main(String[] args) {
        //创建异常对象
        MyException e = new MyException("用户名不能为空");
        /*com.bjpowernode.javase.exception.MyException: 用户名不能为空
        at com.bjpowernode.javase.exception.ExceptionTest15.main(ExceptionTest15.java:6)*/
        //打印异常堆栈信息
        e.printStackTrace();
        //获取简单描述信息
        String str = e.getMessage();
        System.out.println(str);// 用户名不能为空

    }
}

14、自定义异常在实际开发中的作用(重点)

上面我们已经学会了自定义异常,那么自定义异常在实际开发中怎么使用呢?我们不妨用以前写的栈的实现,来运用一下自定义异常的使用。

第一步:先自定义异常

package com.bjpowernode.javase.exception.MyStackException;
/*
自己定义异常,需要满足两个条件:
   1、继承Exception或者RunException
   2、有两个构造方法
*/
public class MyStackException extends  Exception{
    public MyStackException(){
    }
    public MyStackException(String s){
        super(s);
    }
}

第二步:测试模拟栈的实现,我们原来采用System.out.println输出错误信息是非常low的,这里就通过抛出异常的方式,打印错误信息

package com.bjpowernode.javase.exception.MyStackException;

public class MyStack {

    private Object[] object;
    private int sz;

    //无参构造方法
    public MyStack() {
        this.object = new Object[10];
        this.sz= -1;
    }

    //push方法模拟压栈。(栈满了,要有提示信息。)
    public void push(Object obj) throws MyStackException{
        if(sz >= object.length-1){
            /*1、修改之前:
            System.out.println("栈已满,压栈失败");
            return;*/

            /*2、使用自定义异常进行优化
            MyStackException e = new MyStackException("栈已满,压栈失败");
            throw e;//抛出,并且要进行上抛
            这里捕捉没有意义,自己new一个异常,自己捉没有意义。栈已满这个信息你需要传递出去。
           */

            //3.进行合并
            throw new MyStackException("栈已满,压栈失败");
        }
        //因为是从-1开始,要先sz++在压栈
        sz++;
        object[sz] = obj; //也可以合并object[++sz] = obj
        System.out.println("压栈"+obj+"元素成功,栈帧指向"+sz);
    }

    //pop方法模拟弹栈。(栈空了,也有有提示信息。)
    public void pop() throws MyStackException {
        if(sz < 0){
           /* 1、修改之前:
            System.out.println("栈已空,弹栈失败");
            return;*/

          /* //2、使用自定义异常进行优化
            MyStackException e = new MyStackException("栈已空,弹栈失败");
            throw e;*/

            //3.进行合并
            throw new MyStackException("栈已空,弹栈失败");

        }
        System.out.println("弹栈"+object[sz]+"元素成功");
        sz--;
        System.out.println("栈帧指向"+sz);
    }

    //set和get方法----这里实际上没有什么用处,也要写
    public Object[] getObject() {
        return object;
    }
    public void setObject(Object[] object) {
        this.object = object;
    }
    public int getIndex() {
        return sz;
    }
    public void setIndex(int index) {
        this.sz = index;
    }

}

第三步:进行测试

package com.bjpowernode.javase.exception.MyStackException;
//进行测试
public class MyStackExceptionTest {
    public static void main(String[] args) {
        MyStack mystack = new MyStack();

        //压栈
        try {
            mystack.push(new Object());
            mystack.push(new Object());
            mystack.push(new Object());
            mystack.push(new Object());
            mystack.push(new Object());
            mystack.push(new Object());
            mystack.push(new Object());
            mystack.push(new Object());
            mystack.push(new Object());
            mystack.push(new Object());

            mystack.push(new Object());
        } catch (MyStackException e) { // 捕捉并打印异常堆栈信息
            //e.printStackTrace();
            System.out.println(e.getMessage());
        }

        //弹栈
        try {
            mystack.pop();
            mystack.pop();
            mystack.pop();
            mystack.pop();
            mystack.pop();
            mystack.pop();
            mystack.pop();
            mystack.pop();
            mystack.pop();
            mystack.pop();

            mystack.pop();
        } catch (MyStackException e) {
            //e.printStackTrace();
            System.out.println(e.getMessage());
        }
    }
}

15、异常与方法覆盖

(1)之前在讲解方法覆盖的时候,当时遗留了一个问题?重写之后的方法不能比重写之前的方法抛出更多(更宽泛)的异常,可以更少;现在就可以理解啦!

(2)总结异常中的关键字:    

异常捕捉:try、catch、finally     
throws 在方法声明位置上使用,表示上报异常信息给调用者。     
throw 手动抛出异常!
 

(3)关于异常的两条结论:

父类不抛出编译时异常;子类按理说也不能抛出编译时异常,但可以抛出运行时异常!

父类抛出异常,但是子类可以不抛出异常,也可以抛出异常;要是抛出异常也只能抛出比父类范围更小或者相等的异常。

package com.bjpowernode.javase.exception;

class Animal {
    public void doSome(){

    }

    public void doOther() throws Exception{

    }
}

class Cat extends Animal {

    //1. 编译报错。父类不抛,子类抛会编译时异常报错
    public void doSome() throws Exception{

    }
    //
     //2. 编译正常。父类不抛,子类抛运行时异常没问题
    public void doSome() throws RuntimeException{

    }
    

    //3. 编译正常。父类抛,子类不抛,没问题
    public void doOther() {

    }

    //4. 编译正常。子类和父类都抛,抛的是同一个异常也可以
    public void doOther() throws Exception{

    }

    //5. 编译正常。子类抛的异常不能比父类抛出更多(更宽泛)的异常
    public void doOther() throws NullPointerException{ //抛Exception的子类---可以

    }


}

总结:

(1)父类不抛出编译时异常;子类也不能抛编译时异常 或者 可以抛出运行时异常!

(2)父类抛出异常,子类可以抛出与父类相同的异常 或者 异常的子类(更小范围的异常) 或者 不抛异常!

16、小试牛刀

编写程序模拟用户注册:
(1)程序开始执行时,提示用户输入“用户名”和“密码”信息。
(2)输入信息之后,后台java程序模拟用户注册。
(3)注册时用户名要求长度在[6-14]之间,小于或者大于都表示异常。

注意:完成注册的方法放到一个单独的类中;自定义异常类!编写main方法,在main方法中接收用户输入的信息,在main方法中调用UserService的register方法完成注册。

 1. 自定义异常类

package com.bjpowernode.javase.exception.homework;

public class MyException extends Exception{
    public MyException(){

    }
    public MyException(String s){
        super(s);
    }

}

2. 编写登录方法

package com.bjpowernode.javase.exception.homework;

public class UserService {
    public void register(String username,String password) throws MyException {
        if(username == null || username.length()<6 || username.length()>14){
            // 引用等于null的这个判断最好放到所有条件的最前面,
            // 并且把null放到最前面,例如:null = username
            throw new MyException("用户名不合法,长度必须在[6-14]之间");
        }

        // 程序能够执行到此处说明,用户名合法
        System.out.println("注册成功,欢迎["+username+"]");
    }
}

3. 进行测试

package com.bjpowernode.javase.exception.homework;
/**
 * 用户注册
 * @param username 用户名
 * @param password 密码
 * @throws IllegalNameException 当用户名为null,或者用户名长度小于6,或者长度大于14,会出现该异常!
 */

public class Test {
    public static void main(String[] args) {
        // 创建UserService对象
        UserService u  = new UserService();
        try {
            u.register("zhangkang","123456");
        } catch (MyException e) {
            //e.printStackTrace();
            System.out.println(e.getMessage());
        }
    }
}

你可能感兴趣的:(java,jvm,开发语言)