Java基础 - 异常



Java异常

异常体系
Throwable --| Error     --| VirtualMacthineError
						--| ThreadDeath
						--| ...
		  --| Exception --| RuntimeException
     					--| ...

Error
其 实例以及其子类的实例,都表示JVM运行错误,不能通过代码进行处理

VirtualMacthineError
虚拟机错误

ThreadDeath
线程死锁

Exception
其 实例以及其子类的实例,表示在程序运行过程中,不期望出现的异常,可以被JVM的异常处理机制处理。


分类
非检查异常

unchecked exception
ErrorRuntimeException 及其子类
例:
ArithmeticException 计算条件异常,比如 除0
ClassCastException 强制类型转换异常
ArrayIndexOutOfBoundsException 数组索引越界异常
NullPointerException NPE 最容易出现的空指针异常

检查异常

checked exception
ErrorRuntimeException 之外的其他异常

非检查异常和检查异常的区别

对于非检查异常,javac在编译期间不会发现并提示该异常,且并不要求在程序中处理该异常,但仍可以在代码中使用try catch语句进行捕获和处理。但此类异常属于代码缺陷,最好不要进行异常捕获,而应该进行代码修正。

对于检查异常,javac会强制要求代码开发者对此类异常进行处理,要么捕获要么抛出,否则编译无法通过。


异常追踪栈

示例代码运行结果如下

lower running ...
com.example.exception.HigherException: HigherException message & LowerException message
	at com.example.exception.TestDemo.higherRun(TestDemo.java:29)
	at com.example.exception.TestDemo.run(TestDemo.java:20)
	at com.example.exception.TestDemo.main(TestDemo.java:13)
Caused by: com.example.exception.LowerException: LowerException message
	at com.example.exception.TestDemo.lowerRun(TestDemo.java:36)
	at com.example.exception.TestDemo.higherRun(TestDemo.java:26)
	... 2 more

注:Caused by 后输出信息为异常链的内容,具体将在 异常链 章节中进行具体分析,在本章节中不进行分析

根据示例代码,在方法higherRun() 中最先抛出异常,导致所有直接调用或者间接调用该方法的方法都受到影响。当这些方法输出异常信息时,就形成了异常追踪栈

分析

com.example.exception.HigherException: HigherException message & LowerException  message
    at com.example.exception.TestDemo.higherRun(TestDemo.java:29)
    at com.example.exception.TestDemo.run(TestDemo.java:20)
    at com.example.exception.TestDemo.main(TestDemo.java:13)

方法higherRun抛出异常,调用该方法的 run方法无法继续执行,也抛出异常,之后调用run方法的main方法也抛出异常,在栈中就是从栈顶向栈底回溯。由于最终异常没有进行处理,直接抛给了JVM,故程序终止。
按顺序自上而下,第一行信息即为最早抛出的异常点。根据该异常点的类名、方法名以及异常代码所在行,即可定位到出现异常的代码位置。
异常信息输出格式
第一行:异常类型 : 异常原因的说明文字
其余行:at 包路径 . 类名 . 方法名 ( 文件名 : 异常代码所在行 )


异常处理
异常捕获
关键字

try catch finally

使用方式
try {
	...
} catch (LowerException e) {
    ...
} catch (Exception e) {
	...
}  finally {
    ...
}
特点
  • try中为会抛出异常的代码。
  • catch提供可匹配的异常类和对应的处理代码。
  • finally可选,若存在则必定执行,多用来处理流的关闭等清理工作。
执行顺序

try捕获异常,将捕获的异常与catch匹配,匹配到则执行该catch中的代码,没有匹配到则继续执行try...catch...后的代码,如果有finally,则先执行该块中代码,再执行后面的代码。

