一、异常
1.1 异常的简介
异常,是对程序在运行过程中,遇到的种种的不正常的情况的描述。异常,在Java中,使用 Exception类来描述。如果程序遇到了未经处理的异常,将会导致程序无法编译或无法继续运行。
1.2 异常的继承体系
在Java中,使用类 Throwable 来描述所有的不正常的情况。 Throwable有两个子类,Error 和 Exception 。
1. Error 用来描述发生在JVM级别的错误信息,这些错误,无法被处理。
2. Exception 用来描述程序在编译或者运行的过程中遇到的异常信息,这些异常一旦处理了,对程序的编译和运行是没有影响的。
继承体系的层次结构,参考下图:
常见的运行时异常还有:
NoSuchElementException (找不到元素异常); UnsupportedOperationException (不支持请求异常)等等。
1.3 异常的分类
普通的异常发生,将会导致程序无法进行正常的编译。这种异常,称为None-Runtime Exception,非运行时异常,或者称为编译时异常,也有人称之为检查性异常,原因在于该种异常,都是javac编译器能直接检查到的异常。
编译器javac.exe 在将源文件(*.java)编译成字节码文件(*.class)时,
会主动检查代码的语法格式,如果不正确,就不进行编译工作。
如果正确了,才会执行编译工作。
在Exception的子类RuntimeException中,对异常进行了自动的处理,使其可以正常的编译运行。但是一旦出现未经处理的异常,依然会导致程序无法继续向下执行。这样的异常称为Runtime Exception,运行时异常。这类异常,通常编译器是不做任何检查的,所以也叫非检查性异常。
总之,异常可以分为 运行时异常(非检查性异常)和 编译时异常(检查性异常)。
二、异常的处理
异常如果不处理,即JVM帮忙处理,简单粗暴,就是终止程序的运行。但是在实际生产环境中,不能因为异常就终止程序,否则客户体验度会非常差。
2.1 异常处理的语法
try{
// 将可能会出现异常的代码放到这里执行
// 一旦try中的代码出现了异常,则从出现异常的位置开始,到try的大括号的扩回,这一段代码就不执行了。
}catch(需要捕获的异常的类型 标识符) {
// 如果try中的代码出现了异常,并且异常对象的类型和捕获的类型一致
// 这里的代码将就会执行
}
try-catch不会终止程序的后续代码的执行。
注意:catch里要写处理语句!!!
public static void main(String[] args) {
try {
int[] nums = {1, 2, 3, 4, 5};
for (int i = 0; i <= nums.length; i++) {
int num = nums[i];
System.out.println("num=" + num);
}
}catch (Exception e) {
e.printStackTrace();
}
System.out.println("main方法结束");
}
}
num=1
num=2
num=3
num=4
num=5
main方法结束
java.lang.ArrayIndexOutOfBoundsException: 5
at com.se.day01.aException.Exception01.main(Exception01.java:31)
2.2 多种异常的处理
1. 当代码片段中可能出现多种不同类型的异常时,我们每一个异常都想要处理的情况下,使用多catch。
2. 多个异常类型没有继承关系,不用考虑书写顺序。
3. 一旦有继承关系,必须先写子类型。
4. 没有继承关系的异常类型,当处理逻辑一样时,可以写在一个catch中,使用|分开。或者使用这些异常的共同父类型即可,前提条件也是处理逻辑一样。
public class Exception02 {
public static void main(String[] args) {
try {
int[] nums = new int[5];
nums[5] = 100;
String str = null;
int length = str.length();
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("--数组下标越界--");
} catch (NullPointerException e) {
System.out.println("--空指针异常--");
} catch (Exception e) {
System.out.println("--异常--");
}
//简化版本1:没有继承关系的异常类型,可以写在一个catch中。使用|分开。
//前提条件:处理逻辑一样
try {
int[] nums = new int[5];
nums[5] = 100;
String str = null;
int length = str.length();
} catch (ArrayIndexOutOfBoundsException | NullPointerException e) {
e.printStackTrace();
}
//简化版本2:使用这些异常的共同父类型即可 前提条件:处理逻辑一样
try {
int[] nums = new int[5];
nums[5] = 100;
String str = null;
int length = str.length();
} catch (Exception e) {
e.printStackTrace();
}
}
}
2.3 finally模块
1. 位于try或者catch模块后。
2. 无论try里是否发生了异常,都会执行finally模块里的语句。
3. 应用场景:finally模块一般用于做流的关闭,其他资源释放等操作。
public class Exception03 {
public static void main(String[] args) {
String[] names = null;
try{
names = new String[3];
String name = names[1];
int length = name.length();
}catch(Exception e){
e.printStackTrace();
}finally{
names[1] = "zhangsan";
}
System.out.println(Arrays.toString(names));
System.out.println("--main方法结束--");
//finally的应用场景:一般用于流的关闭操作。
InputStream is = null;
try {
is = Exception03.class.getClassLoader().getResourceAsStream("");
BufferedImage image = ImageIO.read(is);
}catch(Exception e){
e.printStackTrace();
}finally{
try {
is.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
java.lang.NullPointerException
at com.se.day01.aException.Exception03.main(Exception03.java:21)
[null, zhangsan, null]
--main方法结束--
2.4 final、finally、finalize 的区别
1)final修饰符(关键字)。被final修饰的类,就意味着不能再派生出新的子类,不能作为父类而被子类继承。因此一个类不能既被abstract声明,又被final声明。将变量或方法声明为final,可以保证他们在使用的过程中不被修改。被声明为final的变量必须在声明时给出变量的初始值,而在以后的引用中只能读取。被final声明的方法也同样只能使用,即不能方法重写。
2)finally是在异常处理时提供finally块来执行任何清除操作。不管有没有异常被抛出、捕获,finally块都会被执行。try块中的内容是在无异常时执行到结束。catch块中的内容,是在try块内容发生catch所声明的异常时,跳转到catch块中执行。finally块则是无论异常是否发生,都会执行finally块的内容,所以在代码逻辑中有需要无论发生什么都必须执行的代码,就可以放在finally块中。
3)finalize是方法名。java技术允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。它是在object类中定义的,因此所有的类都继承了它。子类覆盖finalize()方法以整理系统资源或者被执行其他清理工作。finalize()方法是在垃圾收集器删除对象之前对这个对象调用的。
2.5 finally和return的特点
1. 当try里有return关键字,finally模块没有return关键字。 先执行finally模块代码,然后再执行try里的return关键字。
2. 当try里有return关键字,finally模块也有return关键字。 一定执行并返回的是finally里的return关键字。
三、自定义异常
3.1 为什么要自定义异常
如果系统给我们提供的异常类型,已经不能满足我们的需求了,或者不知道用哪个了。此时就需要进行异常的自定义。
3.2 如何自定义异常
1. 继承Exception 或者继承RuntimeException, 定义两个构造器即可。模拟已经存在的子类异常。
2. 继承Exception的自定义异常,是编译时异常。
3. 继承RuntimeException的自定义异常,是运行时异常。
语法格式如下:
Class 异常名 extends Exception{ //或继承RuntimeException
public 异常名(){
}
public 异常名(String s){
super(s);
}
}
3.3 throw和throws关键字
throw和throws的特点:
1. throw是用在方法里,用于将一个异常对象抛出,自己不处理,抛给调用者,谁调用这个方法,谁就是调用者。
2. throws是用在方法的定义上,表示告诉调用者需要处理的异常类型。
3. throw的如果是编译时异常,必须throws(必须告诉调用者)
throw的如果是RuntimeException,就没有必要throws了。
public class Exception05 {
public static void main(String[] args) {
try {
Person p = new Person("小明", 20);
}catch(Exception e) {
e.printStackTrace();
}
}
}
class Person{
private String name;
private int age;
public Person( String name,int age) throws AgeIllegalException{
this.name = name;
if(age<1||age>120){
throw new AgeIllegalException("年龄不合理,不应该小于1或者大于120");
}
this.age = age;
}
}
class AgeIllegalException extends Exception {
public AgeIllegalException() {
super();
}
public AgeIllegalException(String message) {
super(message);
}
}