接触Java开发这么久,实话实说,我是没有主动抛出过异常的,因为IDEA已经帮我全部做完了。至于说自己写异常抛出,那更是天方夜谭。
我们直接进入正题。
Java的异常捕获机制可以用下面这张图来表示:
最上层一个单独的类叫做Throwable,用以表示所有的异常情况。然后派生出两个子类,一个是Exception,另一个是Error,其中我们只需要关心Exception,因为我们只跟这个类打交道。至于出现了Error,我们除了终止程序运行之外,好像也没有什么好办法。
我们继续说Exception,在Exception中,又可以大致分为两类,RuntimeException和CheckedException,当然,CheckedException又被我省略了,因为这些异常其实是用户导致的,比如说打开一个不存在的文件夹,并不是程序本身的问题,一般而言,Java语言会强制要求捕获这些异常进行处理。而RuntimeException,那就是针对程序本身的错误设计的。
我们主要来讲解非检查类型的异常,我在这里给出一个非检查类型的表格:
异常 | 描述 |
---|---|
ArithmeticException | 当出现异常的运算条件时,抛出此异常。例如,一个整数”除以零”时,抛出此类的一个实例。 |
ArrayIndexOutOfBoundsException | 用非法索引访问数组时抛出的异常。如果索引为负或大于等于数组大小,则该索引为非法索引。 |
ArrayStoreException | 试图将错误类型的对象存储到一个对象数组时抛出的异常。 |
ClassCastException | 当试图将对象强制转换为不是实例的子类时,抛出该异常。 |
IllegalArgumentException | 抛出的异常表明向方法传递了一个不合法或不正确的参数。 |
IllegalMonitorStateException | 抛出的异常表明某一线程已经试图等待对象的监视器,或者试图通知其他正在等待对象的监视器而本身没有指定监视器的线程。 |
IllegalStateException | 在非法或不适当的时间调用方法时产生的信号。换句话说,即 Java 环境或 Java 应用程序没有处于请求操作所要求的适当状态下。 |
IllegalThreadStateException | 线程没有处于请求操作所要求的适当状态时抛出的异常。 |
IndexOutOfBoundsException | 指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。 |
NegativeArraySizeException | 如果应用程序试图创建大小为负的数组,则抛出该异常。 |
NullPointerException | 当应用程序试图在需要对象的地方使用 null 时,抛出该异常 |
NumberFormatException | 当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。 |
SecurityException | 由安全管理器抛出的异常,指示存在安全侵犯。 |
StringIndexOutOfBoundsException | 此异常由 String 方法抛出,指示索引或者为负,或者超出字符串的大小。 |
UnsupportedOperationException | 当不支持请求的操作时,抛出该异常。 |
这些异常都已经被放在了java.lang的包中,所有的程序均可直接使用。
我们直接使用try-catch语句捕获异常。在这里我要再加上一个finally。
try {
//可能抛出异常的语句
}catch (NumberFormatException e){
//捕获异常后执行的语句
}
catch (IndexOutOfBoundsException e){
//捕获异常后执行的语句
}
finally {
//一定会执行的语句
}
以上这段代码就是一个try-catch-finally的基本架构,try语句后面可以有多个catch语句,捕获不同类型的异常,我们一般将抽象层次高的异常类型靠后放。
当然,记住这么多异常类型,着实不是一件容易的事情,所以我们在捕获异常时,可以利用向上转型:
catch (Exception e){
//捕获的任何异常,都会被父类Exception所引用
}
这样我们就可以避免记住那么多子类了。
接下来我们讲一下自定义的异常类,这个应该是比较常用的。自定义异常类当然得继承一下Exception类,我直接给出一个不太恰当的例子。
EquationSolving.java
public class EquationSolving {
private int a;
private int b;
private int c;
public void Solve () throws NoSolutionException
{
int abc=b*b-4*a*c;
if (abc<0)
{
throw (new NoSolutionException(abc));
}
if (abc==0)
{
System.out.print("方程的解:");
System.out.println(-b/2*a);
}
else{
System.out.println("方程组的解:");
System.out.println((-b+Math.sqrt(abc))/2*a+"和"+(-b-Math.sqrt(abc))/2*a);
}
}
public EquationSolving(int a, int b, int c) {
this.a = a;
this.b = b;
this.c = c;
}
public int getA() {
return a;
}
public void setA(int a) {
this.a = a;
}
public int getB() {
return b;
}
public void setB(int b) {
this.b = b;
}
public int getC() {
return c;
}
public void setC(int c) {
this.c = c;
}
}
NoSolutionException.java
public class NoSolutionException extends Exception{
private int abc;
@Override
public String getMessage() {
// return super.getMessage();
return "解析式="+abc+",方程无解";
}
public int getAbc() {
return abc;
}
public void setAbc(int abc) {
this.abc = abc;
}
public NoSolutionException(int abc) {
this.abc = abc;
}
}
这个例子很简单,求解一元二次方程,当方程无解的时候,抛出异常。我们写一个简单的测试类:
public class MyTest {
public static void main(String[] args) throws NoSolutionException
{
EquationSolving Test1=new EquationSolving(1,2,1);
Test1.Solve();
EquationSolving Test2=new EquationSolving(1,3,1);
Test2.Solve();
// EquationSolving Test3=new EquationSolving(2,2,1);
// Test3.Solve();
try {
EquationSolving Test4=new EquationSolving(2,2,1);
Test4.Solve();
//可能抛出异常的语句
}catch (NoSolutionException e){
System.out.println("抛出异常:");
System.out.println(e);
//捕获异常后执行的语句
}
finally {
//一定会执行的语句
}
}
}
运行结果如下:
下面我们重点说一下异常类中的这段代码。
@Override
public String getMessage() {
// return super.getMessage();
return "解析式="+abc+",方程无解";
}
这个其实是重写了父类中的getMessage()方法。在自定义异常类时,如果要给出抛出异常的信息,我们可以自己重新写一个新的方法实现,也可以重写getMessage()方法。
我们这里推荐重写getMessage方法。我们如果自己重写一个新方法,那在抛出具体的异常信息时,这需要我们自己调用自己的新方法,而如果重写getMessage(),那么Java就会默认调用getMessage()方法,同时,getMessage()方法返回的信息也会默认作为toString()方法的描述信息来源。
catch (NoSolutionException e){
System.out.println("抛出异常:");
System.out.println(e);
//捕获异常后执行的语句
}
我在这里并没有显示的调用getMessage()方法,直接输出这个对象,默认调用toString()。
说完这个我们来说JUnit。
我直接抄了一段维基百科上没有用的话作为开头:JUnit是一个Java语言的单元测试框架。它由肯特·贝克和埃里希·伽玛(Erich Gamma)建立,逐渐成为源于Kent Beck的sUnit的xUnit家族中为最成功的一个。
首先我们得引入Jar包,我直接给两个Maven仓库的地址:JUnit、JUnit Jupiter API
首先我们讲一下JUnit的断言吧。
我们给出一段代码
int Number=0;
//普通assert
assert Number==0:"";
//JUnit
Assertions.assertEquals(Number,0);
我们可以看到,其实就是把一些常用的assert给封装了一下,专门给懒人使用。
我在这儿给一份常用的断言,供参考。
assertNotEquals | 检查值是否不相等,第一个参数是一个可选的字符串消息。 |
assertArrayEquals | 检查两数组内容是否相同 |
assertTrue | 检查条件是否为真 |
assertFalse | 检查条件是否为假 |
assertNull | 检查对象是否为空 |
assertNotNull | 检查对象是否不空 |
assertSame | 检查两个变量是否引用同一对象 |
assertNotSame | 检查两个变量是否不引用同一对象 |
我们接下来讲一下JUnit的常用注解,照例给出表格。
注解 | 作用 |
@BeforeClass | 所注解的方法是JUnit测试时首先被运行的方法且只能运行一次,通常用来进行预处理等操作。 |
@Before | 所注解的方法在每个Test测试用例运行前运行,常用来进行初始化测试用例所需的资源。 |
@Test | 所注解方法的代码为测试用例,包含对源程序的测试代码。包括expected和timeout两个可选参数。其中:expected表示测试用例运行后应该抛出的异常;timeout表示测试方法的运行时间,以避免程序测试时死循环或测试时间过长。 |
@After | 所注解的方法在每个Test测试用例运行后运行,常用于释放@Before注解方法打开的资源。(当@Before或@Test注解的方法发生异常时,@After所注解的方法仍会被运行) |
@AfterClass | 所注解的方法是JUnit测试时最后一个被运行的方法且只能运行一次,通常用来释放相关使用资源。 |
@Ignore | 所注解的方法在测试过程中不会运行。 |
我们给出一个例子,来讲解一下@Test注解,这个注解也是最重要的。
import org.junit.Test;
import org.junit.jupiter.api.Assertions;
public class JUnitTest {
@Test
public void Test1() {
Assertions.assertThrows(NoSolutionException.class, () -> {
new EquationSolving(2, 2, 1).Solve();
});
}
@Test(expected = NoSolutionException.class)
public void Test2() throws NoSolutionException {
new EquationSolving(2,2,1).Solve();
}
}
这个例子其实是上面那个一元二次方程的JUnit测试版本。我们首先讲第一个测试方法吧。
这其实是JUnit5中,预期异常方法,下面给出完整的方法体:
public static <T extends Throwable> T assertThrows(Class<T> expectedType, Executable executable)
断言所提供的executable
的执行将引发expectedType
的异常并返回该异常。如果没有引发异常,或者引发了其他类型的异常,则此方法将失败。上述代码中,executable
实际上被写作一个Lambda表达式。
当然,向上转型在这里依旧适用,我可以将上述代码改为:
@Test
public void Test1() {
Assertions.assertThrows(Exception.class, () -> {
new EquationSolving(2, 2, 1).Solve();
});
}
测试依旧可以通过。
接下来说第二个,上面的表格中提到:
@Test
包括expected和timeout两个可选参数。其中:expected表示测试用例运行后应该抛出的异常;timeout表示测试方法的运行时间,以避免程序测试时死循环或测试时间过长。
expected实际上就是指定抛出异常的类型,我们这里通过注解调用。同样的,向上转型适用。
我们在这里演示了两种方法,这两种方法实际上是等效的。
这篇文章写的有点急,希望我后续还能完善一下。