注意事项
  • JDK7,可以将处理方式相同异常以 try { ... } catch (AException | BException | ... e) { ... } 的形式进行捕获,多个异常类必须相互独立,不可存在继承关系,否则无法通过编译。
  • try中捕获的异常按顺序与catch匹配,匹配到执行相应的catch代码块,并且不再与后面的catch匹配,与所有catch未匹配到,异常将会抛给上层调用者。
  • 由于catch的异常类可以匹配到该异常类以及该异常类的子类,故如若多个catch的异常类存在继承关系,则需要将父异常类放置在子异常类的后面,否则将永远无法匹配到子异常类,也不能通过编译。
  • try catch finally 中的局部变量相互独立,不能共享使用。
  • catch可不存在,若catch不存在则finally必须存在,且无catch的情况下,异常无法处理。
  • finally
    try 执行则 finally 必定执行,除非使用 System.exit()
    – 若finally中存在return,则try或者catch中的return的值会被finallyreturn覆盖
    – 若finally中存在return,则try或者catch中即使抛出异常,该异常也不会再上抛,而是正常返回。原因应该是finally中存在return时,该方法的退出方法由异常完成出口更改为了正常完成出口,异常不再上抛,而是正常return;(是否是该原因存疑,需要验证;另外,退出方法、异常完成出口、正常完成出口的解析,请查阅 JVM栈帧 的相关知识)
    – 若finally中存在异常,则try或者catch中的上抛的异常将会被finally上抛的异常覆盖
异常上抛

Throws   将异常上抛至上层调用者
若在方法中抛出检查异常checked exception, 而该方法并没有处理该异常,就必须使用 throws 将异常上抛,否则无法通过编译。非检查异常unchecked exception 也可以上抛,但无意义。

异常链

在同模块或者不同模块之间的方法调用中,容易出现被调用方法抛出的异常被调用方法抛出的异常掩盖掉的问题,这也导致真正的异常源信息丢失。
比如,现有两个方法,一个除法运算方法A,一个数字输入方法BA调用B获取数字进行运算,如果B出现异常BException (以下简称BE),导致A无法完成运算而出现异常AException(以下简称AE),对于A的上层调用者C来说,C接收到的应该是AE,而不是BE。但BE又不能丢失,故将BE封装到AE中,形成异常链,对于C来说,C接受的仍然是AE,在异常定位时,又可以找得到BE这个异常源。

Throwable中的源码中,可以看到一个私有属性 cause,类型为Throwable,就是通过这个属性封装下层源异常类

/**
 * Throwable 部分源码
 */
 public class Throwable implements Serializable {
	...
    private Throwable cause = this;

	public Throwable(Throwable cause) {
        fillInStackTrace();
        detailMessage = (cause==null ? null : cause.toString());
        this.cause = cause;
    }

	public Throwable(String message, Throwable cause) {
        fillInStackTrace();
        detailMessage = message;
        this.cause = cause;
    }
}



自定义异常

自定义检查性异常 继承Exception
自定义运行时异常 继承RuntimeException

按照国际惯例,自定义的异常应该总是包含如下的构造函数:

  • 一个无参构造函数
  • 一个带有String参数的构造函数,并传递给父类的构造函数。
  • 一个带有String参数和Throwable参数,并都传递给父类构造函数
  • 一个带有Throwable参数的构造函数,并传递给父类的构造函数。

例如:

public class IOException extends Exception {
    static final long serialVersionUID = 7818375828146090155L;

    public IOException()
    {
        super();
    }

    public IOException(String message)
    {
        super(message);
    }

    public IOException(String message, Throwable cause)
    {
        super(message, cause);
    }

    public IOException(Throwable cause)
    {
        super(cause);
    }
}



多线程与异常处理
异常处理器
UncaugthExceptionHandler

使用举例

public class TestThread extends Thread {
    public void run() {
        throw new NullPointerException("空指针异常");
    }
}

