返回
Featured image of post 枚举类

枚举类

介绍如何使用枚举类

目录

概述

在使用计算机语言进行项目开发的过程中,即使程序员把代码写得尽善尽美,在系统的运行过程中仍然会遇到一些问题,因为很多问题不是靠代码能够避免的,比如:客户输入数据的格式问题读取文件是否存在网络是否始终保持通畅等等。

异常 :指的是程序在执行过程中,出现的非正常情况。如果不处理异常最终会导致 JVM 的非正常停止。

异常指的并不是语法错误逻辑错误

语法错误,编译不会通过,更不会产生字节码文件,程序根本就不能运行。

代码逻辑错误,只是没有得到想要的结果,例如:求 a 与 b 的和,你写成了 a - b

异常的抛出机制

Java 中把不同的异常用不同的类表示,一旦发生某种异常,就创建该异常类型的对象,并且抛出(throw)。然后程序员可以捕获 (catch)到这个异常对象并处理;如果没有捕获(catch)这个异常对象,那么这个异常对象将会导致程序终止。

运行下面的程序,程序会产生一个数组角标越界异常 ArrayIndexOfBoundsException

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class ArrayTools {
    // 对给定的数组通过给定的角标获取元素。
    public static int getElement(int[] arr, int index) {
        int element = arr[index];
        return element;
    }
}

class ExceptionDemo {
    public static void main(String[] args) {
        int[] arr = { 34, 12, 67 };
        intnum = ArrayTools.getElement(arr, 4)
        System.out.println("num = " + num);
        System.out.println(" over");
    }
}

上述程序执行过程图解:

如何对待异常

对于程序出现的异常,一般有两种解决方法:

一是遇到错误就终止程序的运行。

另一种方法是程序员在编写程序时,就充分考虑到各种可能发生的异常和错误,极力预防和避免。实在无法避免的,要编写相应的代码进行异常的检测、以及异常的处理,保证代码的健壮性

Java 的异常体系

Throwable

java.lang.Throwable 类是Java程序执行过程中发生的异常事件对应的类的根父类。

Throwable中的常用方法:

  • public String getMessage() :获取发生异常的原因。

  • public void printStackTrace() :打印异常的详细信息。

    包含了异常的类型、异常的原因、异常出现的位置,由于 printStackTrace 打印的信息中已经包括了 getMessage 中的信息,所以在开发和调试阶段都较推荐使用 printStackTrace

Error 和 Exception

Throwable 可分为两大类:ErrorException 。分别对应着 java.lang.Errorjava.lang.Exception 两个类。

Error: JVM 无法解决的严重问题。如:JVM 系统内部错误、资源耗尽等严重情况。一般不编写针对性的代码进行处理。例如:

  • StackOverflowError(栈内存溢出)
  • OutOfMemoryError(堆内存溢出,简称OOM)。

Exception: 其它因编程错误或偶然的外在因素导致的一般性问题,需要使用针对性的代码进行处理,使程序继续运行。否则一旦发生异常,程序就会终止。例如:

  • 空指针访问
  • 试图读取不存在的文件
  • 网络连接中断
  • 数组角标越界
1
2
3
4
5
6
public class Test3 {
    public static void main(String[] args) {
        int[] array = new int[10];
        System.out.println(array[10]);
    }
}

编译时异常和运行时异常

Java 程序的执行分为编译时过程和运行时过程。有的错误只有在运行时才会发生,比如:除数为0,数组下标越界等。

因此,根据异常可能出现的阶段,可以将异常分为:

  • 编译时期异常(即 checked 异常、受检异常):在代码编译阶段,编译器就能明确 警示 当前代码可能发生(不是一定发生)某个异常,并 明确督促 程序员提前编写处理它的代码。如果程序员没有编写对应的异常处理代码,则编译器就会直接判定编译失败,从而不能生成字节码文件。通常,这类异常的发生不是由程序员的代码引起的,或者不是靠加简单判断就可以避免的,例如:FileNotFoundException(文件找不到异常)、IOException(输入输出异常)、ClassNotFoundException(类找不到异常)。
  • 运行时期异常(即 runtime 异常、unchecked 异常、非受检异常):在代码编译阶段,编译器完全不做任何检查,无论该异常是否会发生,编译器都不给出任何提示。只有等代码运行起来并确实发生了某个异常,它才能被发现。通常,这类异常是由程序员的代码编写不当引起的,只要稍加判断,或者细心检查就可以避免。

