Java学习笔记 08、异常处理

简介: Java学习笔记 08、异常处理

一、异常概述与异常体系结构


概述说明


异常分类:编译时异常与运行时异常


编译时异常:源代码.java文件在编译器编译时发生的错误。

运行时异常:执行字节码文件时发生不正确情况,其称为异常。

这里要讲的异常指的是运行时异常,其分为两类异常事件:


Error:Java虚拟机无法解决的严重问题。例如:JVM系统内部错误、资源耗尽等严重情况。比如:StackOverflowError(栈溢出)和OOM(内存溢出)。

Exception:其他因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理,例如空指针访问、试图读取不存在文件、网络连接中断、数组角标越界问题。

Error表示不可控异常一般不编写针对性的代码进行处理;而对于Exception这类异常,我们可以编写针对性的代码进行处理。



Error的实例


看Error的两个实例:栈溢出与内存溢出


package com.mjj.pro7;
public class Test1 {
    public static void main(String[] args) {
        //1.栈溢出,无限压栈 报错 java.lang.StackOverflowError
        //main(args);
        //2.堆溢出,创建占用超过初始jvm使用的内存,java.lang.OutOfMemoryError:
        Integer[] arr = new Integer[1024*1024*1024];
    }
}


针对于这Error的情况,我们不对其进行针对性处理。



二、常见异常


异常体系结构



非受检异常:例如RuntimeException在默认情况下会得到自动处理,可以捕获RuntimeException异常,但对于自己的封装RuntimeException的异常,一部分还是需要进行手动抛出。


受检异常:Java编译器要求程序必须捕获或声明抛出这种异常。



RuntimeException举例

表示运行时异常,接下来进行实例举例


NullPointerException(空指针)


import org.junit.Test;
public class ExceptionTest {
    //NullPointerException
    @Test
    public void test1(){
        //例1
//      int[] arr = null;
//      System.out.println(arr[3]);
        //例2
        String str = null;
        System.out.println(str.charAt(0));
    }
}


IndexOutOfBoundsException(下标越界)
//IndexOutOfBoundsException
    @Test
    public void test2(){
        //第一种 ArraryIndexOutOfBoundsException
//      int[] arr = new int[10];
//      System.out.println(arr[10]);
        //第二种 StringIndexOutOfBoundsException
        String arr = "123";
        System.out.println(arr.charAt(3));
    }


ClassCastException(类型转换)
//ClassCastException 类型转换问题
@Test
public void test3(){
    Object obj = new Date();
    String str = (String)obj;
}


NumberFormatException(数值转换)
//NumberFormatException 数值类型转换
@Test
public void test4(){
    String str = "123";  //是通过的
    String str1 = "abc";
    int num = Integer.parseInt(str1);
}


InputMismatchException(输入不匹配)
//InputMismatchException 输入不匹配
@Test
public void test5(){
    Scanner sca = new Scanner(System.in);
    int num = sca.nextInt();  //当输入abc时会报这个错误
    sca.close();
}


ArithmeticException(算术异常)
//ArithmeticException 算术异常
@Test
public void test6(){
    System.out.println(5/0);//java.lang.ArithmeticException: / by zero
}


三、异常处理概述


异常处理好处

问:对于上面异常体系结构中不受检异常指的是我们不进行异常处理系统也会自行捕捉到异常,并且输出异常信息。那么我们处理异常与不处理异常的区别在哪以及为什么要进行异常处理?


区别描述:对不受检异常不进行异常处理时,若我们程序发生异常,就会直接终止程序;若是进行异常处理,程序会按照我们要求进行异常处理并且继续执行程序。

目的:能够让我们对异常更好的处理以及程序继续执行下去。

看一下是否进行异常处理的区别


①不进行异常处理


public class Main {
    public static void main(String[] args){
        String str = "abc";
        int number;
        //有异常的语句
        int i = Integer.parseInt(str);
        System.out.println(123);
    }
}



可以看到一旦出现异常程序直接停止,后面的语句不再执行!!!


②进行异常处理


public class Main {
    public static void main(String[] args){
        String str = "abc";
        int number;
        //进行异常处理
        try {
            int i = Integer.parseInt(str);
        } catch (NumberFormatException e) {
            e.printStackTrace();
        }
        System.out.println(123);
    }
}



我们可以看到程序完整的执行了下来后面的语句也进行了执行,这里我们是对异常情况进行打印输出!!!



抓抛模型

对异常的处理会有两个过程:抛、抓


过程一:“抛”,程序在正常执行的过程中,一旦出现异常,就会在异常代码处生成一个对应异常类的对象,并将此对象抛出。一旦抛出对象以后,其后的代码都不执行。

过程二:“抓”,可以理解为对异常的处理方式,如:try-catch-finally、throws


异常处理机制一:try-catch-finally

语法:


