Java19:异常

Java19:异常,第1张

Java19:异常

在计算机程序运行的过程中,总是会出现各种各样的错误。这些错误可能是用户造成的,可能是程序错误造成的,也可能是不可避免的随机错误。
所以,一个健壮的程序必须处理各种各样的错误。

很多事件并非总是按照人们自己设计意愿顺利发展的,经常出现这样那样的异常情况。例如: 你计划周末郊游,计划从家里出发→到达目的→游泳→烧烤→回家。但天有不测风云,当你准备烧烤时候突然天降大雨,只能终止郊游提前回家。“天降大雨”是一种异常情况,你的计划应该考虑到这样的情况,并且应该有处理这种异常的预案。

文章目录

Throwable:Error、Exception异常的处理

try--catch多异常捕捉finally异常的抛出throwthrows 自定义异常

Throwable:Error、Exception

Java内置了一套异常处理机制,将每种异常封装到一个类中(即每一种异常都对应着一个class),出现错误时就会拋出异常,及时有效地进行处理。

在 Java 中所有异常类都是内置类 java.lang.Throwable 类的子类,即 Throwable 位于异常类层次结构的顶层(Throwable继承于Object)。

除了异常(Exception)之外,Throwable中另一个分支为错误(Error)
(即:Throwable 类是所有异常和错误的超类,下面有 Error 和 Exception 两个子类分别表示错误和异常。):

Exception :用户程序可能出现的异常情况,它也是用来创建自定义异常类型类的类。
是程序正常运行过程中可以预料到的意外情况,并且应该被开发者捕获,进行相应的处理。Error :由 JVM 所侦测到的无法预期的错误,由于这是属于 JVM 层次的严重错误(如堆栈溢出) ,导致 JVM 无法继续执行,因此,这是不可捕捉到的,不是程序可以控制的,无法采取任何恢复的 *** 作,肯定会导致程序非正常终止,顶多只能显示错误信息。
常见Error类型:

OutOfMemoryError内存耗尽NoClassDefFoundError无法加载某个ClassStackOverflowError栈溢出…

Error是无需捕获的严重错误,Exception是应该捕获的可处理的错误。


Exception又可分为2类:

运行时异常(不检查):RuntimeException 类及其子类.
对于这些异常,程序中可以选择捕获处理,也可以不处理,具体根据需要来判断是否需要捕获,不会在编译器强制要求。这些异常一般由程序逻辑错误引起,可以在代码编写层面进行解决。

Java之所以会设计运行时异常的原因之一,是因为下标越界,空指针这些运行时异常太过于普遍,如果都需要进行捕捉,代码的可读性就会变得很糟糕。

非运行时异常(检查):RuntimeException及子类 以外的异常(CheckedException).
对于这种异常,编译器强制要求在代码里必须显式地进行捕获处理,这里是编译期检查的一部分,如果不处理,程序就不能编译通过。

C语言中不存在这样的异常机制,是否进行异常处理全看程序员自觉。而Java不行,在可能出现非运行异常的程序中,编译器要求必须有代码来负责异常处理,如此提高了Java代码的健壮性。

例1:在打开一个文件时,FileNotFoundException是一种检查性异常,因此在程序中必须进行异常处理,不写的话,编译器会报错。

例2:

class test {
    static void f(int i) {  
        f(i);
    }
    public static void main(String[] args) {
        f(5);
    }
}

无限递归造成栈溢出,出现异常:

异常的处理

程序运行某个函数的时候,如果失败了,就表示存在异常。此时这个函数会产生代表该异常的一个对象,并把它交给运行时的系统(抛出throw异常),运行时系统在函数的调用栈中查找,直到找到能够处理该类型异常的对象(捕获catch异常)。

try–catch

将想要执行的语句(可能抛出异常的代码)放在try中,在catch中放针对异常的处理语句。

如果try后面的语句执行成功,就不会执行catch块中的代码。try中定义的变量,在catch中不能使用的。若try后面的语句执行失败,try里的代码会立即终止,程序流程会运行到对应的catch块中。异常会被抛到该catch块,这和传递一个参数到函数是一样的。try...catch 与 if...else 不一样,try 后面的花括号{ }不可以省略,即使 try 块里只有一行代码,也不可省略这个花括号。与之类似的是,catch 块后的花括号{ }也不可以省略。

