JavaEE - 07异常处理
(1)异常概述
- 异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的。
- 比如说,代码中少了一个分号,那么运行出来结果是提示是错误 java.lang.Error;
- 如果用System.out.println(11/0),那么用0做了除数,会抛出 java.lang.ArithmeticException 的异常。
- 异常发生的原因有很多,通常包含以下几大类:
- 用户输入了非法数据。
- 要打开的文件不存在。
- 网络通信时连接中断,或者JVM内存溢出。
- 这些异常有的是因为用户错误引起,有的是程序错误引起的,还有其它一些是因为物理错误引起的。
- 异常:在java语言中,将程序执行中发生的不正常情况称为"异常"。开发过程中的语法错误和逻辑错误不是异常。
- Java程序在执行过程中所发生的异常事件分为两类:
- Error:Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况。StackOverflowError和OOM。一般不编写针对性的代码进行处理。
- Exception: 其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。如:空指针访问、试图读取不存在的文件、网络连接中断、数组角标越界。
- 对于这些错误,一般有两种解决方法:
- 一是遇到错误就终止程序的运行。
- 另一种是在编写程序时,就考虑到错误的检测、错误消息的提示,以及错误的处理。
- 捕获错误最理想的是在编译期间,但是有些错误只有在运行时才会发生。如:除数为0、数组下标越界等。
- 分类: 编译时异常和运行时异常。
- 检查性异常:最具代表的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。
- 运行时异常: 运行时异常是可能被程序员避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略。
- 错误: 错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。例如,当栈溢出时,一个错误就发生了,它们在编译也检查不到的。
- Java 程序通常不捕获错误。错误一般发生在严重故障时,它们在Java程序处理的范畴之外。
public class ErrorTest { public static void main(String[] args) { // 栈溢出: Exception in thread "main" java.lang.StackOverflowError // main(args); // 堆溢出: Exception in thread "main" java.lang.OutOfMemoryError: Java heap space // Integer[] arr = new Integer[1024*1024*1024]; } }
(2)异常体系结构
- Exception 类是 Throwable 类的子类。除了Exception类外,Throwable还有一个子类Error 。
- Error 用来指示运行时环境发生的错误。例如,JVM 内存溢出。一般地,程序不会从错误中恢复。
- 所有的异常类是从 java.lang.Exception 类继承的子类。
- 异常类有两个主要的子类:IOException 类和 RuntimeException 类。
- java.lang.Throwable
- java.lang.Error: 一般不编写针对性的代码进行处理。
- java.lang.Exception: 可以进行异常的处理
- 编译时异常(checked)
- IOException
- EOFException
- FileNotFoundException
- MalformedURLException
- UnknownHostException
- ClassNotFoundException
- CloneNotSupportedException
- IOException
- 运行时异常(unchecked)
- RuntimeException
- NullPointerException
- ArrayIndexOutOfBoundsException
- ClassCastException
- NumberFormatException
- InputMismatchException
- ArithmaticException
- illegalArgumentException
- illegalStateException
- NoSuchElementException
(3)异常处理机制: try-catch-finally
- 异常处理机制:将可能异常的程序代码集中在一起,与正常的程序代码分开。
- 抛: 程序在正常执行的过程中,一旦出现异常,就会在异常代码处生成一个对应异常类的对象,并将此处对象抛出。一旦抛出对象,其后的代码将不再执行。
- 抓: 异常的处理方式 try-catch-finally
- try-catch-finally
- finally是可选的。
- 使用try将可能出现异常代码包装起来,在执行过程中,一旦出现异常,就会生成一个对应异常类的对象,根据此对象的类型,去catch中进行匹配。
- 一旦try中的异常对象匹配到某一个catch时,就进入catch中进行异常处理。一旦处理完成。就会跳出当前try-catch结构(在没有写finally的情况)。继续执行其后的代码。
- catch中的异常类型如果没有子父类关系,则声明顺序无所谓;如果有子父类关系,子类一定声明在父类上面,否则报错。
-
常用的异常对象处理的方式: String getMessage() / printStackTrace()
-
在try结构中声明的变量,在出了try结构以后,就不能再被调用。
- try-catch-finally 结构可以嵌套
- try-catch-finally处理编译时异常,使程序在编译时不再报错,但运行时仍可能报错。将一个编译时出现的异常,延迟到运行时出现。
- 由于运行时异常比较常见,通常就不针对运行时异常编写try-catch-finally了。
- 像数据库连接、输入输出流、网络编程Socket等资源,JVM不会自动回收,需要手动释放资源,就需要声明在finally中。
- try{
- // 可能出现异常的代码
- }catch(异常类型1 变量名1){
- // 处理异常的方式1
- }catch(异常类型2 变量名2){
- // 处理异常的方式2
- }finally{
- // 一定会执行的代码
- }
@Test public void test1(){ String str = "abc"; try{ int num = Integer.parseInt(str); System.out.println("hello--1"); }catch (NumberFormatException e){ System.out.println("出现数值转换异常了"); // 出现数值转换异常了 // e.getMessage(); e.printStackTrace(); }catch (NullPointerException e){ System.out.println("空指针异常"); } System.out.println("hello--2"); // hello--2 }
finally中声明的是一定会被执行的代码。即使catch中又出现异常了、try中有return语句、catch中有return语句等情况。
public int method(){ try{ int[] arr = new int[10]; System.out.println(arr[10]); return 1; }catch (ArrayIndexOutOfBoundsException e){ e.printStackTrace(); return 2; }finally { System.out.println("一定执行的代码"); } } @Test public void test1(){ int i = method(); System.out.println(i); }
一定执行的代码 2 java.lang.ArrayIndexOutOfBoundsException: 10 at FinallyTest.method(FinallyTest.java:14) at FinallyTest.test1(FinallyTest.java:26) ......
@Test public void test1(){ try{ int a = 10; int b = 0; System.out.println(a /b); // 触发异常 }catch (ArithmeticException e){ int[] arr = new int[10]; System.out.println(arr[10]); }catch (Exception e){ e.printStackTrace(); }finally { System.out.println("必须要执行"); } } //执行结果:------------------- 必须要执行 java.lang.ArrayIndexOutOfBoundsException: 10 at FinallyTest.test1(FinallyTest.java:25)
public class ReturnExeceptionDemo {
public static void main(String[] args) {
try{
methodA();
}catch (Exception e){
System.out.println(e.getMessage());
}
methodB();
}
static void methodA(){
try{
System.out.println("进入方法A");
throw new RuntimeException("制造异常");
} finally {
System.out.println("调用方法A的finally");
}
}
static void methodB(){
try{
System.out.println("进入方法B");
return;
} finally {
System.out.println("调用方法B的finally");
}
}
}
执行结果:
进入方法A
调用方法A的finally
制造异常
进入方法B
调用方法B的finally
- 如果try中有return语句, 那么finally中的代码还是会执⾏。因为return表示的是整个方法体返回, finally中的语句会在return之前执⾏。
- 但是return前执行的finally块内,对数据的修改效果对于引用类型和值类型会所不同:
public static int f(){ int ret = 0; try{ return ret; }finally { ++ret; System.out.println("finally 执行"); // finally 执行 0 } } public static int[] f2(){ int[] ret = new int[]{0}; try{ return ret; }finally { ret[0]++; System.out.println("finally 执行"); // finally执行 1 } } @Test public void test5(){ int aa = f(); System.out.println(aa); int[] a = f2(); System.out.println(a[0]); }
public int getNum() { try{ int a = 1 / 0; // 3: 代码走到这一行时,会遇到一个MathExeception异常,这样retrun 1 代码就不会执行了 return 1; }catch (Exception e){ // 5: 捕获异常 return 2; // 6: 走到这一行,异常机制:如果在catch中遇到了return 或者 异常,能使改方法终止的代码,有finally就必须先去执行finally的代码然后再返回值。 }finally { return 3; // 8: 走到这一行, 因为是return语句,这时方法就直接结束了,第6行的返回值不能被真正返回。如果finally仅仅是处理释放资源类的操作,那么就会返回2。 } } @Test public void test5(){ System.out.println(getNum()); // 3 }
(4)异常处理机制: throws + 异常类型
- throws + 异常类型 写在方法的声明处。指明此方法执行时,可能会抛出的异常类型。
- 一旦方法体执行时,出现异常,仍会在异常代码处生成一个异常类的对象,此对象满足throws后异常类型时,就会被抛出。
- 异常代码之后的代码,就不再执行。
- try-catch-finally: 真正的将异常给处理掉了。throws的方式只是将异常抛给了方法的调用者。并没有真正将异常处理掉。
- 如何选择 try-catch-finally 还是 throws?
- 如果父类中被重写的方法没有throws方式处理异常,则子类重写的方法也不能使用throws,意味着如果子类重写的方法中有异常,必须使用try-catch-finally方式处理。
- 执行的方法A中,先后调用了另外几个方法,这几个方法是递进关系执行的。建议这几个方法使用throws 的方式进行处理。而执行的方法A考虑使用try-catch-finally方式进行处理。
public class ThrowsTest { public void method1 () throws FileNotFoundException, IOException { File file = new File("hello.txt"); FileInputStream fis = new FileInputStream(file); int data = fis.read(); while(data != -1){ System.out.println((char)data); data = fis.read(); } fis.close(); } public void method2() throws IOException{ method1(); } public static void main(String[] args) { try{ new ThrowsTest().method2(); } catch (IOException e) { e.printStackTrace(); } } }
(5)手动抛出异常: throw
- 异常对象的产生: 系统自动生成的异常对象。
- 手动的生成一个异常对象,并抛出(throw)
class Student { private int id; public void regist(int id) throws Exception { if(id > 0){ this.id =id; }else { // System.out.println("你输入的数据有误");
// 手动抛出异常对象 throw new RuntimeException("你输入的数据有误"); } } @Override public String toString() { return "Student{" + "id=" + id + '}'; } } public class ThrowTest { public static void main(String[] args) { Student s = new Student(); try{ s.regist(1001); System.out.println(s); }catch (Exception e){ System.out.println(e.getMessage()); } } }
throw 和 throws的异同
throw: 生成一个异常对象,并抛出。使用在方法内部,手动抛出一个异常对象。
throws: 处理异常的方式。使用在方法声明处的末尾。 声明这个方法会抛出某种类型的异常,让使用者知道需要捕获的异常类型,try-catch-finally 进行处理。 出现异常的一种可能性,不一定发生。
(6)用户自定义异常类
如何自定义异常?
- 继承于现有的异常结构: RuntimeException、Exception
- 提供全局常量: serialVersionUID
public class MyException extends RuntimeException { static final long serialVersionUID = 2044963742759499859L; public MyException(){} public MyException(String msg){ super(msg); } }
public class MyExceptionTest { public static void main(String[] args) { try{ int i = Integer.parseInt(args[0]); int j = Integer.parseInt(args[1]); double result = test(i,j); System.out.println(result); }catch (NumberFormatException e){ System.out.println("数据类型不一致"); }catch (ArrayIndexOutOfBoundsException e){ System.out.println("缺少命令行参数"); }catch (ArithmeticException e){ System.out.println("除数为0"); }catch (MyException e){ System.out.println(e.getMessage()); } } static double test(int i, int j){ return i / j; } }
E:\JavaProgramLearn\JavaEE\07Exception\src>javac MyExceptionTest.java -encoding UTF-8 E:\JavaProgramLearn\JavaEE\07Exception\src>java MyExceptionTest 4 2 2.0 E:\JavaProgramLearn\JavaEE\07Exception\src>java MyExceptionTest "4" "2a" 数据类型不一致 E:\JavaProgramLearn\JavaEE\07Exception\src>java MyExceptionTest 缺少命令行参数 E:\JavaProgramLearn\JavaEE\07Exception\src>java MyExceptionTest "3" 0 除数为0