Java学习 -- 异常

Posted 庸人冲

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java学习 -- 异常相关的知识,希望对你有一定的参考价值。

异常体系结构

在Java语言中, 将程序执行中发生的不正常情况称为异常 。(开发过程中的语法错误和逻辑错误不是异常)

异常在Java中被定义为类,当程序发生异常时,会抛出对应异常类的对象,该对象承载了异常信息(万事万物皆对象),异常相关的类被称作异常类

异常的顶级父类是java.lang.Throwable,它有两个子类:

1️⃣ java.lang.Error: Java虚拟机无法解决的严重问题。 如: JVM系统内部错误、 资源耗尽等严重情况。

例如:StackOverflowErrorOutOfMemoryError。 一般不编写针对性的代码进行处理。

2️⃣ java.lang.Exception: 其它因为编程错误或偶然的外在因素导致的一般性问题, 可以使用针对性的代码进行处理。 例如:

  • 空指针访问

  • 试图读取不存在的文件

  • 网络连接中断

  • 数组索引越界

    等等…

我们一般所指的异常都是针对于Exception,其又可以分成两类:

  1. 编译时异常(checked),编译时可能发生的异常。

  2. 运行时异常(unchecked/RuntimeException),编译时无异常,运行时发生的异常。

// 异常类树状图
|--- Throwable
		|--- Error
    			|--- ...
		|--- Exception
    			|--- IOException (checked)   
    	  				|--- FileNotFoundException
    					|--- ...
    			|--- ClassNotFoundException (checked)
    					|--- ...
    			|--- RuntimeException (unchecked)  
    					|--- NullPointerException
    					|--- IndexOutOfBoundsException
    					|--- ClassCastException
    					|--- NumberFormatException
    					|--- InputMismatchExcepton
    					|--- ArithmeticException
    					|--- ...

常见异常举例

运行时异常

  1. NullPointerException
int[] arr = null;
System.out.println(arr[0]); // 空指针异常 

String str = null;
System.out.println(str.charAt(0)); // 空指针异常 
  1. IndexOutOfBoundsException
int[] arr = new int[10];
System.out.println(arr[10]); 	  	// 数组角标越界 ArrayIndexOutOfBoundsException

String str = "abc";
System.out.println(str.charAt(-1)); // 字符串角标越界 StringIndexOutOfBoundsException
  1. ClassCastException
Father sonA = new SonA();
SonB sonB = (Sonb)sonA;  	// 类型转换异常 

Object obj = new Date();
String str = (String)obj; 	// 类型转换异常 
  1. NumberFormatException
public void test2(){
    String str =  "abc";
    Integer.parseInt(str);  // 数字格式异常 
}
  1. InputMismatchExcepton
Scanner scan = new Scanner(System.in);
int num = Scanner.nextInt();    // 输入非数字时,则会出现输入不匹配异常 
  1. ArithmeticException
int a = 10;
int b = 0;   // 不能除0
System.out.println(a / b);  // 算数异常

异常处理机制

在编写程序时,经常要在可能出现错误的地方加上检测的代码,如: 进行x/y运算时,要检测分母为0,数据为空,输入的不是数据而是字符等。 过多的if-else分支会导致程序的代码加长、臃肿,可读性差。因此采用异常处理机制

抓抛模型

过程

程序在正常执行的过程中,一旦出现异常,就会在异常代码处出生成一个对应异常类的对象,并将此对象抛出

一旦抛出对象以后,其后的代码就不再执行

异常对象的产生方式:

  1. 系统自动生成的异常对象。
  2. 手动生成的异常对象,并使用thorw抛出。

过程

可以理解为异常处理的方式分为:try-catch-finallythrows

try-catch-finally

语法格式

try {
  // 可能出现异常的代码
} catch(异常类型1 变量名1) {  // catch可以定义多个
  // 处理异常的方式1
} catch(异常类型2 变量名2) {
  // 处理异常的方式2
} catch(异常类型3 变量名3) {
  // 处理异常的方式3
}
...
finally {
    // 一定会执行的代码
}
// finally 是一个可选的部分,类似Switch语句中的default