File f = new File("/Users/shaoyihao/Desktop/test.txt");
try {
    new FileInputStream(f);
    System.out.println("打开成功!");
} catch (FileNotFoundException e) {  //如果是发生了FileNotFoundException异常,就执行这后面的语句
	e.printStackTrace(); //打印出方法的调用痕迹,便于定位和分析到底哪里出了异常
    System.out.println("文件打开失败!");
}

printStackTrace()的作用:


参考文章(printStackTrace)
除此之外,还可以使用以下 2个方法输出相应的异常信息:

getMessage() :输出错误的性质toString() :给出异常的类型与性质

使用异常的父类Exception、Throwable也可以进行catch,只不过如此便太宽泛了:

try {
    new FileInputStream(f);
	System.out.println("打开成功!");
} catch (Throwable e) {
	System.out.println("文件打开失败!");
}
多异常捕捉

当可能同时出现多种异常时:

方法1:进行分支判断:
如果try后面的代码中发生异常,异常被抛给第一个 catch 块。如果抛出异常的数据类型与 ExceptionType1 匹配,它在这里就会被捕获。如果不匹配,它会被传递给第二个 catch 块。如此,直到异常被捕获或者通过所有的 catch 块。
匹配到某个catch后,执行catch代码块,然后不再继续匹配。即:多个catch语句只有一个能被执行。
因此,存在多个catch的时候,catch的顺序非常重要:子类必须写在前面。

try {
    System.out.println("试图打开 /Users/shaoyihao/Desktop/test.txt");
    new FileInputStream(f);
    System.out.println("成功打开");
    
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    Date d = sdf.parse("2022-01-22");
} catch (FileNotFoundException e) {
    System.out.println("文件不存在");
    e.printStackTrace();
} catch (ParseException e) {
    System.out.println("日期格式解析错误");
    e.printStackTrace();
}

方法2:把多个异常,放在一个catch里统一捕捉:
这种方式从 JDK7开始支持,优点是捕捉的代码更紧凑;
缺点是一旦发生异常,不能确定到底是哪种异常,需要通过instanceof进行判断具体的异常类型.

File f = new File("/Users/shaoyihao/Desktop/test.txt");
try {
    new FileInputStream(f);
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    Date d = sdf.parse("2016-06-03");
} catch (FileNotFoundException | ParseException e) {
    e.printStackTrace();
    if (e instanceof FileNotFoundException) 
    	System.out.println("文件不存在");
    if (e instanceof ParseException) 
    	System.out.println("日期格式解析错误");

	 //e = new FileNotFoundException(); 错误!!!
	//此时,异常变量有隐式的 final 修饰,因此程序不能对异常变量重新赋值。
}
finally

finally语句用于在任何情况下(无论是否发生异常)都必须执行的代码,总是最后执行。
通常,程序在 try 块里会打开了一些物理资源(如数据库连接、网络连接和磁盘文件等),这些物理资源都必须显式回收。

如果try或者catch中有return语句,在执行return之前,先执行finally中的代码.

try {
   	...
} catch () {
    ...
}
finally {
    System.out.println("无论是否异常,都要执行!");
}

某些情况下,可以没有catch,只有try ... finally结构:

void process(String file) throws IOException {
    try {
        ...
    } finally {
        System.out.println("END");
    }
}

因为方法声明了可能抛出的异常,所以可以不写catch。


异常的抛出throw

当某个方法出现异常时,如果当前方法没有捕获异常,异常就会默认被抛到上层调用方法,直到遇到某个try … catch被捕获或者到JVM处为止。

在代码中可以用throw抛出异常。注意:抛出的对象必须是 Throwable 类或其子类的对象。

抛出异常分两步:

    创建某个Exception的实例用throw语句抛出

当 throw 语句执行时,它后面的语句将不执行,此时程序转向上层(调用者)程序,寻找与之相匹配的 catch 语句,执行相应的异常处理程序。
如果没有找到相匹配的catch 语句,则再转向上一层的调用程序。
这样逐层向上,直到最外层的异常处理程序终止程序并打印出调用栈情况。

