Java的Exception异常机制
Posted fntp
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java的Exception异常机制相关的知识,希望对你有一定的参考价值。
好久不见啊,兄弟们,今天阿鑫给大家带来了干货!Java知识点系列,Java的Exception异常机制(一)!
一言不合,就正式进入今天的饭后主题:Java的Exception异常机制。为什么会突然聊到异常?其实不是突然,而是我已经准备了很久,但苦于没有好的例子来讲解,从表象到底层实现,我想给他扒得内裤都不剩,好吧,不搞yellow,我们还是开心地来聊一聊今天的主题:Java的Exception机制!
在Java中,我们在执行代码的过程中难免会遇到错误与Exception异常,可是我们一直都是锤头Coding而忽略了学习Exception这个东西!我们只是知道在发生Exception的地方让代码自动生成throw exception或者是使用try-catch括起来处理,那你了解Java的Exception吗?今天就让我们把一起来看看Java的Exception吧!
在Java中,我们的代码再出现错误的时候无非是两种情况:一是Error,一是异常Exception。如果是Error,那么则说明出现了错误,错误出现在编译器预编译阶段,错误是可以预见的,而异常却是无法预见的。Java中,无论是Error还是Exception都是Throwable的子类(见下图)。这是个什么玩意儿,可能大部分的人都没人听说过吧…不过这并不重要…开始上图…
Error与Exception的区别与相同点:
- Error是严重的错误,并非异常,错误是无法通过编码解决的。
- Exception是异常,异常则是可以通过编码解决的。
Error与Exception的相同点:
- Error与Exception类都是Throwable的子类
Exception类是所有异常的超类。
Java所有的异常在寻找父类的时候最终都能找到Exception类。
异常的分类
java.lang.Exception类是所有异常的超类,主要分为以下两种:
RuntimeException-运行时异常,也叫作非检测性异常.
lOException和其它异常-其它异常,也叫作检测性异常,所谓检测性异常就是指在编译阶段都能被编译器检测出来的异常。
其中RuntimeException类的主要子类︰ArithmeticException类-算术异常,ArraylndexOutOfBoundsException类-数组下标越界异常,NullPointerException-空指针异常,ClassCastException-类型转换异常,NumberEormatException-数字格式异常注意:
当程序执行过程中发生异常但又没有手动处理时,则由Java虚拟机采用默认方式处理异常,而默认处理方式就是︰打印异常的名称、异常发生的原因、异常发生的位置以及终止程序。
关于RuntimeException
非检测性异常
package com.sinsy.exception;
public class ExceptionTest {
public static void main(String[] args) {
// 非检测性异常 : Exception in thread "main" java.lang.ArithmeticException: / by zero jvm无法检测
System.out.println(5 / 0);
//检测型异常
Thread.sleep(3000);
}
}
RuntimeException的主要子类有:
-
ArithmeticException–算数异常
-
ArrayIndexOutOfBoundsException–数组下标越界异常
-
NullPointerException–空指针异常
-
ClassCastException–类型转换异常
-
NumberFormatException–数字格式异常
下面通过代码来对异常做出解释:
为了让所有的异常都能正常的显示出来,我这里对每一种异常使用多线程来打印输出:
package com.sinsy.exception;
public class ExceptionTest {
public static void main(String[] args) throws Exception {
// 非检测性异常 : Exception in thread "main" java.lang.ArithmeticException: / by zero jvm无法检测
// System.out.println(5 / 0);
//检测型异常
//Thread.sleep(3000);
exceptionTest();
}
public static void exceptionTest(){
new Thread(new Runnable() {
@Override
public void run() {
//第一种 数组下标越界异常
int arr[] = new int[5];
System.out.println(arr[5]);
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
//算数异常
int a = 1;
int b = 0;
System.out.println(a/b);
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
//空指针异常
String abc = null;
System.out.println(abc.length());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
//类型转换异常
Exception exception = new Exception();
InterruptedException interruptedException =(InterruptedException) exception;
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
//数字格式异常
String ad = "1234a";
System.out.println(Integer.parseInt(ad));
}
}).start();
}
}
运行之后的结果为:很清晰的看见,每一个线程代表着一种异常。
通过编码对各种异常进行处理之后,结果为:
/**
* 测试各种异常转换机制的修复
*/
public static void exceptionModifyTest(){
new Thread(() -> {
//第一种 数组下标越界异常
int arr[] = new int[5];
if (arr.length>5){
System.out.println(arr[5]);
}
System.out.println("下标越界异常已经规避");
}).start();
new Thread(new Runnable() {
@Override
public void run() {
//算数异常
int a = 1;
int b = 0;
if (0!=b){
System.out.println(a/b);
}
System.out.println("算术异常已经规避");
}
}).start();
new Thread(() -> {
//空指针异常
String abc = null;
if (null!=abc) {
System.out.println(abc.length());
}
System.out.println("空指针异常已经规避");
}).start();
new Thread(() -> {
//类型转换异常
Exception exception = new Exception();
if (exception instanceof InterruptedException) {
InterruptedException interruptedException = (InterruptedException) exception;
}
System.out.println("类型转换异常已经规避");
}).start();
new Thread(() -> {
//数字格式异常
String ad = "1234a";
//字符串类型的数据做筛选一般选择正则表达式做第一选择
if (ad.matches("\\\\d+")){
System.out.println(Integer.parseInt(ad));
}
System.out.println("数字格式异常已避免");
}).start();
}
运行结果为:
异常的避免
异常的避免其实上文已经凸显出来了结局方案,就是在可能出现异常的地方进行if判断处理,提前预判异常,然后对可能出现的异常跳过处理。
异常的避免(使用If-else)可能会导致大量的代码冗余,导致代码的沉积度过大,变得臃肿,可读性比较差。
异常的捕获
异常的捕获使用try{}catch{}来进行捕获
捕获不了的我们对其进行抛出
异常的抛出(异常的转移)
异常抛出抛给谁呢?当然是异常的捕获者。
异常抛出的时候需要注意一点,抛出的异常之间有父子类继承关系的,如果跑的异常包含多个,那么我们可以选择抛出异常最大的父类,直接可以抛出所有异常,这样节省了代码,但同时也对代码的可读性造成了一定的影响,让修改者无法直接得知此代码在使用过程中会出现什么样的异常。
基本概念
在某些特殊情况下有些异常不能处理或者不便于处理时,就可以将该异常转秘给该方法的调用者,这种方法就叫异常的抛出,本质上其实是异常的转移。方法执行时出现异常,则底层生成一个异常类对象抛出,此时异常代码后续的代码就不再执行。
语法格式
访问权限返回值类型方法名称(形参列表) throws异常类型1,异常类型2…{方法体;}如︰
public void show() throws lOExceptiont
方法重写的原则
a.要求方法名相同、参数列表相同以及返回值类型相同,从jdk1.5开始支持返回子类类型;b.要求方法的访问权限不能变小,可以相同或者变大;
c.要求方法不能抛出更大的异常;
注意
子类重写的方法不能抛出更大的异常、不能抛出平级不一样的异常,但可以抛出一样的异常、更小的异常以及不抛出异常。一定注意!!!!!!!!!!若父类中被重写的方法没有抛出异常时,则子类中重写的方法只能进行异常的捕获。不建议在Main方法中抛出异常,JVM需要执行的任务有很多,如果此时将大量的异常工作交给他来做,那么会影响JVMd额执行效率
异常的常规处理
对于常见的异常,通常对不同类型的异常有着不同的处理方法.
(1)如果父类方法抛出了异常,子类不能抛出比父类更大的异常,只能抛出与父类所抛异常类子类的异常,特殊情况可不抛出任何异常。
- 父类抛出InterruptedException与IOException,子类只抛出InterruptedException,不抛出IOException
/**
* 父类
*/
package com.sinsy.test;
import java.io.FileInputStream;
import java.io.IOException;
public class ThrowExceptionFather {
public void show() throws IOException, InterruptedException {
Thread.sleep(100);
System.out.println("hehehe");
FileInputStream fileInputStream =new FileInputStream("");
fileInputStream.close();
}
}
/**
* 子类
*/
package com.sinsy.test;
public class ThrowExceptionTest extends ThrowExceptionFather{
@Override
public void show() throws InterruptedException {
Thread.sleep(20);
}
}
- 父类的原始方法只抛出IOExeption,而子类在重写该方法的时候抛出父类中没有的异常,并且该异常与父类所抛异常并非同属某一类的子类,也就是说子类所抛出的异常与父类所抛出的异常并不是同一级。
/**
* 父类
*/
package com.sinsy.test;
import java.io.FileInputStream;
import java.io.IOException;
public class ThrowExceptionFather {
public void show() throws IOException {
FileInputStream fileInputStream =new FileInputStream("");
fileInputStream.close();
}
}
/**
* 子类
*/
package com.sinsy.test;
public class ThrowExceptionTest extends ThrowExceptionFather{
@Override
public void show() throws InterruptedException {
Thread.sleep(20);
}
}
此时代码报错,证明子类无法抛出与父类非同级关系的异常:
- 父类只抛出一个异常,子类抛出与父类所抛异常同级但不同的异常:如下代码:子类抛出了一个与父类方法中同级别的异常但父类并未抛出该异常,此时代码报错:证明子类不可抛出与父类同级别的异常:(ClassNotLoadedException与IOException属于同一级别)
/**
* 父类
*/
package com.sinsy.test;
import com.sun.jdi.ClassNotLoadedException;
import java.io.FileInputStream;
import java.io.IOException;
public class ThrowExceptionFather {
public void show() throws IOException {
FileInputStream fileInputStream =new FileInputStream("");
fileInputStream.close();
}
}
/**
* 子类
*/
package com.sinsy.test;
import com.sun.jdi.ClassNotLoadedException;
public class ThrowExceptionTest extends ThrowExceptionFather{
@Override
public void show() throws ClassNotLoadedException {
}
}
-
但是子类可以抛出一个是父类所抛异常类的子类的异常:
通过对比IOException的简单继承关系我们可以选择一个IOException的子类,让继承了ThrowExceptionFather的子类在方法重写的时候去抛出该异常,如下代码:
package com.sinsy.test;
import com.sun.jdi.ClassNotLoadedException;
import java.nio.file.FileSystemException;
public class ThrowExceptionTest extends ThrowExceptionFather{
@Override
public void show() throws FileSystemException {
}
}
此时未报错:
证明,子类可以抛出父类所抛异常类子类的异常。
因此,总结一句话就是:子类继承父类后,在重写父类方法的时候,如果父类中原有方法抛出了异常,那么子类不能抛出比父类所抛异常大的异常,只能抛出属于父类所抛异常类的子类的异常。
(2)若一个方法内部以递进式的方法分别调用了好几个其他的方法,则建议这些方法将异常逐层抛出,然后再统一处理。如下代码所示。
package com.sinsy.test;
public class DigonThrow {
public static void yu() {
try {
yu1();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void yu1() throws Exception {
yu2();
}
public static void yu2() throws Exception {
yu3();
}
public static void yu3() throws Exception {
yu4();
}
public static void yu4() throws Exception {
yu5();
}
public static void yu5()throws Exception{
System.out.println(1);
}
public static void main(String[] args){
yu();
}
}
将异常抛至最顶层的时候,此时做最后的 try-catch 处理。
(3)如果父类中被重写的方法没有抛出异常的时候,则子类中重写的方法只能进行异常的捕获处理。
//父类代码
package com.sinsy.test;
public class ThrowExceptionFather {
public void show(){
}
}
//子类代码
package com.sinsy.test;
import java.io.FileInputStream;
import java.io.IOException;
public class ThrowExceptionTest extends ThrowExceptionFather{
@Override
public void show() {
FileInputStream fileInputStream =new FileInputStream("");
fileInputStream.close();
}
}
若此时子类抛出异常,则会出现以下警示:
Method ‘show’ is inherited.Do you want to add exceptions to method signatures in the whole method hierarchy?意思就是在说,方法‘Show’是继承的。要在整个方法层次结构中向方法签名添加异常吗?如果添加了之后,则父类中过就会抛出此异常!
自定义异常
基本概念
当需要在程序中表达年龄不合理的情况时,而Java官方又没有提供这种针对性的异常,此时就需要程序员自定义异常加以描述。
实现流程
a.自定义xxxException异常类继承Exception类或者其子类。
b.提供两个版本的构造方法,一个是在·无参构造方法,另外一个是字符电作为参数的构造方法。
异常的产生
throw new异常类型(实参);如∶throw new AgeException("年龄不合理!!! ");Java采用的异常处理机制,是将异常处理的程序代码集中在一起,与正常的程序代码分开**,使得程序简洁、优雅,并易于维护。**
自定义Exception异常类
要想实现自定义Exception异常类,我们需要写一个Exception的异常类去继承Exception类,实现无参构造方法以及有参数构造方法。
package com.sinsy.exception;
public class AgeException extends Exception{
static final long serialVersionUID = -3387516993124229948L;
/**
* 无参数构造方法
*/
public AgeException() {
}
/**
* 有参构造方法
* @param message
*/
public AgeException(String message) {
super(message);
}
}
- 自定义Exception异常类的使用:
(1)首先确定应用场景为:用户个人信息的封装中,对成员属性的封装的时候对成员属性的设置出现异常:
package com.sinsy.bean;
import com.sinsy.exception.AgeException;
import java.util.Objects;
public class Person {
private String name;
private String sex;
private int age;
public Person() {
}
public Person(String name, String sex, int age) {
this.name = name;
this.sex = sex;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public Java的Exception异常机制