不使用finally的情况

1.利用try将可能出现异常的代码包装以来,在执行过程中,一旦出现异常,就会生成一个对于异常类的对象,根据此对象的类型,去catch中进行匹配。

@Test
public void test1(){
    String str = "abc";
    try{
        int num = Integer.parseInt(str);
        System.out.println("test1");
    } catch (NumberFormatException e){
        System.out.println("出现了数值转换异常");
    }
    System.out.println("test2");
}

2.一旦try中的异常对象匹配到某一个catch时,就进入catch中进行异常的处理,一旦处理完成,就跳出当前的try-cathc结构(没有写finally的情况),继续执行其后的代码。

3.当抛出的异常对象没有在catch匹配到异常类型时,则以上会向上抛出,直到main()方法。

@Test
public void test1(){
    String str = "abc";
    try{
        int num = Integer.parseInt(str);
        System.out.println("test1");
    } catch (NullPointerException e){
        System.out.println("出现空指针异常");
    } 
    System.out.println("test2");
}


4.catch中的异常类型如果存在子父类关系,则要求子类一定声明在父类的上面

  1. try结构中声明的变量,出了try{}的作用域不能使用。

  2. 不建议使用Exeption 去捕获所有的异常,范围太广,不容易定位错误。

catch 中常使用的两个方法

getMessage() : 返回值是String

printStackTrace(): 返回值类型是void

使用finally的情况

finally的代码块中的代码一定会被执行,即使**catch中也出现了异常**,或者**try-cathc中使用了return语句**等情况,finally中的代码也一定会被执行。

例如:

  1. catch中编写空指针异常的代码。

  1. try-catch中使用了return语句

  2. 没有异常时,try中的return语句也会在finally之后执行。

  3. 如果finally中也存在return语句,则会执行finallyreturn语句。

finally的使用场景

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