java.lang.RuntimeException 类及它的子类都是运行时异常。比如:ArrayIndexOutOfBoundsException 数组下标越界异常, ClassCastException 类型转换异常、NullPointerException 空指针异常。

常见错误和异常

Error

最常见的就是 VirtualMachineError ,它有两个经典的子类:StackOverflowErrorOutOfMemoryError

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// StackOverflowError
public class StackOverflowErrorTest {
    public static void main(String[] args) {
        main(args); // 无限递归 main 方法
    }
}

// OutOfMemoryError
public class OutOfMemoryErrorTest {
    public static void main(String[] args) {
        while (true) {
            long[] longs = new long[1024 * 1024 * 1024]; // 每一次都申请了 8G 内存
        }
    }
}

Exception

编译时异常

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import org.junit.Test;

import java.io.File;
import java.io.FileInputStream;

public class CheckedExceptionTest {
    @Test
    public void test1() {
        Class loadClass = Class.forName("java.lang.Exception"); // ClassNotFoundException
    }

    public void test2() {
        File file = new File("D:\\Java从入门到提桶跑路.txt");
        FileInputStream fis = new FileInputStream(file); // FileNotFoundException
        int input = fis.read(); // IOException
        while (input != -1) {
            System.out.print((char) input);
            input = fis.read(); // IOException
        }
        
        fis.close(); // IOException
    }
}

运行时异常

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import org.junit.Test;

import java.util.Scanner;

public class RuntimeExceptionTest {
    @Test
    public void test1(){
        int[][] arr = new int[3][];
        System.out.println(arr[0].length); // NullPointerException
    }

    @Test
    public void test2(){
        Object obj = 15; // 这里执行了自动装箱
        String str = (String) obj; // ClassCastException
    }

    @Test
    public void test3(){
        int[] arr = new int[5];
        for (int i = 1; i <= 5; i++) {
            System.out.println(arr[i]); // ArrayIndexOutOfBoundsException
        }
    }

    @Test
    public void test4(){
        Scanner input = new Scanner(System.in);
        System.out.print("请输入一个整数:");
        int num = input.nextInt(); // 这里输入了一个非整数 InputMismatchException
        input.close();
    }

    @Test
    public void test5(){
        int a = 1;
        int b = 0;
        System.out.println(a / b); // ArithmeticException
    }
}

异常的处理

概述

在编写程序时,经常要在可能出现错误的地方加上检测的代码,如进行 x / y 运算时,要检测用户输入的是数值还是字符、数据是否为空、分母是否为 0 等。过多的 if-else 分支会导致程序的代码加长、臃肿,可读性差,程序员需要花很大的精力 “堵漏洞” 。因此采用异常处理机制来简化操作。

Java 采用的异常处理机制,是将需要异常处理的程序代码集中在一起 ,与正常的程序代码分开,使得程序简洁、优雅,并易于维护。

Java 异常处理有两种方式:

  • try-catch-finally
  • throws + 异常类型

捕获异常(try-catch-finally)

Java 提供了异常处理的抓抛模型

  • Java 程序的执行过程中如果出现异常就会生成一个异常类对象,该异常对象将被提交给 Java 运行时系统,这个过程称为抛出(throw)异常
  • 如果一个方法内抛出异常,该异常对象会被抛给方法调用者来处理。如果异常没有被调用者处理,则异常会继续被抛给这个调用方法的上层方法。这个过程将一直继续下去,直到异常被处理。这一过程称为捕获(catch)异常。如果一个异常回到 main() 方法,并且 main() 也不处理,则程序会终止运行。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
try {
	......	// 可能产生异常的代码
}
catch( 异常类型1 e ) {
	......	// 当产生异常类型1异常时的处置措施
}
catch( 异常类型2 e ) {
	...... 	// 当产生异常类型2异常时的处置措施
}  
finally {
	...... // 无论是否发生异常,都无条件执行的语句
} 

try - catch 模型

当某段代码可能发生异常,不管这个异常是编译时异常(受检异常)还是运行时异常(非受检异常),我们都可以使用 try 块将它包围起来,并在 try 块下面编写 catch 分支尝试捕获对应的异常对象。

