1、什么是异常,java提供异常处理机制有什么用?
- 什么是异常:程序执行过程中的不正常情况。
- 异常的作用:增强程序的
健壮性
。
eg.
public class ExceptionTest01 {
public static void main(String[] args) {
int a = 10;
int b = 0;
// 实际上JVM在执行到此处的时候,会new异常对象:new ArithmeticException("/ by zero");
// 并且JVM将new的异常对象抛出,打印输出信息到控制台了。
int c = a / b;
System.out.println(a + "/" + b + "=" + c);
// 此处运行也会创建一个:ArithmeticException类型的异常对象。
System.out.println(100 / 0);
}
}
2、java语言中异常是以什么形式存在的呢?
异常在java中以 类
的形式存在,每一个 异常类 都可以创建 异常对象。
eg.
public class ExceptionTest02 {
public static void main(String[] args) {
// 通过“异常类”实例化“异常对象”
NumberFormatException nfe = new NumberFormatException("数字格式化异常!");
// java.lang.NumberFormatException: 数字格式化异常!
System.out.println(nfe);
}
}
3、异常继承结构图
- Exception的直接子类:编译时异常(要求程序员在编写程序阶段必须预先对这些异常进行处理,如果不处理编译器报错,因此得名编译时异常。)。
- RuntimeException:运行时异常。(在编写程序阶段程序员可以预先处理,也可以不管,都行。)
4、异常的分类
异常分为 编译时异常
和 运行时异常
。
所有异常都是在 运行阶段
发生的。因为只有程序运行阶段才可以 new对象。
因为异常的发生就是 new异常对象
。
4.1编译时异常因为什么而得名?
因为编译时异常必须在编译(编写)阶段预先处理,如果不处理编译器报错,因此得名。
4.2 编译时异常和运行时异常的区别?
- 编译时异常一般发生的概率
比较高
。 - 运行时异常一般发生的概率
比较低
。 - 编译时异常发生概率较高,需要在运行之前对其进行
预处理
。 - 运行时异常发生概率较低,没必要提前进行预处理。
4.3编译时异常和运行时异常别称
- 编译时异常
- 受检异常:CheckedException
- 受控异常
- 运行时异常
- 未受检异常:UnCheckedException
- 非受控异常
1、补充:
public class ExceptionTest03 {
public static void main(String[] args) {
System.out.println(100 / 0);
// 这里的HelloWorld没有输出,没有执行。
System.out.println("Hello World!");
}
}
程序执行到System.out.println(100 / 0);
此处发生了 ArithmeticException
异常,底层 new
了一个ArithmeticException异常对象,然后抛出了。
由于是 main方法
调用了100 / 0,所以这个异常ArithmeticException抛给了main方法。
main方法没有处理,将这个异常自动抛给了 JVM
。JVM最终终止程序的执行。
此时System.out.println("Hello World!");
并不会执行。
注意:
ArithmeticException
继承 RuntimeException
,属于 运行时异常
。在编写程序阶段不需要对这种异常进行预先的处理。
eg.
public class ExceptionTest04 {
public static void main(String[] args) {
// main方法中调用doSome()方法
// 因为doSome()方法声明位置上有:throws ClassNotFoundException
// 我们在调用doSome()方法的时候必须对这种异常进行预先的处理。
// 如果不处理,编译器就报错。
//编译器报错信息: Unhandled exception: java.lang.ClassNotFoundException
doSome();
}
/**
* doSome方法在方法声明的位置上使用了:throws ClassNotFoundException
* 这个代码表示doSome()方法在执行过程中,有可能会出现ClassNotFoundException异常。
* 叫做类没找到异常。这个异常直接父类是:Exception,所以ClassNotFoundException属于编译时异常。
* @throws ClassNotFoundException
*/
public static void doSome() throws ClassNotFoundException{
System.out.println("doSome!!!!");
}
}
解决方法一、throws上报给方法调用者(推卸责任:调用者知道)
public class ExceptionTest04 {
public static void main(String[] args) throws ClassNotFoundException {
doSome();
}
public static void doSome() throws ClassNotFoundException{
System.out.println("doSome!!!!");
}
}
解决方法二、try…catch捕捉,处理(调用者是不知道)
public class ExceptionTest04 {
public static void main(String[] args) {
try {
doSome();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static void doSome() throws ClassNotFoundException{
System.out.println("doSome!!!!");
}
}
5、异常的处理方式
5.1 throws
在方法声明的位置上使用 throws
关键字抛出,谁调用我这个方法,我就抛给谁。抛给 调用者
来处理。
这种处理异常的态度:上报。
5.2 try…catch
这个异常不会上报,自己把这个事儿处理了。
异常抛到此处为止,不再上抛了。
注意:
- 只要异常没有捕捉,采用上报的方式,此方法的
后续代码不会执行
。 - try语句块中的某一行出现异常,该行
后面的代码不会执行
。 - try…catch捕捉异常之后,后续代码可以执行。
eg.
private static void m1() throws FileNotFoundException {
System.out.println("m1 begin");
m2();
// 以上代码出异常,这里是无法执行的。
System.out.println("m1 over");
}
try {
m1();
// m1方法出异常,下面代码不执行。
System.out.println("hello world!");//不执行
} catch (FileNotFoundException e){
//异常处理
System.out.println("出异常了!!");
System.out.println(e);
}
System.out.println("hello world"); //会执行
注意:
- 异常发生之后,如果我选择了上抛,抛给了我的调用者,调用者需要对这个异常继续处理,那么调用者处理这个异常同样有两种处理方式。
- 一般不建议在main方法上使用throws,因为这个异常如果真正的发生了,一定会抛给JVM。JVM只有终止。
- 一般main方法中的异常建议使用try…catch进行捕捉。
注意:
try {
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
这个分支中可以使用e引用,e引用
保存的内存地址是那个new出来 异常对象的内存地址
。
6、在以后开发中,处理编译时异常,应该上报还是捕捉呢?
- 如果希望调用者来处理,选择throws上报。
- 其它情况使用捕捉的方式。
7、深入try…catch
- catch后面的小括号中的类型可以是
具体的异常类型
,也可以是该异常类型的父类型
。 - catch可以写多个。建议catch的时候,精确的一个一个处理。这样有利于程序的调试。
- catch写多个的时候,从上到下,必须遵守
从小到大
。
eg.
try {
FileInputStream fis = new FileInputStream("D:\\Download\\Javabean-addperson案例解析.docx");
} catch(FileNotFoundException e) {
System.out.println("文件不存在!");
}
等同于
try {
FileInputStream fis = new FileInputStream("D:\\Download\\Javabean-addperson案例解析.docx");
} catch(Exception e) {// 多态:Exception e = new FileNotFoundException();
System.out.println("文件不存在!");
}
try {
FileInputStream fis = new FileInputStream("D:\\Download\\Javabean-addperson案例解析.docx");
fis.read();
} catch(IOException e){
System.out.println("读文件报错了!");
} catch(FileNotFoundException e) {
System.out.println("文件不存在!");
}
- JDK8的新特性:
catch() 异常间可以自小到大用|
分割
eg.
try {
//创建输入流
FileInputStream fis = new FileInputStream("D:\\Download\\Javabean-addperson案例解析.docx");
// 进行数学运算
System.out.println(100 / 0); // 这个异常是运行时异常,编写程序时可以处理,也可以不处理。
} catch(FileNotFoundException | ArithmeticException | NullPointerException e) {
System.out.println("文件不存在?数学异常?空指针异常?都有可能!");
}
8、异常两个重要方法
方法名 | 作用 |
---|---|
String getMessage() | 返回异常的详细消息字符串 |
void printStackTrace() | 追踪堆栈异常信息(采用异步线程) |
9、finally字句
在finally子句中的代码是最后执行的,并且是 一定会执行
的,即使try语句块中的代码出现了异常。
finally子句必须和try一起出现,不能单独编写。
9.1 finally语句通常使用在哪些情况下呢?
通常在finally语句块中完成 资源的释放/关闭
。
eg.
public class ExceptionTest10 {
public static void main(String[] args) {
FileInputStream fis = null; // 声明位置放到try外面。这样在finally中才能用。
try {
fis = new FileInputStream("D:\\Download\\Javabean-addperson案例解析.docx");
String s = null;
// 这里一定会出现空指针异常!
s.toString();
System.out.println("hello world!");
// 流使用完需要关闭,因为流是占用资源的。
// 即使以上程序出现异常,流也必须要关闭!
// 放在这里有可能流关不了。
//fis.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch(IOException e){
e.printStackTrace();
} catch(NullPointerException e) {
e.printStackTrace();
} finally {
System.out.println("hello 浩克!");
// 流的关闭放在这里比较保险。
// finally中的代码是一定会执行的。
// 即使try中出现了异常!
if (fis != null) { // 避免空指针异常!
try {
// close()方法有异常,采用捕捉的方式。
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
9.2try和finally联用,没有catch
eg.
public class ExceptionTest11 {
public static void main(String[] args) {
try {
System.out.println("try...");
return;
} finally {
System.out.println("finally...");
}
// 这里不能写语句,因为这个代码是无法执行到的。
//System.out.println("Hello World!");
}
}
以下代码的执行顺序:
- 先执行try…
- 再执行finally…
- 最后执行 return (return语句只要执行方法必然结束。)
注意:
- try不能单独使用。
- try finally可以联合使用。
- 放在finally语句块中的代码是一定会执行的
9.3 finally子句失效
System.exit(0);
只有这个可以治finally。
public class ExceptionTest12 {
public static void main(String[] args) {
try {
System.out.println("try...");
// 退出JVM
System.exit(0); // 退出JVM之后,finally语句中的代码就不执行了!
} finally {
System.out.println("finally...");
}
}
}
9.4 finally面试题
public class ExceptionTest13 {
public static void main(String[] args) {
int result = m();
System.out.println(result); //100
}
/*
java语法规则(有一些规则是不能破坏的,一旦这么说了,就必须这么做!):
java中有一条这样的规则:
方法体中的代码必须遵循自上而下顺序依次逐行执行(亘古不变的语法!)
java中海油一条语法规则:
return语句一旦执行,整个方法必须结束(亘古不变的语法!)
*/
public static int m(){
int i = 100;
try {
// 这行代码出现在int i = 100;的下面,所以最终结果必须是返回100
// return语句还必须保证是最后执行的。一旦执行,整个方法结束。
return i;
} finally {
i++;
}
}
}
反编译之后的效果:
public static int m(){
int i = 100;
int j = i;
i++;
return j;
}
9.5 final finally finalize有什么区别?
- final 关键字
- final修饰的
类
无法继承 - final修饰的
方法
无法覆盖 - final修饰的
变量
不能重新赋值。
- finally 关键字
- finally 和try一起联合使用。
- finally语句块中的代码是必须执行的。
- finalize 标识符
- 是一个Object类中的方法名。
- 这个方法是由垃圾回收器GC负责调用的
10、自定义异常(开发中常用)
10.1前言
SUN提供的JDK内置的异常肯定是不够的用的。在实际的开发中,有很多业务,这些业务出现异常之后,JDK中都是没有的。和业务挂钩的。因此需要自定义异常。
10.2自定义异常步骤
- 第一步:编写一个类继承
Exception
或者RuntimeException
. - 第二步:提供两个
构造方法
,一个无参数的,一个带有String参数的。
eg.
//栈操作异常:自定义异常!
public class StackOperationException extends Exception{ // 编译时异常!
public MyStackOperationException(){
}
public MyStackOperationException(String s){
super(s);
}
}
11、方法覆盖,时遗留的问题
- 重写之后的方法不能比重写之前的方法抛出更多(更宽泛)的异常,可以更少。方法覆盖
class Animal {
public void doSome(){
}
public void doOther() throws Exception{
}
}
class Cat extends Animal {
// 编译正常。
public void doSome() throws RuntimeException{
}
// 编译报错。
/*public void doSome() throws Exception{
}*/
// 编译正常。
/*public void doOther() {
}*/
// 编译正常。
/*public void doOther() throws Exception{
}*/
// 编译正常。
public void doOther() throws NullPointerException{
}
}
注意:
一般不会这样考虑,方法覆盖复制一份,然后重写就好了。
12、总结异常中的关键字
- 异常捕捉:
- try
- catch
- finally
- throws 在方法声明位置上使用,表示上报异常信息给调用者。
- throw 手动抛出异常!
eg.
public void pop() throws StackOperationException {
if(index < 0){
throw new MyStackOperationException("弹栈失败,栈已空!");//手动抛出异常
}
}
该方法index < 0时手动抛出异常,然后在方法里没有处理,上报给调用者,让调用者处理!