try{
//可能出现异常的代码
}catch(异常类型1 变量名1){
//处理异常的方式1
}catch(异常类型2 变量名2){
//处理异常的方式2
}
...
finally{
//一定会执行的代码
}


try-catch注意点

try中包裹可能出现异常的代码,若出现异常先生成指定异常,接着去catch中去找匹配异常。

当try中出现错误,进入catch部分中进行异常处理,一旦处理完就执行finally中包裹内容(finally存在),接着跳出try-catch结构,继续执行try-catch结构之外的代码。

catch中的异常类型如果没有子父类关系,谁在上谁在下都无所谓;若是多个catch中有子父类关系的,子类必须要声明在父类异常之上,否则报错。父类在上的话,子类异常声明在下就没意义了!!!

catch的{}中异常对象处理方式常见的两种:

e.getMessage():获取异常的简略信息。

e.printStackTrace():比较常用,打印完整的堆栈情况,会显示指定的出错位置。

注意在try中声明的变量(局部变量),其生命周期只在try结构中,try-catch结构之外无法调用。


注意点5中的实例演示:


public class Main {
    public static void main(String[] args){
        String str = "abc";
        int number;
        try {
            int i = Integer.parseInt(str);
        } catch (NumberFormatException e) {
            //e.printStackTrace(); //详细堆栈错误信息
            System.out.println(e.getMessage());//简略信息
        }
    }
}





finally注意点


finally中声明的代码是一定执行的,尽管catch中出现异常会先执行finally,再抛出异常。

try-catch-finally使用于方法中catch与finally包含return,无论catch是否有异常,都会执行finally中的return返回。


注意点1中情况举例:


public class Main {
    public static void main(String[] args){
        try{
            int a=10;
            int b=0;
            System.out.println(a/b);
        }catch(ArithmeticException e){
            int[] a = new int[10];
            //这里有数组越界异常
            System.out.println(a[10]);
        }
      finally{
          System.out.println("执行finally语句");
      }
        System.out.println("长路哥哥");
    }
}


catch中如果出现异常,只会执行finally中的代码,try-catch结构外的也不会处理!!!



注意点2中举例:


public class Main {
    public static void main(String[] args){
        System.out.println(Main.method());
        System.out.println(123456);
    }
    public static int method(){
        try{
            System.out.println(10/0);
        }catch(ArithmeticException e){
            int[] a = new int[10];
            //这里有数组越界异常
            System.out.println(a[10]);
            return 2;
        }
        finally{
            return 3;
        }
    }
}



可以看到结果没有出现异常,说明返回的是finally中的。


原因:在方法中catch出现异常了,会直接先去执行finally中内容,这里finally中是返回值,那么当catch异常处理前方法已经结束了,所以没有报异常出来!!!



finally实际使用

例如数据库连接、输入输出流,网络编程Socket等资源,JVM是不能自动的回收的,我们需要自己手动的进行资源的释放,此时的资源释放,就需要声明在finally中。


下面例子是演示输入输出流的关闭:


