异常概述与异常体系结构
Java源程序 - javac.exe(编译) -> 字节码文件 - java.exe(运行) -> 在内存中加载、运行类
- 异常体系结构:
-
父类:java.lang.Throwable
-
子类: java.lang.Error,不编写针对代码处理
-
子类: java.lang.Exception,编写针对代码处理
- 编译时异常(checked):IOException、ClassNotException、FileNotFoundException
- 运行时异常(unchecked):NullPointerException、ArrayIndexOutBoundsException
-
异常: 在Java语言中,将程序执行中发生的不正常情况称为
异常
- 开发过程中的语法错误和逻辑错误不是异常
Java程序在执行过程中所发生的异常事件可分为两类:
Error:Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等;如:StackOverflowError和OOM,一般不编写针对性代码进行处理
public class ErrorTest {
public static void mian (String[] args) {
// 1.栈溢出: java.lang.StackOverflowError
// main(args);
// 2.堆溢出: java.lang.OutOfMemoryError(OOM)
Integer[] arr = new Integer[1024*1024*1024];
}
}
Exception:其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性代码进行处理。- 空指针访问
- 读取不存在的文件
- 网络中断
- 数组角标越界
// Throwable
// 1. Error
// 2. Exception
public class ExceptionTest {
//
}
-
对于这些错误,一般有两种解决方案: 一是遇到错误就终止程序运行;另一种方法是由程序员在编写程序时,就考虑到错误的检测、错误消息提示,及错误的处理
-
捕获错误最理想的是在
编译期间,但有的错误只会在运行时才会发生;如: 除数为0、数组下标越界等- 编译时异常(checked)
- 运行时异常(RuntimeException)
-
开发中,运行时异常一般不处理,我们着重处理编译时异常
Java异常处理
Java采用的异常处理机制,是将异常处理的程序代码集中在一起,与正常的程序代码分开,使得程序简洁、优雅、易于维护
Java异常处理的方式:
-
Java提供的是异常处理的
抓抛模型-
抛:程序正常执行过程中,一旦出现异常,就会在异常代码处生成一个对应异常类的对象,并将此对象抛出,且之后的代码终止执行 -
抓: 可以理解为异常的处理方式
-
-
Java程序的执行过程中如出现异常,会生成一个异常类对象,改异常对象将被提交给Java运行时系统,这个过程称为抛出(throw)异常
-
异常对象的生成
- 由虚拟机自动生成,后台自动创建一个对应异常类的实例对象并抛出 --- 自动抛出
- 由开发人员手动创建
常见异常
- 编译时异常:执行javac.exe命令时,可能出现的异常
- 运行时异常:执行java.exe命令时,出现的异常
// 1.编译时异常
public class CheckedTest {
// FileNotFoundException
@Test
public void test7 () {
File f1 = new File("hello.txt");
// 流
FileInputStream fis = new FileInputStream(f1);
// 读取流
// IOException
int data = fis.read();
while (data != -1) {
System.out.println((char)data);
data = fis.read();
}
// 关闭流
fis.close();
}
}
// 2.运行时异常
public class ExceptionTest {
// NullPointerException
@Test
public void test1 () {
int[] arr = null;
System.out.println(arr[3]);
String str = "123";
str = null;
System.out.println(str.charAt(0));
}
// IndexOutOfBoundsException
@Test
public void test2 () {
// ArrarIndexOutOfBoundsException
int[] arr = new int[5];
System.out.println(arr[5]);
// IndexOutOfBoundsException
String str = "abc";
System.out.println(str.charAt(3));
}
// ClassCastException 类型转换异常
@Test
public void test3 () {
Object o1 = new Date();
String s1 = (String)o1;
}
// NumberFormatException 数值类型转换错误
@Test
public void test4 () {
String str = "123";
str = "123ab";
int num = Integer.parseInt(str);
}
// InputMismatchException 输入异常
@Test
public void test5 () {
Scanner scan = new Scanner(System.in);
int score = scan.nextInt(); // "abc"
System.out.println(score);
scan.close();
}
// ArithmeticException 算数异常
public void test6 () {
int a = 10;
int b = 0;
System.out.println(a / b);
}
}
异常处理机制一*:try-catch-finally
- 系统自动生成的异常
一、try-catch-finally(自己处理)
try {
// 可能出现异常的代码
} catch (异常类型1 变量名1) {
// 处理异常的方式1
} catch (异常类型2 变量名2) {
// 处理异常的方式2
} catch (异常类型n 变量名n) {
// 处理异常的方式n
} finally {
// 一定会执行的代码
}
- 说明:
- finally是可选的
- 使用try将可能出现异常代码包裹起来,在执行过程中,出现异常后,就会生成一个对应异常类的对象,根据此对象的类型,去catch中进行匹配
- 一旦try中的异常对象匹配到某个catch后,就进入catch中就进行异常处理,处理完成后,就跳出 try-catch结构(无finally),继续执行其后的代码
- catch中的异常类型如果没有子父类关系,则声明前后没有关系,catch中的异常类型如果有子父类关系,则要求子类在前父类在后
- 常用异常对象处理方式: ①String getMessage()、② void printStackTrace()
- 在try、catch、finally结构中声明的变量,在结构外就不能调用;解决办法就是在结构外声明并初始化,在结构中使用
- 可以嵌套使用
体会:
- 使用try-catch-finally处理编译时异常,使得程序在编译时就不再报错,但运行时仍可能报错;相当于我们使用 try-catch-finally 将一个编译时可能出现的异常,延迟到运行时出现
- 开发中,由于运行时异常比较常见,所以我们通常就不针对运行时异常进行处理;但
针对编译时异常,我们一定要考虑异常处理
try-catch-finally 中 finally的使用
- finally 是可选的
- finally 中声明的是一定会被执行的代码,即使catch中出现异常,或try、catch 中出现 return 语句,也会先执行 finally 中的代码后才执行 return 语句
- 垃圾回收中对物理连接,如:数据库连接、输入输出流、Socket连接操作的资源,JVM是不能自动回收的,需要手动关闭(
必须要执行的操作),可以放在 finally中执行
public class FinallyTest {
@Test
// finally
public void test1 () {
// 要赋值
FileInputStream fis = null;
try {
File f1 = new File("Hello.txt");
fis = new FileInputStream(f1);
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();
} finally {
// fis.close();
try {
// 避免空指针异常
if (fis != null) {
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Test
public int testin () {
try {
int[] arr = new int[5];
System.out.println(arr[5]);
return 1;
} catch (ArrayIndexOutOfBoundsException e) {
e.printStackTrace();
return 0;
} finally {
System.out.println("俺一定会执行");
// return 2;
}
// 俺一定会执行
// 0
}
@Test
public void test0 () {
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 {
// 一定会被执行的代码
System.out.println("我最帅!");
}
// 我最帅!
}
}
public class ExceptionTest {
String str = "123";
str = "abc";
int num = 0;
try {
num = Integer.parseInt(str);
System.out.println("try异常");
} catch (NumberFormatException e) {
System.out.println("数值转换异常!");
// 1. String getMessage();
System.out.println(e.getMessage());
// For input string "abc"
// 2. printStackTrace();报错的详细信息
e.printStackTrace();
} catch (NullPointerException e) {
System.out.println("NullPointerException");
} catch (Exception e) {
// ...
}
System.out.println("异常处理后");
// 输出:
// 数值转换异常!
// 异常处理后
public void test1 () {
try {
File f1 = new File("Hello.txt");
FileInputStream fis = new FileInputStream(fis);
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();
}
}
}
二、throws + 异常类型(向上求助)
- 见下
异常处理机制二*:throws
- 系统自动生成的异常
- 写在方法的声明处,指明此方法在执行时可能出现的异常,出现异常时仍会在异常代码生成处生成一个异常类的对象,此对象满足throws后的异常类型时,会将异常抛给调用者,异常对象生成处之后的代码就不再执行
- 并没有真正解决问题,只是交给上级去处理
- try-catch-finally的方式是真正的将异常处理掉;throws的方式只是将异常抛给了方法的调用者,并没有自己解决异常
public class ExceptionTest2 {
public static void main (String[] args) {
try {
method2();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void method2 () throws FileNotFoundException, IOException {
method2();
}
public static void method1 () throws FileNotFoundException, IOException {
File f1 = new File("Hello.txt");
FileInputStream fis = new FileInputStream(fis);
int data = fis.read();
while (data != -1) {
System.out.println((char)data);
data = fis.read();
}
fis.close();
}
}
实际应用:
-
方法重写的规则之一: 子类重写的方法抛出的异常类型,不大于父类抛出的类型
-
开发中如何选择try-catch-finally和throws
- 父类中被重写的方法没有throws,那子类中只能使用try-catch-finally
- 多个方法递进调用时,各方法使用throws,在调用处使用try-catch统一处理
public class OverrideTest {
public static void main (String[] args) {
OverrideTest t1 = new OverrideTest();
t1.display(new SubClass());
}
public void display (SuperClass 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 {
//
}
}
手动抛出异常(手动生成异常):throw
public class StudentTest {
try {
Student s1 = new Student();
s1.regist(-1001);
System.out.println(s); // Student[id = 0]
} catch (Exception e) {
e.printStackTrace();
System.out.println(e.getMessage()); // input illegal data!
}
}
class Student {
private int id;
public void regist (int id) throws Exception { // 体现异常的处理
if (id > 0) {
this.id = id;
} else {
System.out.println("illegal data!");
// 手动抛出异常(运行)
// throw new RuntimeException("input illegal data!")
// 生成异常对象(编译)
// throw new Exception("input illegal data!");
// 自定义异常类
throw new MyException("input illegal data!");
}
}
public String toString () {
return "Student[ id = " + id + "]"
}
}
用户自定义异常类
如何自定义异常类
- 继承于现有的异常结构: RuntimeException(运行时异常)、Exception(编译异常)
- 模仿其它异常继承类
- 提供全局常量serialVersionUID,唯一标识此类
- 提供重载的构造器
// 如上面使用
class MyException extends RuntimeException {
static final long serialVersionUID = -777777777777777777L;
public MyException () {}
public MyException (String msg) {
super(msg);
}
}
总结: 异常处理5个关键字
捕获异常
- try:包裹执行可能产生异常的代码
- catch:捕获异常
- finally:无论是否发生异常或return,代码总被执行
抛出异常
- throw:异常的生成阶段,手动抛出异常对象
声明异常
- throws:异常的处理方式,声明方法可能要抛出的各种异常类
上游排污(throw),下游治污(try、throws)
throw和throws对比
- throws写在方法的声名处
- throw写在方法中
- throws是异常的处理方式之一
- throw是手动生成异常对象并抛出
final、finally、finalize三者的区别
- final: 修饰类、变量
- finally:异常处理try-catch-finally
- finalize:单词
小悟
- 世上最远的
距离,我在if你在else,似乎一直相伴又永远分离 - 世上最痴心的
等待,我当case你是switch,获取永远都选不上自己 - 世上最真情的
相依,你在try我在catch;无论你发神马脾气,我都默默承受,静静处理;到时再来期待我们的finally
练习
// 1.
public class ReturnExceptionDemo {
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");
}
}
public static void main (String[] args) {
try {
methodA();
} catch (Exception e) {
System.out.println(e.getMessage());
}
methodB();
}
// 进入方法A
// 用A方法的finally
// 制造异常
// 进入方法B
// 调用B方法的finally
}
// 2.
// 异常的类型
// 异常的处理 try-catch-finally、throws、throw
// 自定义异常类
public class EcmDef {
public static void main (String[] args) {
try {
int i = Integer.parseInt(args[0]);
int j = Integer.parseInt(args[1]);
int result = ecm(i, j);
System.out.println(result);
} catch (NumberFormatException e) {
// e.printStackTrace();
System.out.println("NumberFormatException");
} catch (ArrayIndexOutOfBoundsException e) {
// e.printStackTrace();
System.out.println("ArrayIndexOutOfBounds");
} catch (ArithmeticException e) {
// e.printStackTrace();
System.out.println("ArithmeticException");
} catch (EcDef e) {
System.out.println("EcDef" + e.getMessage());
} finally {
System.out.println("done");
}
System.out.println("after");
// 执行顺序:
// ArrayIndexOutOfBounds
// done
// after
}
public static int ecm (int i, int j) throws EcDef {
if (i < 0 || j < 0) {
throw new EcDef("cannot input fushu!");
} else {
return i / j;
}
}
}
class EcDef extends Exception {
static final long serialVersionUID = -46546131453132135L;
public EcDef () {}
public EcDef (String msg) {
super(msg);
}
}