捕获异常的第一步是用 try{ … }语句块 选定捕获异常的范围,将可能出现异常的业务逻辑代码放在 try 语句块中。

catch 分支分为两个部分,catch() 中编写异常类型和异常参数名,{} 中编写如果发生了这个异常要怎么做的处理代码。

  • 每个 try 语句块可以伴随一个或多个 catch 语句,用于处理可能产生的不同类型的异常对象。

  • 如果明确知道产生的是何种异常,可以用该异常类作为 catch 的参数类型,也可以用其父类作为 catch 的参数类型。

    • 比如:可以用 ArithmeticException 类作为参数的地方,就可以用 RuntimeException 类作为参数,或者用所有异常的父类Exception 类作为参数。但不能是与 ArithmeticException 类无关的异常类型,如 NullPointerException 或自定义的ArithmeticException 的子类,如果是的话 catch 中的语句将不会执行。
  • 如果有多个 catch 分支,并且多个异常类型有父子类关系,必须保证小的子异常类型在上,大的父异常类型在下,否则会编译失败。

  • catch 中常用异常处理的方式:① public String getMessage()public void printStackTrace()

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public void test() {
    try {
        String str = "Hello Java Exception";
        str = null;
        System.out.println(str.charAt(0));
    } catch (NullPointerException e) {
        // 异常的处理方式1
        System.out.println("出现了空指针异常");
    } catch (ClassCastException e) {
        // 异常的处理方式2
        System.out.println("出现了类型转换的异常");
    } catch (RuntimeException e) { // 因为运行时异常是上面两个异常的父类 所以要排在它们后面
        // 异常的处理方式3
        System.out.println("出现了运行时异常");
    }
    // 此处的代码,在异常被处理了以后,是可以正常执行的
    System.out.println("hello");
}

如果在程序运行时,try 块中的代码没有发生异常,那么 catch 所有的分支都不执行。

如果在程序运行时,try 块中的代码发生了异常,根据异常对象的类型,将从上到下选择第一个匹配的 catch 分支执行。此时 try 中发生异常的语句下面的代码将不执行,而整个 try…catch 之后的代码可以继续运行。

如果在程序运行时,try 块中的代码发生了异常,但是所有 catch 分支都无法匹配(捕获)这个异常,那么 JVM 将会终止当前方法的执行,并把异常对象“抛”给调用者。如果调用者不进行处理,程序就会终止运行。

finally 的使用

因为异常会引发程序跳转,从而会导致有些语句执行不到。而程序中有一些特定的代码无论异常是否发生都一定需要执行。例如,数据库连接、输入流输出流、Socket 连接、Lock 锁的关闭等,这样的代码通常就会放到 finally 块中。所以,我们通常将一定要被执行的代码写在 finally 中。

不论在 try 代码块中是否发生了异常、try 代码块中是否有 return、catch 语句是否执行、catch 中语句是否有异常、catch 语句中是否有 return,finally块中的代码都会被执行。唯一的例外就是使用 System.exit() 来强制终止当前正在运行的 Java 虚拟机,这样的话 finally 中的代码就没有机会执行了。

finally 语句和 catch 语句是可选的,不一定要跟在 try 的背后。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public void test() {
    try {} 
    catch (Exception e) {} 
    finally {}
    
    try {}
    catch (Exception e) {}
    
    try {}
    finally {}
}

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
// finally 的使用

// 1.确保资源被关闭
import org.junit.Test;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.InputMismatchException;
import java.util.Scanner;

public class FinallyTest1 {
    @Test
    public void test1() {
        Scanner input = new Scanner(System.in);
        try {
            System.out.print("请输入第一个整数:");
            int a = input.nextInt();
            System.out.print("请输入第二个整数:");
            int b = input.nextInt();
            int result = a / b;
            System.out.println(a + " / " + b + " = " + result);
        } catch (InputMismatchException e) {
            System.out.println("数字格式不正确,请输入两个整数");
        } catch (ArithmeticException e) {
            System.out.println("第二个整数不能为0");
        } finally {
            System.out.println("程序结束,释放资源");
            input.close();
        }
    }

