Day09
1.异常
1.1 介绍
在使用计算机语言进行项目开发的过程中,即使程序员把代码写得尽善尽美,在系统的运行过程中仍然会遇到一些问题,因为很多问题不是靠代码能够避免的,比如:客户输入数据的格式问题,读取文件是否存在,网络是否始终保持通畅等等。
异常 :指的是程序在执行过程中,出现的非正常情况,如果不处理最终会导致 JVM 的非正常停止。
1.2 异常的抛出机制
Java 中把不同的异常用不同的类表示,一旦发生某种异常,就创建该异常类型的对象,并且抛出(throw)。然后程序员可以捕获(catch)到这个异常对象,并处理;如果没有捕获(catch)这个异常对象,那么这个异常对象将会导致程序终止。
1.3 Throwable类
Throwable类是Java程序执行过程中发生的异常事件对应的类的根父类。Throwable 中的常用方法:
- public void printStackTrace():打印异常的详细信息。包含了异常的类型、异常的原因、异常出现的位置、在开发和调试阶段都得使用printStackTrace。
- public String getMessage():获取发生异常的原因。
1.3.1 Error 和 Exception
Throwable 可分为两类:Error 和 Exception。分别对应着 java.lang.Error 与java.lang.Exception 两个类。
- Error:Java 虚拟机无法解决的严重问题。如:JVM 系统内部错误、资源耗尽等严重情况。一般不编写针对性的代码进行处理。例如:StackOverflowError(栈内存溢出)和 OutOfMemoryError(堆内存溢出,简称OOM)。
- Exception: 其它因编程错误或偶然的外在因素导致的一般性问题,需要使用针对性的代码进行处理,使程序继续运行。否则一旦发生异常,程序也会挂掉。例如:
- 空指针访问
- 试图读取不存在的文件
- 网络连接中断
- 数组角标越界
1.3.2 编译时异常和运行时异常
- 编译时期异常(即 checked 异常、受检异常):在代码编译阶段,编译器就能明确警示当前代码可能发生(不是一定发生)xx 异常,并明确督促程序员提前编写处理它的代码。如果程序员没有编写对应的异常处理代码,则编译器就会直接判定编译失败,从而不能生成字节码文件。通常,这类异常的发生不是由程序员的代码引起的,或者不是靠加简单判断就可以避免的,例如FileNotFoundException(文件找不到异常)。
- 运行时期异常(即 runtime 异常、unchecked 异常、非受检异常):在代码编译阶段,编译器完全不做任何检查,无论该异常是否会发生,编译器都不给出任何提示。只有等代码运行起来并确实发生了 xx 异常,它才能被发现。通常,这类异常是由程序员的代码编写不当引起的,只要稍加判断,或者细心检查就可以避免。
- java.lang.RuntimeException:类及它的子类都是运行时异常。比如:ArrayIndexOutOfBoundsException:数组下标越界异常,ClassCastException类型转换异常。
1.3.3 常见的运行时异常
- NullPointerException
- ClassCastException
- ArrayIndexOutOfBoundsException
- NumberFormatException
- InputMismatchException
- ArithmeticException
//NullPointerException
int[][] arr = new int[3][];
System.out.println(arr[0].length);
}
@Test
public void test02(){
//ClassCastException
Object obj = 15;
String str = (String) obj;
}
@Test
public void test03(){
//ArrayIndexOutOfBoundsException
int[] arr = new int[5];
for (int i = 1; i <= 5; i++) {
System.out.println(arr[i]);
}
}
@Test
public void test04(){
//InputMismatchException
Scanner input = new Scanner(System.in);
System.out.print("请输入一个整数:");//输入非整数
int num = input.nextInt();
input.close();
}
@Test
public void test05(){
int a = 1;
int b = 0;
//ArithmeticException
System.out.println(a/b);
}
}
1.3.4 常见的编译时异常
- InterruptedException
- ClassNotFoundException
- SQLException
@Test
public void test06() {
Thread.sleep(1000);//休眠 1 秒 InterruptedException
}
@Test
public void test07(){
Class c = Class.forName("java.lang.String");//ClassNotFoundException
}
@Test
public void test08() {
Connection conn = DriverManager.getConnection("...."); //SQL
Exception
}
@Test
public void test09() {
FileInputStream fis = new FileInputStream("尚硅谷 Java 秘籍.txt
"); //FileNotFoundException
}
@Test
public void test10() {
File file = new File("尚硅谷 Java 秘籍.txt");
FileInputStream fis = new FileInputStream(file);//FileNotFound
Exception
int b = fis.read();//IOException
while(b != -1){
System.out.print((char)b);
b = fis.read();//IOException
}
fis.close();//IOException
}
1.4 异常的处理
Java 采用的异常处理机制,是将异常处理的程序代码集中在一起,与正常的程序代码分开,使得程序简洁、优雅,并易于维护。
1.4.1 try-catch-finally
Java 提供了异常处理的抓抛模型。
- 前面提到,Java 程序的执行过程中如出现异常,会生成一个异常类对象,该异常对象将被提交给 Java 运行时系统,这个过程称为抛出(throw)异常。
- 如果一个方法内抛出异常,该异常对象会被抛给调用者方法中处理。如果异常没有在调用者方法中处理,它继续被抛给这个调用方法的上层方法。这个过程将一直继续下去,直到异常被处理。这一过程称为捕获(catch)异常。
- 如果一个异常回到 main()方法,并且 main()也不处理,则程序运行终止。
try{
...... //可能产生异常的代码
}
catch( 异常类型 1 e ){
...... //当产生异常类型 1 型异常时的处置措施
}
catch( 异常类型 2 e ){
...... //当产生异常类型 2 型异常时的处置措施
}
finally{
...... //无论是否发生异常,都无条件执行的语句
}
- 将可能出现异常的代码声明在try语句中。一旦代码发生异常,就会自动生成一个对应异常类的对象,并将此对象抛出。
- 针对于try中抛出的异常类的对象,使用之后的catch语句进行匹配。一旦匹配上,就进入catch语句块进行处理。当处理结束之后,代码就可以继续向下执行。
- 如果声明了多个catch结构,不同的异常类型在不存在子父类关系的情况下,谁声明在上面,谁声明在下面都可以。
- 如果多个异常类型满足子父类的关系,则必须将子类声明在父类结构的上面,否则会报错。
- catch中异常处理的方式:
- 编写异常处理的语句。
- printStackTrace():打印异常的详细信息。(推荐)
- getMessage():获取发生异常的原因。
- try中声明的变量,由于作用域限制,在try结构之外不可以进行调用。
- 将一定要被执行的代码声明在finally结构中。 唯一的例外是使用System.exit(0)来终止当前正在运行的Java虚拟机。
- finally语句和catch语句是可选的,但finally不能单独使用。
- 在开发中,有一些资源(比如:输入流、输出流、数据库连接、Socket连接等资源)在使用完以后。必须显式的进行关闭操作,否则,GC不会自动的回收这些资源。进而导致内存的泄露。为了保证这些资源在使用完以后,不管是否出现了未被处理的异常的情况下,这些资源能被关闭。我们必须将这些操作声明在finally中!
1.4.2 throws + 异常类型
- 在方法的声明处,使用"throws 异常类型1,异常类型2,…"。
public void test() throws 异常类型1,异常类型2,...{
//可能存在编译时异常
}
- 从编译是否能通过的角度看,看成是给出了异常万一要是出现时候的解决方案。此方案就是,继续向上抛出(throws)。但是,此throws的方式,仅是将可能出现的异常抛给了此方法的调用者。此调用者仍然需要考虑如何处理相关异常。从这个角度来看,throws的方式不算是真正意义上处理了异常。
- 方法重写的要求:子类重写的方法抛出的异常类型可以与父类被重写的方法抛出的异常类型相同,或是父类被重写的方法抛出的异常类型的子类。
1.4.3 开发中如何选择异常处理的方式
- 如果程序代码中,涉及到资源的调用(流、数据库连接、网络连接等),则必须考虑使用 try-catch-finally 来处理,保证不出现内存泄漏。
- 如果父类被重写的方法没有 throws 异常类型,则子类重写的方法中如果出现异常,只能考虑使用 try-catch-finally 进行处理,不能 throws。
- 开发中,方法 a 中依次调用了方法 b,c,d 等方法,方法 b,c,d 之间是递进关系。此时,如果方法 b,c,d 中有异常,我们通常选择使用 throws,而方法 a 中通常选择使用 try-catch-finally。
1.5 throw手动抛出异常对象
1.5.1 原因
在实际开发中,如果出现不满足具体场景的代码问题,我们就有必要手动抛出一个指定类型的异常对象。
1.5.2 介绍
- 程序在执行的过程当中,不满足指定条件的情况下,我们主动的使用"throw +异常类的对象"方式抛出异常对象。
- throw new 异常类名(参数);
- throw后的代码不能被执行,编译不通过。
1.6 自定义异常类
1.6.1 原因
Java 中不同的异常类,分别表示着某一种具体的异常情况。那么在开发中总是有些异常情况是核心类库中没有定义好的,此时我们需要根据自己业务的异常情况来定义异常类。例如年龄负数问题,考试成绩负数问题,某员工已在团队中等。
1.6.2 如何自定义异常类
- 继承于现有的异常体系,通常继承于Runtime Exception\Exception。
- 通常提供几个重载的构造器。
- 提供一个全局变量,声明为:static final long serialVersionUID。
1.6.3 使用自定义异常类
- 在具体的代码中,满足指定条件的情况下,需要手动的使用"throw +自定义异常类的对象"方式,将异常对象抛出。
- 如果自定义异常类是非运行时异常,则必须考虑如何处理此异常类的对象。(具体的:① try-catch-finally ② throws)