import java.io.*;
public class Main {
    public static void main(String[] args){
        File file = new File("hello.txt");
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(file);
            int read = fis.read();
            while(read != -1){
                System.out.println((char)read);
                read = fis.read();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            //fis放在这里进行关闭资源 再使用一个try-catch是因为IOException是受检型的必须声明
            try {
                if(fis != null)
                    fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}


在项目工程目录下添加hello.txt文件后执行




总结

使用于main()中:

try-catch:try异常,catch无异常进行处理,执行try-catch结构外的。

try-catch:try异常,catch异常,直接结束程序(打印异常)。

try-catch-finally:try异常,执行指定catch中,无异常会先执行完catch内容,接着执行finally中内容,然后执行try-catch以外内容。

try-catch-finally:try异常,执行指定catch中若是有异常会先执行finally内容,接着程序结束(打印异常)。


调用单独方法中:

try-catch:try异常,catch无异常,正常执行后序程序。

try-catch:try异常,catch异常,程序直接结束(打印异常)。

try-catch-finally:catch与finally中都有返回值,若是catch中出现异常,会先去找finally,之后直接结束方法,此时异常也不会打印。

无try-catch结构捕捉异常,也无throws抛出,对于出现非受检查的异常系统会自动抛出。


总结总结:catch中无异常,执行完catch后执行finally以及try-catch结构之外的;catch中若是出现异常会先去执行finally中内容,接着程序直接停止。



异常处理机制二:throws

认识及使用throws

语法:throws 异常类


//例如
public void method()throws RuntimeException{
}


写在方法的声明处,指明此方法执行时可能会抛出的异常。一旦方法体执行时,出现异常,仍会在异常代码处生成一个异常类的对象,若此对象满足throws的异常就会抛出,在方法中出现异常代码后续的代码就不会执行。


与try-catch-finally比较:


try-catch-finally:真正将异常处理掉。

throws:将异常抛给方法的调用者,并没有真正将异常处理掉。

实例1:throws声明可能会抛出的异常(表示了一种规范),方法中出现异常则会向上传递:


import java.io.*;
public class Main {
    public static void main(String[] args){
        try {
            method1();
        } catch (RuntimeException e) {
            System.out.println("捕捉到方法中的异常");
        }
    }
    public static void method1() throws RuntimeException {
        method2();
    }
    public static void method2 () throws RuntimeException{
        System.out.println(5/0);
    }
}



其实就算我两个方法都不添加throws RuntimeException,最终使用try-catch也是能够接收到的,那为什么要使用thorows声明勒?也可以算是一种规范吧,声明则指明其方法可能会出现的异常,好让调用方法者知道捕捉异常时使用什么类型捕捉!!!不声明的话使用try-catch结构中catch默认会是Exception。



重写方法异常抛出规则

对于重写方法throws的规则:


子类重写方法的抛出异常类型应当小于或等于父类方法抛出异常类型

父类中没有throws异常,子类重写时也不应该有

实例如下:


class SuperClass{
    public void method()throws IOException{
    }
}
class SubClass extends SuperClass{   //继承SuperClass
    @Override
    public void method() throws FileNotFoundException {  //子类重写方法异常应当小于等于父类的异常
    }
}


开发中如何选择异常处理机制

若是被重写父类的方法中没有throws,那么子类重写的方法必定也没有throws,若重写方法有异常,必须使用try-catch解决。

若调用多个不同的方法,并且几个方法是递进关系,那么建议使用throws,最先执行调用的方法使用try-catch捕捉异常。

try(){}语法

Java7 build 105版开始,Java7的编译器和运行环境支持新的 try-with-resources 语句,称为 ARM 块(Automatic Resource Management) ,自动资源管理。


语法如:try块退出时,会自动调用res.close()方法,关闭资源


try(Resource res = xxx)//可指定多个资源
{
     work with res
}


这相比我们之前在finally中一个个手动关闭资源好的多。


我们看一下两者对比:


//以前try{}catch{}:
FileInputStream fis = null;
FileOutputStream fos = null;
try {
    //目标图片1234.jpg
    fis = new FileInputStream(new File("1234.jpg"));
    //复制地址
    fos = new FileOutputStream(new File("图片.jpg"));
    ....
} catch (IOException e) {
    e.printStackTrace();
}finally {
    if(fis != null){
        try {
            fis.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    if(fos != null){
        try {
            fos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
//现在的try(){}
try (
    FileInputStream fis = new FileInputStream(new File("1234.jpg"));
    FileOutputStream fos = new FileOutputStream(new File("图片.jpg"));
  ){
    ....
} catch (IOException e) {
    e.printStackTrace();
}


省去了大量的语句,舒服!!!



四、手动抛出异常throw


介绍一下异常对象的产生:①系统在出现异常时自动生成异常对象②手动生成一个异常对象,例如throw new Exception()


只要是使用了throw,就一定表示抛出了一个异常,而throws只是用于声明可能抛出的异常便于调用其方法的人做出相应的异常处理!!


实例:手动抛出异常,并使用throws声明该方法可能会有什么异常


public class Main {
    public static void main(String[] args){
        try {
            Main.method("");
        } catch (RuntimeException e) {
            System.out.println(e.getMessage());
        }
    }
    public static void method(String str)throws RuntimeException{
        if(!"".equals(str)){
            System.out.println(str);
        }else {
            throw new RuntimeException("str不能为空");
        }
    }
}




五、自定义异常类


首先问一下为什么要自定义异常类勒?有几点原因,例如设置多个自己定义的异常类,仅仅捕获其自己所关心的异常类,并知道如何处理;根据自己的需求出现异常的时候来去做特殊的处理;异常分类,业务中不同场景抛出不同异常,便于统一捕获并根据类型做进一步处理


对于自定义异常类有几点规范如下:


继承于现有的异常类如:Exception、RuntimeException…

自定义异常类需要提供一个全局常量如:serialVersionUID ,用来标识自己定义的异常类

必须提供重载的构造器

自定义异常:包含最基本的几个部分


class MyException extends RuntimeException{
    //需要一个UID来表示自己的自定义异常类
    static final long serialVersionUID = -7034897190745766959L;
    public MyException(){
    }
    //想要有自定义异常描述,就需要有一个有参构造
    public MyException(String msg){
        super(msg);
    }
}


//测试上面的自定义异常
public class Main {
    public static void main(String[] args){
        try {
            Main.method("");
        } catch (MyException e) {
            System.out.println(e.getMessage());
        }
    }
  //测试使用
    public static void method(String str)throws MyException{
        if(!"".equals(str)){
            System.out.println(str);
        }else {
            throw new MyException("自定义异常描述:str不准为空");
        }
    }
}




参考资料


[1]. Java受检异常和非受检异常


[2]. Java基础之《受检查异常和不受检查异常》


[3]. Java 中的异常和处理详解


[4]. Java中关键字throw和throws的区别


[5]. Java:为什么要有自定义异常?


[6]. Java自定义异常(优雅的处理异常) 实际应用配合枚举


[7]. java try(){}catch(){}自动资源释放

相关文章
|
18天前
|
Java 开发者
Java中的异常处理:从基础到高级
在Java编程的世界里,异常处理是一块基石,它确保了程序的健壮性和稳定性。本文将带你从异常的基础概念出发,逐步深入到高级处理技巧,通过实例展示如何在Java中有效管理和处理异常。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的见解和技巧。
|
17天前
|
Java 程序员
Java编程中的异常处理:从基础到高级
在Java的世界中,异常处理是代码健壮性的守护神。本文将带你从异常的基本概念出发,逐步深入到高级用法,探索如何优雅地处理程序中的错误和异常情况。通过实际案例,我们将一起学习如何编写更可靠、更易于维护的Java代码。准备好了吗?让我们一起踏上这段旅程,解锁Java异常处理的秘密!
|
15天前
|
Java
Java 异常处理:11 个异常处理最佳实践
本文深入探讨了Java异常处理的最佳实践,包括早抛出晚捕获、只捕获可处理异常、不忽略异常、抛出具体异常、正确包装异常、记录或抛出异常但不同时执行、不在finally中抛出异常、避免用异常控制流程、使用模板方法减少重复代码、抛出与方法相关的异常及异常处理后清理资源等内容,旨在提升代码质量和可维护性。
|
17天前
|
安全 Java 数据库连接
Java中的异常处理:理解与实践
在Java的世界里,异常处理是维护代码健壮性的守门人。本文将带你深入理解Java的异常机制,通过直观的例子展示如何优雅地处理错误和异常。我们将从基本的try-catch结构出发,探索更复杂的finally块、自定义异常类以及throw关键字的使用。文章旨在通过深入浅出的方式,帮助你构建一个更加稳定和可靠的应用程序。
29 5
|
16天前
|
Java 程序员
深入理解Java异常处理机制
Java的异常处理是编程中的一块基石,它不仅保障了代码的健壮性,还提升了程序的可读性和可维护性。本文将深入浅出地探讨Java异常处理的核心概念、分类、处理策略以及最佳实践,旨在帮助读者建立正确的异常处理观念,提升编程效率和质量。
|
17天前
|
Java 开发者 UED
深入探索Java中的异常处理机制##
本文将带你深入了解Java语言中的异常处理机制,包括异常的分类、异常的捕获与处理、自定义异常的创建以及最佳实践。通过具体实例和代码演示,帮助你更好地理解和运用Java中的异常处理,提高程序的健壮性和可维护性。 ##
42 2
|
17天前
|
Java 开发者
Java中的异常处理机制深度剖析####
本文深入探讨了Java语言中异常处理的重要性、核心机制及其在实际编程中的应用策略,旨在帮助开发者更有效地编写健壮的代码。通过实例分析,揭示了try-catch-finally结构的最佳实践,以及如何利用自定义异常提升程序的可读性和维护性。此外,还简要介绍了Java 7引入的多异常捕获特性,为读者提供了一个全面而实用的异常处理指南。 ####
39 2
|
17天前
|
Java 开发者
Java 中的异常处理:不仅仅是 try-catch
在Java的世界里,异常处理是代码的守护神,它保护着程序不会因为意外错误而崩溃。但异常处理远不止try-catch那么简单。本文将深入探讨Java的异常处理机制,从基本的try-catch到更复杂的自定义异常和finally块的使用,带你理解如何在Java中优雅地处理错误。
47 1
|
20天前
|
Java 程序员 UED
深入理解Java中的异常处理机制
本文旨在揭示Java异常处理的奥秘,从基础概念到高级应用,逐步引导读者掌握如何优雅地管理程序中的错误。我们将探讨异常类型、捕获流程,以及如何在代码中有效利用try-catch语句。通过实例分析,我们将展示异常处理在提升代码质量方面的关键作用。
31 3
|
20天前
|
Java 数据库连接 开发者
Java中的异常处理机制:深入解析与最佳实践####
本文旨在为Java开发者提供一份关于异常处理机制的全面指南,从基础概念到高级技巧,涵盖try-catch结构、自定义异常、异常链分析以及最佳实践策略。不同于传统的摘要概述,本文将以一个实际项目案例为线索,逐步揭示如何高效地管理运行时错误,提升代码的健壮性和可维护性。通过对比常见误区与优化方案,读者将获得编写更加健壮Java应用程序的实用知识。 --- ####
下一篇
DataWorks