    @Test
    public void test2() {
        FileInputStream fis = null;
        try {
            File file = new File("D:\\Java从入门到提桶跑路.txt");
            fis = new FileInputStream(file); // FileNotFoundException
            int b = fis.read(); // IOException
            while (b != -1) {
                System.out.print((char) b);
                b = fis.read(); //IOException
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fis != null)
                    fis.close(); // IOException
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

// 2.在 try 中返回
class FinallyTest2 {
    public static void main(String[] args) {
        int result = test("12");
        System.out.println(result);
        
        /*
            console:
                test结束
                1
         */
    }

    public static int test(String str) {
        try {
            Integer.parseInt(str);
            return 1;
        } catch (NumberFormatException e) {
            return -1;
        } finally {
            System.out.println("test结束");
        }
    }
}

// 3.在 catch 中返回
class FinallyTest3 {
    public static void main(String[] args) {
        int result = test("a");
        System.out.println(result);
        
        /*
            console:
                test结束
                -1
         */
    }

    public static int test(String str) {
        try {
            Integer.parseInt(str);
            return 1;
        } catch (NumberFormatException e) {
            return -1;
        } finally {
            System.out.println("test结束");
        }
    }
}

// 4.在 finally 中返回
class FinallyTest4 {
    public static void main(String[] args) {
        int result = test("a");
        System.out.println(result);
        /*
            console:
                test结束
                0
         */
    }

    public static int test(String str) {
        try {
            Integer.parseInt(str);
            return 1;
        } catch (NumberFormatException e) {
            return -1;
        } finally {
            System.out.println("test结束");
            return 0;
        }
    }
}

如何对待编译期异常和运行时异常

编译期异常:

如果抛出的异常是 IOException 等编译时异常就必须要捕获,否则就会导致编译错误。也就是说,我们必须处理编译时异常,将异常进行捕捉,转化为运行时异常

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public class Test {
    public static void main(String[] args) {
        try {
            Class loadClass = Class.forName("java.lang.Object");
        }
        catch (ClassNotFoundException e) {
//            throw new RuntimeException("ClassNotFoundException"); OK 将编译时异常转化为运行时异常
            e.printStackTrace(); // OK 直接处理异常
        }
    }
}

运行时异常:

运行时异常的特点是:即使没有使用 try 和 catch 捕获,编译也可以通过(但运行时会发生异常使得程序运行终止)。所以对于这类异常,可以不作处理,因为这类异常很普遍,若全处理可能会对程序的可读性和运行效率产生影响

声明抛出异常类型(throws)

如果在编写方法体的代码时,某句代码可能发生某个编译时异常,此时不处理这个异常编译不能通过,但是在当前方法体中可能不适合处理或无法给出合理的处理方式,则此方法应显示地声明抛出异常,表明该方法将不对这些异常进行处理,而由该方法的调用者负责处理

throws的使用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
[修饰符] <返回值类型> <方法名(形参列表)> throws 异常类型1, 异常类型2 ... {
    // ...
} 

// 抛出编译时异常
public class ThrowsCheckedExceptionTest {
    public static void main(String[] args) {
        System.out.println("上课.....");
        try {
            afterClass(); // 换到这里处理异常
        } catch (InterruptedException e) {
            e.printStackTrace();
            System.out.println("准备提前上课");
        }
        System.out.println("上课.....");
    }

    public static void afterClass() throws InterruptedException {
        for (int i = 10; i >= 1; i--) {
            // sleep()有可能会抛出 InterruptedException 这个编译期异常
            Thread.sleep(1000); // 本来应该在这里通过 try-catch 来处理异常
            System.out.println("距离上课还有:" + i + "分钟");
        }
    }
}

// 抛出运行时异常
import java.util.InputMismatchException;
import java.util.Scanner;

public class ThrowsCheckedExceptionTest {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        try {
            System.out.print("请输入第一个整数:");
            int a = input.nextInt();
            System.out.print("请输入第二个整数:");
            int b = input.nextInt();
            int result = divide(a, b);
            System.out.println(a + "/" + b + "=" + result);
        } catch (ArithmeticException | InputMismatchException e) {
            e.printStackTrace();
        } finally {
            input.close();
        }
    }

    public static int divide(int a, int b) throws ArithmeticException {
        return a / b;
    }
}

throws 后面也可以跟运行时异常类型,只是运行时异常类型写或不写对于编译器和程序执行来说几乎没有任何区别。如果写了,唯一的区别就是调用者调用该方法后,使用 try…catch 结构时,IDEA可以获得更多的信息,提示用户需要添加哪种catch分支。 所以虽然写不写无所谓,但我个人还是推荐写一下的。

throws 后面的异常类型可以是方法中产生的异常类型,也可以是它的父类。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public class ThrowsCheckedExceptionTest {
    public static void main(String[] args) {
        try {
            method();
        } catch (RuntimeException e) {
            e.printStackTrace();
        }
    }

    public static void method() throws RuntimeException {
        int i = 10 / 0; // ArithmeticException
        int[] array = new int[10];
        array[100] = 10; // ArrayIndexOutOfBoundsException
    }
}

ArithmeticExceptionArrayIndexOutOfBoundsException 都是 RuntimeException 的子类,所以通过抛出 RuntimeException 来将这两个异常抛出。

重写方法时对 throws 的要求

方法重写时对于方法签名是有严格要求的:

  • 方法名必须相同
  • 形参列表必须相同
  • 返回值类型:
    • 基本数据类型和void:必须相同
    • 引用数据类型:相同或者是被重写方法返回值的子类
  • 权限修饰符:必须大于等于被重写方法的权限,并且要求父类中的被重写方法在子类中是可见的(非private)
  • 不能是static,final修饰的方法

附表:

权限大小:public > protected > default > private

修饰符 本类内部 本包内 其他包的子类 其他包非子类
private × × ×
缺省(default) × ×
protected ×
public

此外,对于throws异常列表要求:

  • 如果父类被重写方法的方法签名后面没有 “ throws 编译时异常类型”,那么重写方法时,方法签名后面也不能出现“ throws 编译时异常类型”。
  • 如果父类被重写方法的方法签名后面有 “ throws 编译时异常类型”,那么重写方法时,throws 的编译时异常类型必须是被重写方法 throws 的编译时异常类型或其子类,或者不 throws 编译时异常(如果重写方法中有编译时异常的话,就用 try-catch 处理掉)。
  • 方法重写,对于“ throws 运行时异常类型”没有要求。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import java.io.IOException;

class Father {
    public void method() throws Exception {
        System.out.println("Father.method");
    }
}

class Son extends Father {
    @Override
    public void method() throws IOException, ClassCastException {
        System.out.println("Son.method");
    }
}

两种异常处理方式的选择

前提:对异常使用下面相应的处理方式,此时的异常主要指的是编译时异常,比如运行时异常就不需要满足第二条规则。

  • 如果程序代码中涉及到资源的调用(流、数据库连接、网络连接等),则必须考虑使用 try-catch-finally 来处理,保证不出现内存泄漏
  • 如果父类被重写的方法没有 throws 异常类型,而子类重写的方法中出现异常,则只能考虑使用 try-catch-finally 进行处理,不能 throws。
  • 开发中,如果方法 a 中依次调用了方法 b, c, d 等方法,且方法 b, c, d 之间是递进关系(比如 c 的运行依赖 b 的结果,d 的运行依赖于 c 的结果)。此时,如果方法 b, c, d 中有异常,我们通常选择使用 throws ,而方法 a 中通常选择使用 try-catch-finally
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 运行时异常不满足第二条规则
class Father {
    public void method() {}
}

class Son extends Father {
    @Override
    public void method() throws RuntimeException {
        throw new RuntimeException();
    }
}

手动抛出异常对象(throw)

Java 中异常对象的生成有两种方式:

  • 由虚拟机自动生成:程序运行过程中,虚拟机检测到程序发生了问题,那么针对当前代码,就会在后台自动创建一个对应异常类的实例对象并抛出。如果深究源码就会发现自动生成的异常对象也是 api 开发者手动抛出的对象,只是相对于我们这些 api 使用者来说是自动的而已。
  • 由开发人员手动创建new 异常类型([实参列表]); 如果创建好的异常对象不抛出(throw)就不会对程序有任何影响,就像创建一个普通对象一样。但是一旦 throw 抛出,就会对程序运行产生影响了。
1
throw new 异常类名([参数列表]);

throw 语句抛出的异常对象,和 JVM 自动创建和抛出的异常对象一样:

  • 如果是编译时异常类型的对象,同样需要使用 throws 或者 try...catch 处理,否则编译不通过。
  • 如果是运行时异常类型的对象,编译器不做约束。

可以抛出的异常必须是 Throwable 或其子类的实例。下面的语句在编译时将会产生语法错误:

1
throw new String("want to throw");

注意点:

无论是编译时异常类型的对象,还是运行时异常类型的对象,如果没有被 try..catch 合理的处理,都会导致程序崩溃。

throw 语句会导致程序执行流程被改变,throw 语句是明确抛出一个异常对象,因此 throw 下面的代码将不会执行

如果当前方法没有 try...catch 处理这个抛出的异常对象,throw 语句就会代替 return 语句提前终止当前方法的执行,并返回一个异常对象给调用者。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class ThrowTest {
    public static void main(String[] args) {
        try {
            System.out.println(max(4, 2, 31, 1));
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
            System.out.println(max(4));
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
            System.out.println(max());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static int max(int... nums) {
        if (nums == null || nums.length == 0) {
            throw new IllegalArgumentException("没有传入任何整数,无法获取最大值");
        }
        int max = nums[0];
        for (int i = 1; i < nums.length; i++) {
            if (nums[i] > max) {
                max = nums[i];
            }
        }
        return max;
    }
}

自定义异常

为什么要自定义异常

Java 中不同的异常类,分别表示着某一种具体的异常情况。那么在开发中总是有些异常情况是核心类库中没有定义好的,此时我们需要根据自己业务的异常情况来定义异常类。例如年龄负数问题,考试成绩负数问题,某员工已在团队中等。

如何自定义异常

  1. 继承一个异常类型
    • 自定义一个编译时异常类型:自定义类 extends java.lang.Exception
    • 自定义一个运行时异常类型:自定义类 extends java.lang.RuntimeException
  2. 提供构造器
    • 建议提供至少两个构造器:一个是无参构造,一个是参数列表为(String message)的构造器。
  3. 提供 serialVersionUID
    • 这个 uid 是供对象的序列化和反序列化用的

如果还是不清楚如何自定义异常可以参考 JDK 中的 NullPointerException 是如何继承 RuntimeException 的,当然这只是一个例子,你可以参考异常体系中的任意一对继承关系。

注意事项

  1. 自定义的异常只能通过 throw 抛出。抛出后可以由 try-catch 处理,也可以借助 throws 甩锅给调用者。
  2. 自定义异常最重要的是异常类的名字和 message 属性。当异常出现时,可以根据名字判断异常类型。比如:TeamException("成员已满,无法添加"); TeamException("该员工已是某团队成员"); 所以自定义异常类给手动抛出的异常提供 message时一定要做到见名知意,简洁明白。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import java.io.Serial;

class MyException extends Exception {
    @Serial
    private static final long serialVersionUID = 114514415411L;

    public MyException() {
    }

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

}

class MyExceptionTest {
    public void register(int num) throws MyException {
        if (num < 0)
            throw new MyException("人数为负值,不合理");
        else
            System.out.println("登记人数" + num);
    }

    public void manager() {
        try {
            register(100);
        } catch (MyException e) {
            System.out.print("登记失败,出错种类");
        }
        System.out.print("本次登记操作结束");
    }

    public static void main(String[] args) {
        MyExceptionTest t = new MyExceptionTest();
        t.manager();
    }
}

小结

异常处理5个关键字:

类比:上游排污,下游治污

小哲理:

世界上最遥远的距离,是我在 if 里你在 else 里,似乎一直相伴又永远分离;

世界上最痴心的等待,是我当 case 你是 switch,或许永远都选不上自己;

世界上最真情的相依,是你在 try 我在 catch 。无论你发神马脾气,我都默默承受,静静处理。到那时,再来期待我们的 finally

参考引用

尚硅谷Java零基础全套视频教程(宋红康主讲,java入门自学必备) - 异常处理

Licensed under CC BY-NC-SA 4.0
鹅掌草の森已经茁壮生长了
发表了8篇文章 · 总计50.10k字 · 共 0 次浏览
记录任何我想记录的事情。若无特殊说明,则本博客文章均为原创,复制转载请保留出处。
使用 Hugo 构建
主题 StackJimmy 设计