目录
一、异常概述与异常体系结构
1. 异常概述
异常 : 在Java语言中,将程序执行中发生的不正常情况称为"异常"。(开发过程中的语法错误和逻辑错误不是异常)
在Java程序执行过程中所发生的异常事件可分为两类 :
-
Error : Java虚拟机无法解决的严重问题。如 : JVM系统内部错误、资源耗尽等严重情况。比如 : StackOverflowError 和 OOM。一般不编写针对性的代码进行处理。
-
Exception : 其它编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理,例如 :
-
空指针访问
-
试图读取不存在的文件
-
网络连接中断
-
数组角标越界
由于Error无法编写针对性的代码来解决,所以本章所说的异常都是指的"Exception"
-
-
对于Exception,一般有两种解决办法 : 一是遇到错误就终止程序的运行。另一种方法是由程序员在编写程序时,就考虑到错误的检测、错误消息的提示,以及错误的处理。
-
捕获异常最理想的是在编译期间 (javac)(在idea里会有提示),但有的错误只有在运行(java)的时候才会发生。比如 : 数组下标越界等。
所以异常分为编译时异常和运行时异常 :
编译时异常指的是在命令行中javac以后,出现错误,并且无法生成字节码文件,无法运行
运行时异常指的是在javac生成字节码文件以后,java运行时报错
2. 异常体系结构
java . lang . Throwable
|-------java . lang . Error : 一般不编写针对性的代码进行处理
|-------java . lang . Exception : 可以进行异常的处理
|------编译时异常(checked)
|-------IOException
|-------FileNotFoundException
|-------ClassNotFoundException
|-------运行时异常(unchecked)
|-------NullPointerException
|-------ArrayIndexOutOfBoundsException
|-------ClassCastException
|-------NumberFormatException
|-------InputMismatchException
二、常见异常
常见异常见以下代码 :
public class ExceptionTest {
/***************************以下是运行时异常********************************/
//NullPointerException
//空指针异常 : 出现 "null.什么" 就会有空指针异常
@Test
public void test1(){
String[] strings = new String[2];
System.out.println(strings[1].toString());
}
//IndexOutOfBoundsException
@Test
public void test2(){
int[] arr =new int[2];
//ArrayIndexOutOfBoundsException
// System.out.println(arr[2]);
//
//StringIndexOutOfBoundsException
String str = "abc";
System.out.println(str.charAt(3));
}
//ClassCastException
@Test
public void test3() {
Object obj = new Date();//使用多态让obj为Object类型的对象
String str = (String) obj;//编译器右边认为是Object左边向下转型为String所以编译通过了
}
//NumberFormatException
@Test
public void test4(){
String str = "abc";
int a = Integer.parseInt(str);
}
//InputMismatchException
@Test
public void test5(){
Scanner sc = new Scanner(System.in);
int score = sc.nextInt();
System.out.println(score);
}
//ArithmeticException
@Test
public void test6(){
int a = 10;
int b = 0;
System.out.println(a / b);
}
/***************************以下是编译时异常********************************/
@Test
public void test7(){
// File file = new File("hello.txt");
// //Unhandled exception: java.io.FileNotFoundException
// FileInputStream fis = new FileInputStream(file);
// //Unhandled exception: java.io.IOException
// int data = fis.read();
// while (data != -1) {
// System.out.println((char)data);
// data = fis.read();
// }
//
// fis.close();
}
}
三、异常处理机制一
Java采用的异常处理机制,是将异常处理的程序代码集中在一起,与正常的程序代码分开,使得程序简洁优雅,并易于维护。
异常的处理 : 抓抛模型
-
过程一 : "抛",程序在正常执行过程中,一旦出现异常,就会在异常代码处生成一个对应异常类的对象。
并将此对象抛出。一旦抛出对象以后,其后的代码就不再执行。
-
过程二 : "抓",可以理解为异常的处理方式,分为 : try - catch - finally 和 throws
关于异常对象的产生 :
-
系统自动生成的异常对象
-
手动的生成一个异常对象,并抛出(throw : 用于产生一个异常对象)
try - catch - finally 的使用 :
try{
//可能出现异常的代码
} catch(异常类型1 变量名1){
//处理异常的方式1
} catch(异常类型2 变量名2){
//处理异常的方式2
} catch(异常类型3 变量名3){
//处理异常的方式3
}
...
finally{
//一定会执行的代码
}
说明 :
-
finally是可选的
-
使用try将可能出现异常的代码包装起来,在执行过程中,一旦出现异常,就会生成一个对应异常类的对象,根据此对象的类型,去catch中进行匹配。
-
一旦try中的异常对象匹配到某一个catch时,就进入catch中进行异常处理。一旦处理完成,就跳出当前的try-catch结构(在没有写finally的情况下),继续执行其后的代码。
-
catch中的异常类型如果没有子父类关系,则谁声明在上,谁声明在下无所谓。catch中的异常类型如果满足子父类关系,则要求子类一定要声明在父类的上面,否则报错。
-
常用的异常对象处理方式 :
-
String getMessage()
-
printStackTrace()
-
-
在try结构中声明的变量,在出了try结构以后,就不能再被调用
-
try-catch-finally结构可以嵌套
public class ExceptionTest1 {
//NumberFormatException
@Test
public void test1(){
String str = "abc";
try {
int a = Integer.parseInt(str);
//不执行
System.out.println("hello--1");
} catch (NullPointerException e){
System.out.println("出现空指针异常");
} catch (NumberFormatException e){
// System.out.println("出现数值转换异常");
String info = e.getMessage();
System.out.println(info);
e.printStackTrace();
} catch (Exception e){
System.out.println("出现异常");
}
//执行
System.out.println("hello--2");
//在try结构中声明的变量,在出了try结构以后,就不能再被调用
// System.out.println(a);
}
@Test
public void test2(){
try {
File file = new File("hello.txt");
//Unhandled exception: java.io.FileNotFoundException
FileInputStream fis = new FileInputStream(file);
//Unhandled exception: java.io.IOException
int data = fis.read();
while (data != -1) {
System.out.println((char) data);
data = fis.read();
}
fis.close();
} catch (FileNotFoundException e){
e.printStackTrace();
} catch (IOException e){
e.printStackTrace();
}
}
}
try-catch-finally中finally的使用 :
-
finally是可选的
-
finally中执行的是一定会被执行的代码,即使catch中又出现异常了,try中有return语句,catch中有return语句等情况,那么也会在执行完毕finally中的代码,再return或者抛出异常结束本方法。
-
像数据库连接,输入输出流,网络编程中的Socket等资源,JVM是不能自动回收的,我们需要自己手动进行资源的释放。此时的资源释放的操作就需要声明在finally当中。
public class FinallyTest { @Test public void test1(){ try { int a = 10; int b = 0; System.out.println(a / b); } catch (ArithmeticException e){ e.printStackTrace(); int[] arr = new int[10]; System.out.println(arr[10]); } catch (Exception e){ e.printStackTrace(); } finally { //catch中出现了异常时,先执行了finally的代码再报的异常 System.out.println("hello world"); } //catch中出异常了就不会被执行 System.out.println("hello world"); } public int method(){ try { int[] arr = new int[10]; System.out.println(arr[10]); return 1; } catch (ArrayIndexOutOfBoundsException e){ e.printStackTrace(); return 2; } finally{ //先执行finally 最后return结束方法 System.out.println("I will be executed"); } } @Test public void testMethod(){ int num = method(); System.out.println(num); } @Test public void testFile(){ FileInputStream fis = null; try{ File file = new File("D:\\JavaIOTest.txt"); fis = new FileInputStream(file); int data = fis.read(); while (data != -1) { System.out.print((char) data); data = fis.read(); } } catch (FileNotFoundException e){ e.printStackTrace(); } catch (IOException e){ e.printStackTrace(); } finally { try { if (fis != null) fis.close(); } catch (IOException e){ e.printStackTrace(); } } } }
体会1 : 使用try-catch-finally结构处理编译时异常,使得程序在编译时就不再报错,但是运行时仍可能报错,相当于问我们使用try-catch-finally将一个编译时可能出现的异常,延迟到运行时出现。
体会2 : 开发中,由于运行时异常比较常见,所以我们通常不针对运行时异常编写try-catch-finally了,针对编译时异常,一定要考虑异常的处理。
四、异常处理机制二
throws + 异常类型
-
"throws + 异常类型"写在方法的声明处,指明此方法执行时,可能会抛出的异常类型。一旦方法体执行时,出现异常,仍会在异常代码处生成一个异常类的对象,此对象满足throws后的异常类型时,就会被抛出,异常 代码后续的代码就不再执行!
-
体会 : try-catch-finally : 真正的将异常给处理掉了
throws : 只是将异常抛给了方法的调用者,并没有真正的将异常处理掉
-
实际上,有些编译时报错提示要处理异常的方法,是因为在其中调用了throws出异常的方法了,需要我们处理掉,不处理也要throws给上一级处理。
-
如果一个方法中出现了运行时异常,在调用该方法处加入try-catch-finally进行处理,那么throws和不throws的效果都一样的,throws的作用我认为主要体现在方法中出现了编译时异常,必须将其处理掉,不然不能通过编译,但此时在方法内不知道如何处理掉此异常,那么就可以选择throws抛给上级调用该方法的方法内来处理此异常,这样出现异常的这个方法就可以通过编译了。
public class ExceptionTest2 { public static void main(String[] args) { try { method2(); } catch (IOException e){ e.printStackTrace(); } method3(); } public static void method3(){ try{ method2(); }catch (IOException e){ e.printStackTrace(); } } public static void method2() throws IOException{ method1(); } public static 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(); } }
4.1 方法重写的规则
方法重写的规则之一 : 子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型
如果父类被重写的方法没有抛出异常,那么子类重写的方法也不能抛异常。
public class OverrideTest {
public static void main(String[] args) {
OverrideTest test = new OverrideTest();
test.display(new SubClass());
}
public void display(SubClass s){
try {
s.method();
} catch (IOException e){
e.printStackTrace();
}
}
}
class SuperClass{
public void method() throws IOException{
}
}
class SubClass extends SuperClass{
//子类重写的方法只能抛比父类被重写方法抛出的异常小的异常类型
public void method() throws FileNotFoundException {
}
}
4.2 开发中如何选择机制
-
如果父类中被重写的方法没有throws的方式处理异常,则子类重写的方法也不能使用throws,意味着,如果子类重写的方法中有异常,必须使用try-catch-finally方式进行处理。
-
执行的方法a中,先后又调用了另外的几个方法,这几个方法是递进关系执行的。我们建议这几个方法使用throws的方式进行处理,就不要在方法内部进行try-catch-finally处理了,因为处理完此方法以后,下面的方法也会报出异常。而执行的方法a可以考虑使用try-catch-finally的方式进行处理。
-
一般在我们写代码的时候,如果没有出现红叉提醒处理编译时的异常,是不需要解决异常的。
五、手动抛出异常
throw new 异常对象
只有异常类的对象才可以throw
public class StudentTest {
public static void main(String[] args) {
try {
Student st = new Student();
st.regist(-1);
System.out.println(st);
}catch (Exception e){
System.out.println(e.getMessage());
}
}
}
class Student{
private int id;
public void regist(int id) throws Exception {
if (id > 0) {
this.id = id;
} else {
// System.out.println("您输入的数据非法");
//手动抛出一个异常对象
//抛出RunTimeException可以不用处理 直接让他报异常就行
// throw new RuntimeException("您输入的数据非法!");
//抛出Exception必须处理,因为Exception包含编译时异常,编译不通过运行不了
throw new Exception("您输入的数据非法!");
}
}
public String toString(){
return "Student{id = " + this.id + "}";
}
}
5.1 throw和throws的区别
throws是异常处理的一种机制,即声明出方法可能要抛出的各种异常类,交给上级调用该方法的方法处理,声明在方法的声明处。
throw是异常生成的一种方式,手动的生成(抛出)异常对象,声明在方法体内。
六、用户自定义异常类
如何自定义异常类 :
-
继承于现有的异常结构 : RunTimeException Exception
-
提供全局常量 : serialVersionUID 序列版本号,唯一的标识这个类,在网络传输时会用到,来判断传递的对象是不是同一个类的。
-
提供重载的构造器
public class MyException extends Exception{
static final long serialVersionUID = -7034897190745766939L;
public MyException(){
super();
}
public MyException(String message){
super(message);
}
}