public class TestThreadDemo {
    public static void main(String[] args) {

        TestThread t1 = new TestThread();
        t1.setName("线程1");
        setUncaughtExceptionHandler(t1);
        t1.start();


        TestThread t2 = new TestThread();
        t1.setName("线程2");
        setUncaughtExceptionHandler(t2);
        t2.start();
    }

    private static void setUncaughtExceptionHandler(TestThread testThread) {
        testThread.setUncaughtExceptionHandler((t, e) -> {
            System.out.println(" 线程:" + t.getName() + " 出现了异常:");
            e.printStackTrace();
        });
    }
}

执行结果(日志打印顺序不一定如下图所示)

线程:线程2 出现了异常:
java.lang.NullPointerException: 空指针异常
	at com.example.exception.TestThread.run(TestThread.java:11)
线程:Thread-1 出现了异常:
java.lang.NullPointerException: 空指针异常
	at com.example.exception.TestThread.run(TestThread.java:11)
DefaultUncaugthExceptionHandler

使用举例

public class TestThread extends Thread {
    public void run() {
        throw new NullPointerException("空指针异常");
    }
}

public class TestThreadDemo {
    public static void main(String[] args) {
        
        TestThread.setDefaultUncaughtExceptionHandler((t, e) -> {
            System.out.println(" 线程:" + t.getName() + " 出现了异常:");
            e.printStackTrace();
        });

        TestThread t1 = new TestThread();
        t1.setName("线程1");
        t1.start();

        TestThread t2 = new TestThread();
        t2.setName("线程2");
        t2.start();
    }
}

执行结果(日志打印顺序不一定如下图所示)

线程:线程2 出现了异常:
java.lang.NullPointerException: 空指针异常
	at com.example.exception.TestThread.run(TestThread.java:11)
线程:线程1 出现了异常:
java.lang.NullPointerException: 空指针异常
	at com.example.exception.TestThread.run(TestThread.java:11)
Future

如下,使用ExecutorService创建一个线程池并submit了一个实现了Callable接口的线程对象,从而获得封装了该线程执行结果的Future对象,而如果子线程中发生了异常,通过future.get()获取返回值时,可以捕获到ExecutionException异常。

public class ChildrenThread implements Callable<String> {

    @Override
    public String call() throws Exception {
        exceptionMethod();
        return "result";
    }

    private void exceptionMethod() {
        throw new RuntimeException("ChildThread exception");
    }
}

public class ChildrenThreadDemo {

    public static void main(String[] args) {

        ExecutorService executorService = Executors.newFixedThreadPool(8);
        Future<String> future = executorService.submit(new ChildrenThread());

        try {
            future.get();
        } catch (InterruptedException e) {
            System.out.println(String.format("handle exception in child thread. %s", e));
        } catch (ExecutionException e) {
            System.out.println(String.format("handle exception in child thread. %s", e));
        } finally {
            if (executorService != null) {
                executorService.shutdown();
            }
        }
    }
}

执行结果

handle exception in child thread. java.util.concurrent.ExecutionException: java.lang.RuntimeException: ChildThread exception



异常处理的一般原则



异常与事务



示例代码
//HigherException
public class HigherException extends Exception {

    public HigherException(String message, Throwable cause) {
        super(message, cause);
    }
    public HigherException(String message) {
        super(message);
    }
}

//LowerException
public class LowerException extends Exception {

    public LowerException(String message, Throwable cause) {
        super(message, cause);
    }
    public LowerException(String message) {
        super(message);
    }
}

//TestDemo
public class TestDemo {

    public static void main(String[] args) {

        try {
            work();
        } catch (HigherException e) {
            e.printStackTrace();
        }
    }

    public static void work() throws HigherException {

        try {
            lowerRun();
            System.out.println("higher running ...");
        } catch (LowerException e) {
            throw new HigherException("HigherException message & " + e.getMessage(), e);
        }
    }

    public static void lowerRun() throws LowerException {

        System.out.println("lower running ...");
        throw new LowerException("LowerException message");
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值