异常处理
一 什么是程序的异常
异常 :指的是程序在执行过程中,出现的非正常情况,如果不处理最终会导致JVM的非正常停止。
二 异常的抛出机制
Java中把不同的异常用不同的类表示,一旦发生某种异常,就创建该异常类型的对象
,并且抛出(throw)。然后程序员可以捕获(catch)到这个异常对象,并处理;如果没有捕获(catch)这个异常对象,那么这个异常对象将会导致程序终止。
三 Java异常体系
1 Throwable
java.lang.Throwable
类是Java程序执行过程中发生的异常事件对应的类的根父类。
2 Error 和 Exception
Throwable可分为两类:Error和Exception。分别对应着java.lang.Error
与java.lang.Exception
两个类。
**Error:**Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况。一般不编写针对性的代码进行处理。
- 例如:StackOverflowError(栈内存溢出)和OutOfMemoryError(堆内存溢出,简称OOM)。
Exception: 其它因编程错误或偶然的外在因素导致的一般性问题,需要使用针对性的代码进行处理,使程序继续运行。否则一旦发生异常,程序也会挂掉。例如:
- 空指针访问
- 试图读取不存在的文件
- 网络连接中断
- 数组角标越界
3 编译时异常和运行时异常
- 编译时期异常(即checked异常、受检异常):在代码编译阶段,编译器就能明确
警示
当前代码可能发生(不是一定发生)
xx异常,并明确督促
程序员提前编写处理它的代码。如果程序员没有编写
对应的异常处理代码,则编译器就会直接判定编译失败,从而不能生成字节码文件。通常,这类异常的发生不是由程序员的代码引起的,或者不是靠加简单判断就可以避免的,例如:FileNotFoundException(文件找不到异常)。 - 运行时期异常(即runtime异常、unchecked异常、非受检异常):在代码编译阶段,编译器完全不做任何检查,无论该异常是否会发生,编译器都不给出任何提示。只有等代码运行起来并确实发生了xx异常,它才能被发现。通常,这类异常是由程序员的代码编写不当引起的,只要稍加判断,或者细心检查就可以避免。
- java.lang.RuntimeException类及它的子类都是运行时异常。比如:ArrayIndexOutOfBoundsException数组下标越界异常,ClassCastException类型转换异常。
4 运行时常见的异常举例
package com.atguigu.exception;
import org.junit.Test;
import java.util.Scanner;
public class TestRuntimeException {
@Test
public void test01(){
//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);
}
}
5 编译时常见的异常举例
package com.atguigu.exception;
import org.junit.Test;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class TestCheckedException {
@Test
public void test06() {
Thread.sleep(1000);//休眠1秒 InterruptedException
}
@Test
public void test07(){
Class c = Class.forName("java.lang.String");//ClassNotFoundException
}
@Test
public void test10() {
File file = new File("hello.txt");
FileInputStream fis = new FileInputStream(file);//FileNotFoundException
int b = fis.read();//IOException
while(b != -1){
System.out.print((char)b);
b = fis.read();//IOException
}
fis.close();//IOException
}
}
四 异常处理的方式
方式1(抓抛模型):try-catch-finally
过程1:“抛”
程序在执行过程中,一旦出现异常,就会在出现异常的代码处,生成对应异常类的对象,并将此对象抛出。
一旦抛出,此程序就不执行其后的代码了。
过程2:“抓”
针对于过程1抛出的异常对象,进行捕获处理。此捕获处理的过程,就称为“抓”。
一旦将异常进行了处理,代码就可以继续执行、
2 基本格式
try{
...... //可能产生异常的代码
}
catch( 异常类型1 e ){
...... //当产生异常类型1型异常时的处置措施
}
catch( 异常类型2 e ){
...... //当产生异常类型2型异常时的处置措施
}
finally{
...... //无论是否发生异常,都无条件执行的语句
}
3 使用细节:
-
将可能出现异常的代码声明在try语句中,一旦代码出现异常,就会自动生成一个对应异常类的对象。并将此对象抛出。
-
针对于try中抛出的异常类的对象,使用之后的catch语句进行匹配。一旦匹配上,就进入catch语句块进行处理。一旦处理结束,代码就可继续向下执行。
-
如果声明了多个catch结构,不同异常类型在不存在子父类关系的情况下,谁声明在上面,谁声明在下面都可以。
-
如果多个异常类型满足子父类的关系,则必须将子类声明在父类结构的上面,否则就会报错。
-
catch中异常处理的方式:
1 自己编写输出的语句
2 printStackTrace():打印异常的详细信息(推荐)
3 getMessage():获取发生异常的原因
-
try中声明的变量,出了try结构之后,就不可以进行调用了。
-
try-catch结构是可以嵌套使用的
4 举例:
public class IndexOutExp {
public static void main(String[] args) {
String friends[] = { "lisa", "bily", "kessy" };
try {
for (int i = 0; i < 5; i++) {
System.out.println(friends[i]);
}
} catch (ArrayIndexOutOfBoundsException e) {
//System.out.println("index err");
e.printStackTrace();
}
System.out.println("程序结束");
}
}
4 finally的使用说明
4.1 我们将一定要被执行的代码声明在finally结构中。
4.2 更深刻的理解:无论try中或catch中是否存在仍未被处理的异常,无论try中或catch中是否存在return语句等,finally中声明的语句都一定要被执行。
4.3 finally语句和catch语句是可选的,但finally不能单独使用
4.4 举例:
public class FinallyTest1 {
public static void main(String[] args) {
int result = test("12");
System.out.println(result);
}
public static int test(String string){
try{
Integer.parseInt(string);
return 1;
}catch(NumberFormatException e){
e.printStackTrace();
return -1;
}finally{
System.out.println("程序结束");
}
}
}
//运行结果:
//test结束
//1
public class FinallyTest2 {
public static void main(String[] args) {
int result = test1("a");
System.out.println(result);
}
public static int test1(String string){
try{
Integer.parseInt(string);
return 1;
}catch(NumberFormatException e){
e.printStackTrace();
return -1;
}finally{
System.out.println("程序结束");
}
}
}
//运行结果:
//test结束
//-1
public class FinallyTest3 {
public static void main(String[] args) {
int result = test("a");
System.out.println(result);
}
public static int test(String str) {
try {
Integer.parseInt(str);
return 1;
} catch (NumberFormatException e) {
return -1;
} finally {
System.out.println("test结束");
return 0;
}
}
}
//运行结果:
//test结束
//0
方式2:声明抛出异常类型(throws)
如果在编写方法体的代码时,某句代码可能发生某个编译时异常
,不处理编译不通过,但是在当前方法体中可能不适合处理
或无法给出合理的处理方式
,则此方法应显示地
声明抛出异常,表明该方法将不对这些异常进行处理,而由该方法的调用者负责处理。
1 throws基本格式
修饰符 返回值类型 方法名(参数) throws 异常类名1,异常类名2…{
//可能存在编译时异常
}
在throws后面可以写多个异常类型,用逗号隔开。
2 举例:
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class ThrowsTest {
public static void main(String[] args) {
mothods3();
}
public static void mothods3(){
try{
mothods2();
}catch(FileNotFoundException e){
e.printStackTrace();
}catch(IOException e){
e.printStackTrace();
}finally{
System.out.println("程序结束");
}
}
public static void mothods2() throws FileNotFoundException,IOException{//mothods也解决不了继续往上抛
mothods1();
}
public static void mothods1() throws FileNotFoundException, IOException {
File file = new File("hello.txt");
FileInputStream fis = new FileInputStream(file);//可能会报FileNotFoundException
int data = fis.read();
while(data != -1){
System.out.println((char)data);
data = fis.read();
}
fis.close();//可能会报IOException
}
}
3 方法重写中throws的要求
对于throws异常列表要求:
- 如果父类被重写方法的方法签名后面没有 “throws 编译时异常类型”,那么重写方法时,方法签名后面也不能出现“throws 编译时异常类型”。
- 如果父类被重写方法的方法签名后面有 “
throws 编译时异常类型
”,那么重写方法时,throws的编译时异常类型必须 <= 被重写方法throws的编译时异常类型,或者不throws编译时异常。 - 方法重写,对于“
throws 运行时异常类型
”没有要求。
举例:
import java.io.IOException;
class Father{
public void method()throws IOException,ClassCastException{
System.out.println("Father.method");
}
}
class Son extends Father{
@Override
public void method() throws IOException,ClassCastException {
System.out.println("Son.method");
}
}
4 两种异常处理方式的选择
前提:对于异常,使用相应的处理方式。此时的异常,主要指的是编译时异常。
-
如果程序代码中,涉及到资源的调用(流、数据库连接、网络连接等),则必须考虑使用try-catch-finally来处理,保证不出现内存泄漏。
-
如果父类被重写的方法没有throws异常类型,则子类重写的方法中如果出现异常,只能考虑使用try-catch-finally进行处理,不能throws。
-
开发中,方法a中依次调用了方法b,c,d等方法,方法b,c,d之间是递进关系。此时,如果方法b,c,d中有异常,我们通常选择使用throws,而方法a中通常选择使用try-catch-finally。4 两种异常处理方式的选择
五 手动抛出异常对象:throw
1 使用格式
throw new 异常类名(参数);
如果是编译时异常类型的对象,同样需要使用throws或者try…catch处理,否则编译不通过。
2 使用注意点:
无论是编译时异常类型的对象,还是运行时异常类型的对象,如果没有被try…catch合理的处理,都会导致程序崩溃。
throw语句会导致程序执行流程被改变,throw语句是明确抛出一个异常对象,因此它下面的代码将不会执行
。
如果当前方法没有try…catch处理这个异常对象,throw语句就会代替return语句
提前终止当前方法的执行,并返回一个异常对象给调用者。
3 举例:
package Test1;
public class ThrowTest {
public static void main(String[] args) {
Student student = new Student();
try{
student.regist(10);
student.regist(-18);
System.out.println(student);
}catch(Exception e){
e.printStackTrace();
}
}
}
package Test1;
public class Student {
int id;
public void regist(int id) throws Exception {
if(id > 0){
this.id = id;
}else{
throw new Exception("输入非法字符");
}
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
'}';
}
}
六 自定义异常
1 如何自定义异常类
(1)要继承一个异常类型
自定义一个编译时异常类型:自定义类继承java.lang.Exception
。
自定义一个运行时异常类型:自定义类继承java.lang.RuntimeException
。
(2)建议大家提供至少两个构造器,一个是无参构造,一个是(String message)构造器。
(3)自定义异常需要提供serialVersionUID
2 注意点
- 自定义的异常只能通过throw抛出。
- 自定义异常最重要的是异常类的名字和message属性。当异常出现时,可以根据名字判断异常类型。比如:
BelowZeroException("除0");
、 - 自定义异常对象只能手动抛出。抛出后由try…catch处理,也可以甩锅throws给调用者处理。
3 举例:
package Test1;
public class DivisionDemo {
public static void main(String[] args) {
try{
int m = Integer.parseInt(args[0]);
int n = Integer.parseInt(args[1]);
int result = divide(m,n);
System.out.println(result);
}catch(BelowZeroException e){
System.out.println(e.getMessage());
}catch(NumberFormatException e){
System.out.println("数据类型不一致");
}catch(ArrayIndexOutOfBoundsException e){
System.out.println("缺少命令行参数");
}catch(ArithmeticException e){
System.out.println("除0");
}
}
public static int divide(int m,int n) throws BelowZeroException{//手动抛出
if(m < 0 || n < 0){
throw new BelowZeroException("输入负数");
}else{
return m / n ;
}
}
}
package Test1;
public class BelowZeroException extends Exception{
public static final long serialVertionUID = -33875169939948L;
public BelowZeroException() {
}
public BelowZeroException(String message) {
super(message);
}
}