void f(String s) {
    if (s == null) {
    	throw new NullPointerException(); //括号中可以传入参数或写异常信息
   //即:NullPointerException e = new NullPointerException();
     // throw e;
    }
}
void getArr(int[] arr,int index) {
    if(arr == null) { // 如果数组为null,抛出空指针异常
        throw new NullPointerException("传入的数组不能为空"); //throw执行后,函数不能继续执行
    }
    if(index >= arr.length) { //若索引超出范围,抛出异常
        throw new ArrayIndexOutOfBoundsException(index);
    }
    
    int a = arr[index]; //确保无误后,才能安全地根据下标取元素
    System.out.println(a);
}

如果一个方法捕获了某个异常后,又在catch子句中抛出新的异常,就可以实现把抛出的异常类型“转换” 了:

public static void main(String[] args) {
    try {
        process1();
    } catch (Exception e) {
        e.printStackTrace();  //打印不出完整的实际异常栈
    }
}

void process1(String s) {
    try {
        process2();
    } catch (NullPointerException e) { //捕获了NullPointerException异常
        throw new IllegalArgumentException();  //抛出IllegalArgumentException异常
    }
}

void process2(String s) {
    if (s == null) {
        throw new NullPointerException();
    }
}

如果按照这种写法,新的异常就会丢失原始异常信息,将无法看到原始的异常信息了。

为了能追踪到完整的异常栈,在构造异常的时候,把原始的Exception实例传进去,新的Exception就可以持有原始Exception信息:

static void process1() {
    try {
        process2();
    } catch (NullPointerException e) {
        throw new IllegalArgumentException(e); //传入原始异常的实例
    }
}


注意到Caused by: Xxx,说明捕获的IllegalArgumentException并不是造成问题的根源,根源在于NullPointerException,是在Main.process2()方法抛出的。

捕获到异常并再次抛出时,一定要留住原始异常,否则很难定位第一案发现场!
在代码中获取原始异常可以使用Throwable.getCause()方法。如果返回null,说明已经是“根异常”了。


在catch中抛出异常,不会影响finally的执行。 JVM会先执行finally,然后抛出异常。
如果在执行finally语句时又抛出异常,则原来在catch中准备抛出的异常就“消失”了,因为只能抛出一个异常。没有被抛出的异常称为“被屏蔽”的异常(Suppressed Exception)。

在极少数的情况下,我们需要获知所有的异常。如何保存所有的异常信息?
方法是先用origin变量保存原始异常,然后调用Throwable.addSuppressed(),把原始异常添加进来,最后在finally抛出:

public static void main(String[] args) throws Exception {
    Exception origin = null;
    try {
        System.out.println(Integer.parseInt("abc"));
    } catch (Exception e) {
        origin = e;
        throw e;
    } 
    finally {
        Exception e = new IllegalArgumentException();
        if (origin != null) {
            e.addSuppressed(origin); //将异常信息全部加到orgin中
        }
        throw e; //抛出
    }
}

此时虽然catch的异常被屏蔽了,但是,finally抛出的异常仍然包含了它。

通常情况下不在 finally 代码块中使用 return 或 throw 等导致方法终止的语句,否则将会导致 try 和 catch 代码块中的 return 和 throw 语句失效。


throws

throws语句用于声明拋出可能出现的异常,是在警示上层函数对这种异常进行考虑,即:可以理解为throws提出了一个预警。

使用 throws 声明抛出异常的思路是:

当前方法不知道如何处理这种类型的异常,该异常应该由向上一级的调用者处理如果 main 方法也不知道如何处理这种类型的异常,也可以使用 throws 声明抛出异常,该异常将交给 JVM 处理JVM 对异常的处理方法是,打印异常的跟踪栈信息,并中止程序运行,这就是前面程序在遇到异常后自动结束的原因

注意:

throws后面的异常必须是Exception或者是Exception的子类可以抛出多个异常,异常之间用,分隔如果多个异常之间具有子父类的继承关系,可以直接声明一个父类的异常即可如果声明的异常是运行时异常(非检查性),那么上层函数不进行处理也行一般来说,只要是throws声明的检查性异常,不在调用层捕获,也必须在更高的调用层捕获。所有未捕获的异常,最终也必须在main()方法中捕获。main()方法也是最后捕获Exception的机会(否则就main方法中仍throws到JVM中处理)。一般,如果在方法中抛出了一个异常对象,就在方法上声明这个异常的抛出

例如:在嵌套调用的程序 M a i n → F → f Mainrightarrow Frightarrow f Main→F→f 中,当函数 f f f 中可能存在异常时,在 f f f 中不进行异常处理,而是将异常抛出到 F F F 中。 F F F 对于该异常同样有两种处理方式:抛出到 M a i n Main Main 或进行处理(消化)。若 F F F 中进行了异常消化,则 M a i n Main Main 函数中就不用进行异常处理了。

static void f() throws FileNotFoundException {  //若发生这种异常,就将这种异常抛出                         
    File ff = new File("/Users/shaoyihao/Desktop/tes.txt");                       
    new FileInputStream(ff);                                                      
}                                                                                 
static void F() {                                                                 
    try {     //对f()中抛出的异常进行处理                                                    
        f();                                                                      
    } catch (FileNotFoundException e) {                                           
        e.printStackTrace();                                                      
    }                                                                             
}                                                                                 
public static void main(String[] args) {                                          
    F(); //调用F()就不用进行异常处理了                                                        
}                                                                                 

throw与throws的区别:

throw是已经出现了异常,将其抛出;throws则是可能出现异常,作出警示。throws 通常不用显示地捕获异常,可由系统自动将所有捕获的异常信息抛给上级方法; throw 则需要用户自己捕获相关的异常,而后再对其进行相关包装,最后将包装后的异常信息抛出。

方法重写时声明抛出异常的限制
方法重写中规定:子类方法声明抛出的异常类型应该是父类方法声明抛出的异常类型的子类或相同;子类方法声明抛出的异常不允许比父类方法声明抛出的异常多。

自定义异常

自定义新的异常类型要注意:保持一个合理的异常继承体系。

常见做法:自定义一个baseException作为“根异常”,然后由此派生出各种业务类型的异常。
自定义的baseException应该提供多个构造方法。

public class baseException extends RuntimeException {
    public baseException() {
        super();
    }

    public baseException(String message, Throwable cause) {
        super(message, cause);
    }

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

    public baseException(Throwable cause) {
        super(cause);
    }
}

//派生出其它异常
public class UserNotFoundException extends baseException {
}

public class LoginFailedException extends baseException {
}
...
import java.util.Scanner;

class LoginException extends Exception {
    public LoginException() {
        super();
    }

    public LoginException(String msg) {
        super(msg);
    }
}

class test {
    public static boolean validateLogin(String username, String pwd) {
        boolean conName = true;    // 用户名格式是否正确
        boolean conPwd = true;    // 密码是否正确
        try {
            if (username.length() >= 6 && username.length() <= 10) {
                for (int i = 0; i < username.length(); i++) {
                    char ch = username.charAt(i);
                    if (!(ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z')) { // 如果字符不是0~9的数字,则拋出LoginException异常
                        conName = false;
                        throw new LoginException("用户名中包含有非字母的字符!");
                    }
                }
            } else { // 如果用户名长度不在6~10位之间,拋出异常
                conName = false;
                throw new LoginException("用户名长度必须在6〜10位之间!");
            }

            if (pwd.length() != 6) {    // 如果密码长度不等于6,拋出异常
                conPwd = false;
                throw new LoginException("密码长度必须为 6 位!");
            }
        } catch (LoginException e) { // 捕获 LoginException 异常
            System.out.println(e.getMessage());
        }

        if (conPwd && conName) return true;
        else return false;
    }

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        System.out.println("请输入用户名:");
        String username = input.next();
        System.out.println("请输入密码:");
        String password = input.next();

        boolean con = validateLogin(username, password);
        if (con) {
            System.out.println("登录成功!");
        }
    }
}

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5712231.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存