@Test
public void test(){
    FileInputStream fis = null;
    try{
        File file = new File("hello.txt");
        fis = new FileInputStream(file);

        int data = fis.read();
        while(data != -1) {
            System.out.println((char) data);
            data = fis.read();
        }
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {  // 如果finally中的代码也会导致异常, 可以在finally中嵌套 try-catch
        try {
            if(fis != null)
            {
				fis.close();
            }    
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

总结

  • 使用try-catch-finally处理编译时异常,使得程序在编译时不再报错,但是运行时仍可能报错。相当于将一个编译时可能出现的异常,延迟到运行时出现

  • 开发中,由于运行时异常比较常见,所以通常不针对运行时异常编写try-catch-finally

  • 针对于编译时异常一定要考虑异常的处理

throws

throws + 异常类型 写在方法的声明处,指明此方法执行时,可能会抛出的异常类型。

权限修饰符 [关键字] 返回值类型 方法名 throws 异常类型1, 异常类型2... () {
    // 方法体
}

一旦当方法体执行时,出现异常,仍会在异常代码处生成一个异常类的对象。此对象满足throws后的异常类型时,就会被抛出。异常代码后的代码不会被执行。

异常会抛给方法的调用者,如果调用者仍不能解决异常则可以继续向上抛出,直到main方法。

重写方法异常抛出的规则

子类重写方法所抛出的异常类型不大于父类被重写方法抛出的异常类型,子类也可以不抛出异常。

两种异常处理的选择

  1. 如果父类中被重写方法没有throws则子类重写的方法也不能使用thorws,这意味着如果子类重写的方法中有异常,必须使用try-catch-finally方式进行处理。

  2. 执行的方法A中,先后又调用了另外的几个方法,这几个方法是递进关系执行的,并且都有可能出现异常。那么建议这几个方法使用throws的方式进行处理。把异常语句写在方法A中,然后方法A可以考虑使用try-catch-finally方式进行统一处理。

  3. 如果方法中有必须关闭的资源,如数据库连接,数据流,socket网络资源等,则必须在该方法内部处理掉异常,如果此时仍然向上抛出,可能会导致方法中的资源未关闭。

thorw 手动抛出异常

public class ThrowTest {

    public static void main(String[] args) {
        Student stu = new Student();
        // 在异常方法调用处解决异常
        try {
            stu.register(-1);
        } catch (Exception e) {
            System.out.println(e.getMessage());; // 此时输出的就是Exception构造器中输入的字符串
        }
    }
}

class Student {
    private int id;

    public  void register(int id)  throws Exception  {
        if(id > 0) {
            this.id = id;
        } else {
//            System.out.println("您输入的数据非法!");
            // 手动抛出异常对象
            // 通常是new Exception 或者 RuntimeException 的对象
            // RuntimeException会在运行时抛出异常
//            throw new RuntimeException("您输入的数据非法");

            // Exception 需要在编译之前解决,此时可以在本代码块中解决,或者通过throws 向上抛给调用者
            throw new Exception("您输入的数据非法");
        }
    }
}

用户自定义异常类

如何自定义异常类?

  1. 自定义的类继承于现有的异常结构:通常继承于RuntimeException (unchecked)Exception (checked)
  2. 提供全局常量:serialVersionUID,可以理解为对异常类的唯一标识。
  3. 提供重载的构造器(空参和String msg)
// 继承于运行时异常或者编译时异常时的处理方式与处理类库中的异常相同。
public class MyException extends RuntimeException {

    static final long serialVersionUID = -731074574532446639L;

    public MyException() {
        super();
    }

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

模拟登录时抛出异常:

// 自定义异常类
// 父类 继承于Exception
public class LoginCheckException extends Exception{

    static final long serialVersionUID = -3386666993124229948L;

    public LoginCheckException(){
        super();
    }

    public LoginCheckException(String message){
        super(message);
    }
}
// 子类
public class UsernameInvalidException extends LoginCheckException{
    static final long serialVersionUID = -3386666998884229948L;

    public UsernameInvalidException(){
        super();
    }

    public UsernameInvalidException(String message){
        super(message);
    }
}
public class PasswordInvalidException extends LoginCheckException{
    static final long serialVersionUID = -3386666998884229948L;

    public PasswordInvalidException(){
        super();
    }

    public PasswordInvalidException(String message){
        super(message);
    }
}
// 实体类
public class UserInfo {

    private final String username;
    private final String password;

    public UserInfo(String username, String password) {
        this.username = username;
        this.password = password;
    }

    public String getUsername() {
        return username;
    }

    public String getPassword() {
        return password;
    }
}
// 测试类
public class ExceptionTest {

    public static final Scanner SCANNER = new Scanner(System.in);
    
    // 注册
    public static UserInfo register() {
        System.out.println("*********注册*********");
        System.out.print("请输入用户名:");
        String username = SCANNER.next();
        System.out.print("请输入密码:");
        String password = SCANNER.next();
        return new UserInfo(username, password);
    }

    // 登录
    public static void login(UserInfo userInfo) {
        System.out.println("*********登录*********");
        try {
            System.out.print("请输入用户名:");
            String username = SCANNER.next();
            if (!userInfo.getUsername().equals(username)) throw new UsernameInvalidException("用户名不正确");
            System.out.print("请输入密码:");
            String password = SCANNER.next();
            if (!userInfo.getPassword().equals(password)) throw new LoginCheckException("密码不正确");
            System.out.println("登录成功");
        } catch (LoginCheckException e) {
            System.out.println(e.getMessage());
        }
    }

    public static void main(String[] args) {
        login(register());
    }
}

以上是关于Java学习 -- 异常的主要内容,如果未能解决你的问题,请参考以下文章

java SpringRetry学习的代码片段

片段中的 EditText 上的空指针异常 [重复]

Java异常处理机制

片段 getArguments() 空指针异常

Android Java:在 onCreateView() 中返回空视图的片段

java.util.MissingResourceException: Can't find bundle for base name init, locale zh_CN问题的处理(代码片段