Lesson16_异常
异常是什么
程序运行过程中那些不正常的情况:比如,该输入整数,结果输入的不是整数。
比如,该解析的是yyyy年MM月dd日格式的日期字符串,结果传入的是“2018-11-29”
异常机制的本质
就是当程序出现错误,程序安全退出的机制。
会导致什么结果
整个程序直接结束。用户体验极差!!!
常见的异常有哪些
package com.lesson19.常见异常;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;
// 用尽可能简短的代码把异常搞出来
public class Demo {
public static void main(String[] args) {
// 1.数组下标越界异常 ArrayIndexOutOfBoundsException
int[] arr = {};
// System.out.println(arr[0]);
// 2.空指针异常 NullPointerException
String str = null;
// System.out.println(str.length()); // str的最大长度 字符的个数
class Person{
private String name;
private int age;
// 省略get\set、构造方法等
public void test(){}
}
Person person = null;
// person.test(); // p是null null就是不指向任何对象,也就是没有对象。没有对象,哪来的test()方法呢? 所以抛出Null Pointer Exception(NPE)
// 3.算术异常 ArithmeticException: / by zero
// System.out.println(3/0);
// 4.类型转换异常 ClassCastException
class Pet{}
class Cat extends Pet{}
class Dog extends Pet{}
Pet p = new Cat();
// Dog dog = (Dog) p;
// 5.输入类型不匹配异常 InputMismatchException
Scanner input = new Scanner(System.in);
// int num = input.nextInt();
// 6.字符串下标越界异常 StringIndexOutOfBoundsException
String s = "abc";
// System.out.println(s.charAt(3));
// 7.解析异常 ParseException
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日");
/*try {
Date date = sdf.parse("2018-11-29");
} catch (ParseException e) {
e.printStackTrace();
}*/
// 8.栈溢出 StackOverflowError
test();
}
public static void test(){
// System.out.println("我中有我:递归");
test();// 自己调用自己
}
}
java对异常的处理过程:
- java是采用面向对象的方式来处理异常的。
- 抛出异常:在执行方法时,如果发生异常,则这个方法生成代表该异常的一个对象,停止当前执行路径,并把异常对象交给JRE
- 捕获异常:JRE得到该异常后,寻找相应的代码来处理异常。JRE在方法调用栈中查找,从生成异常的方法开始回溯,只直到找到相应的异常处理代码为止。
防止中断
设计程序过程中,可以在可能出现异常(预见性)的地方提供异常的方式来防止程序的中断。
异常的分类
-
非受查异常
- 【阴柔】如果不作处理,编译器不会给出任何错误提示。在你程序运行期间,惹到了它,就会给你脸色
- 需要凭借软件工程师的经验预判
- RuntimeException是非受查异常类的鼻祖,另一派是Error
- 派生于RuntimeException的异常,如被 0 除、数组下标越界、空指针等,其产生比较频繁,处理麻烦,如果显式的声明或捕获将会对程序可读性和运行效率影响很大。 因此由系统自动检测并将它们交给缺省的异常处理程序(用户可不必对其处理)。
- 这类异常通常是由编程错误导致的,所以在编写程序时,并不要求必须使用异常处理机制来处理这类异常,经常需要通过增加“逻辑处理来避免这些异常”。
-
受查异常
- 【刚烈】如果不处理,就跟你没完,编译器会报错,无法运行
- Exception是受查异常的代表,它的子类除了RuntimeException分支外,都是受查异常
-
Error-错误,由java虚拟机生成并抛出,程序不做任何处理,程序员也不能解决的问题,是程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。例如,Java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。
- Error表明系统JVM已经处于不可恢复的崩溃状态中,我们不需要管它。
-
Exception-IoException、RuntimeException
注意事项
- 在方法抛出异常之后,运行时系统将转为寻找合适的异常处理器(exception handler)。潜在的异常处理器是异常发生时依次存留在调用栈中的方法的集合。当异常处理器所能处理的异常类型与方法抛出的异常类型相符时,即为合适的异常处理器。
- 运行时系统从发生异常的方法开始,依次回查调用栈中的方法,直至找到含有合适异常处理器的方法并执行。当运行时系统遍历调用栈而未找到合适的异常处理器,则运行时系统终止。同时,意味着Java程序的终止。
- 方法重写中声明异常原则:子类重写父类方法时,如果父类方法有声明异常,那么子类声明的异常范围不能超过父类声明的范围。
- 要避免使用异常处理代替错误处理,这样会降低程序的清晰性,并且效率低下。
- 处理异常不可以代替简单测试—只在异常情况下使用异常机制。
- 不要进行小粒度的异常处理—应该将整个任务包装在一个try语句块中。
- 异常往往在高层处理(先了解!后面做项目会说!) 。
如何处理异常(重要)
异常处理机制
-
try-catch-finally
-
try 监视,监视一段有异常风险的代码,可能会产生异常,也可能会正常执行。需要跟catch或者finally语句块一起使用,try-catch、try-finally、try-catch-finally
-
catch 如果try语句块抛出了异常,会寻找能处理这个异常的catch来处理异常,相应的catch语句块就会执行。一般是给用户提示,告知异常原因,可以有多个catch快。当catch语句块用于捕获多个异常时,将具体的异常放在前面,通用的异常放在后面,否则会编译报错,当前面异常都无法处理时,通过Exception处理,防止漏掉异常。
-
finally 具备执行的“坚决性”,就算它前面有return,也要等finally执行完毕后,才能return。如果try中有System.exit(0),会直接退出程序,一般用于释放资源。
通常用于资源的释放。无论是否抛出异常,finally语句块可以确保执行
-
-
throw-throws
- throw 扔出一个异常对象; 可以在任何地方使用,一般用于方法内部。
- throws 声明一个异常类型; 在方法上声明本方法可能抛出的异常类型,由调用者去处理,实现类职能分离(此方法只关注要实现的功能,实现功能过程中会遇到的异常交由调用者处理)
- 如果有可能抛出异常,需要在方法上通过throws声明,throw与throws一般配合使用。
-
选取
- 如果是内部原因导致的异常,则自己处理,try-catch-finally
- 如果是传入的参数不合法,外部原因导致的异常,就算自己捕获了也解决不了问题。则throw-throws
- 当有可能由于调用时方法参数导致的异常,将异常细化并抛出具体的异常,提示调用者参数问题。当方法在调用时不时由于传入参数导致的异常,尽量在该方法内将异常处理。
自定义异常(了解)
异常类最关键的是类名。
大家会发现,异常类的类名往往都很长。目的是通过类名就能够大概判断出问题原因。
Java系统提供的异常类型虽然很多,但终究是不足的。所以才有了自定义异常。
如果想自定义一个受查异常,则继承Exception;
如果想自定义一个非受查异常,则继承RuntimeException;
错误轨迹栈的查看(了解)
飘红
Exception in thread "main" java.lang.NullPointerException: 空指针异常
at com.lesson19.错误轨迹栈.Demo.d(Demo.java:22) // 这里才是异常的第一案发现场,优先查看
at com.lesson19.错误轨迹栈.Demo.c(Demo.java:18)
at com.lesson19.错误轨迹栈.Demo.b(Demo.java:14)
at com.lesson19.错误轨迹栈.Demo.a(Demo.java:10)
at com.lesson19.错误轨迹栈.Demo.main(Demo.java:6)
Exception in thread "main" java.util.InputMismatchException
at java.util.Scanner.throwFor(Scanner.java:864) // 这个异常不是我们能控制的,不必查看
at java.util.Scanner.next(Scanner.java:1485)
at java.util.Scanner.nextInt(Scanner.java:2117)
at java.util.Scanner.nextInt(Scanner.java:2076)
at com.lesson19.常见异常.Demo.main(Demo.java:39) // 离异常最近的,属于自己的代码,优先查看
优先查看:距离异常最近的(最上面的)属于自己的代码。
★debug能力(注意培养)
-
System.out.println();
-
有些代码的运行情况并非如我们预期的那样。不要想当然,打印出来看一下,有助于我们排查错误
public static void main(String[] args) { int sum = 0; int i = 1; for(; i <= 10; i ++);{ // 错在哪儿了? System.out.println("i = "+i); sum += i; } System.out.println(sum); // sum == 11 ?! }
-
-
单步调试
- 如果已经确定错误的范围,而且范围比较小,此时可以使用单步调试
- 步骤
- 打断点 在行号后面单击鼠标左键
- Step Into 单步调试,一行一行地执行
- Step Return 误入方法时,想快速出去
- Step Over 把方法调用当作一条语句直接执行完毕,不进入方法内部
-
单元测试
-
好的单元测试:单元测试能否通过,标志着对应的功能模块是否正常。
-
业务代码和测试代码分离,互不干扰,而且可以重复测试。项目越大,单元测试的价值越高
-
https://www.cnblogs.com/huaxingtianxia/p/5563111.html
// 需要导入的包和方法 import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; // 测试方法演示 @Test public void testXXX(){ assertEquals(期望值,实际值); // 如果实际值与预期不符,则说明测试未通过,功能代码有问题 }
-