第九节: 异常处理
之前也写了一篇关于异常的,其实只看这篇也可以,这篇要比之前那篇详细一点点。。。
异常的概述
程序不可避免的会有bug,对于一些不符合逻辑的情况,我们就可以用 异常 来处理。
一旦我们遇到了 异常(不正常、不符合逻辑) 的情况,系统就会自动的产生一个 异常类的实例化对象 来进行处理。(Java 是面向对象的语言,都是通过对象来解决问题。。。)
Java 中的异常体系如下图:
应当注意的是,我们处理异常的目的是解决这种情况下应该怎么办,怎么去补救这样的不正常的情况。 而不是捕获了这个异常之后就打印出来就完事了。
但有时候我们是真的不知到该怎么处理,这时候就可以把异常抛给调用他的方法,在更高的视角下,逻辑往往会清晰一些。。。
处理异常的2个原则:
-
不要去捕获大级别的异常:比如 Exception。。。(应该要去捕获特定类型的异常)
因为这样我们无法针对特殊情况进行处理,做不到个性化,往往对用户的感觉就会不好。
-
不要去生吞异常:
也就是不要去想着隐藏一个异常,不要去想着要忽视他,出现错误就应该要解决,而不是逃避。
try-catch-finally 处理异常
我们处理异常就是在可能出现异常的代码用 try 去把他抱起来,对于捕获到的具体异常情况在 catch 里处理。
finally 是一块必然执行的代码块,不管发生什么情况,里面的语句都会执行。(除非程序退出JVM、停电等这样的极端情况)。
catch , finally 我们只可以省略一个,但不能都省略,可以同时存在。
对于 return 和 finally 的问题,也没那么难理解
-
return 时,我们得到的返回值是 “值”。。。
就比如: 对于基本数据类型,我们得到的是 他代表的数字值。。。
对于引用类型,我们得到的是 引用的值,或者说是 引用指向的 那个地址。
理解了这点,问题就不大了。。。
-
但是 finally 里有 return 的话,就直接返回 finally 里的 return 了。
下面看下代码加深下理解:
/** * final 里的语句是必然执行的。 返回值判断只是跟返回的机制有关,或者说跟引用类型和基本数据类型有关。 * return 时,是已经 复制值了,不是复制引用,是复制引用的值。 * 对引用类型来说,就是复制他的地址值。 对基本数据类型来说,就是他的值。 * * 注意是必然的,除非程序终止的情况:包括但不限于JVM退出、电脑突然停电。。。 */
public class Demo1 {
private static class Person{
int age;
}
/** * 返回 number = 10; */
private static int method1() {
int number;
try{
number = 10;
return number;
}finally{
number = 99;
}
}
/** * 返回: person.age = 99 */
private static Person method2() {
Person person = new Person();
try {
person.age = 10;
return person;
} finally{
person.age = 99;
}
}
/** * String 是不可变的 * 返回: try string */
private static String methodString() {
String res = "";
try {
res = "try string";
return res;
} finally {
System.out.println("finally in methodString execute...");
res = "finally string";
}
}
/** * StringBuilder ,就和上面那个 Person 的情况是类似的。 * 返回: try string finally string */
private static StringBuilder methodStringBuilder() {
StringBuilder res = new StringBuilder();
try {
res.append("try string ");
// 如果返回类型是 String, 这里写的是 res.toString()。 那返回的是: try string
return res;
}finally {
System.out.println("finally in methodStringBuilder execute...");
res.append("finally string...");
}
}
/** * 返回 try string */
private static String methodStringBuilder2() {
StringBuilder res = new StringBuilder();
try {
res.append("try string ");
// 如果返回类型是 String, 这里写的是 res.toString()。 那返回的是: try string
return res.toString();
}finally {
System.out.println("finally in methodStringBuilder execute...");
res.append("finally string...");
}
}
/** * finally 里有 return 的话,就直接返回了。。。 * 返回: "return in finally" */
private static String meghodReturn() {
try {
return "return in try";
}finally {
System.out.println("finally in methodReturn execute...");
return "return in finally";
}
}
/** * 程序在fianlly前终止了,finally就不会执行。 */
private static void method3() {
try{
System.out.println("method3() 已经开始执行。。。");
System.exit(0); // 人为的退出JVM
}finally {
// 上面退出程序了,这里是不会执行的。
System.out.println("method3() 里 finally代码块。。。");
}
}
public static void main(String[] args) {
// int number = method1();
// Person person = method2();
// System.out.println("number : " + number);
// System.out.println("person.age : " + person.age);
String s = methodString();
System.out.println("methodString() 的返回值 : " + s + "\n");
StringBuilder stringBuilder = methodStringBuilder();
System.out.println("methodStringBuilder() 的返回值 : " + stringBuilder + "\n");
String s2 = methodStringBuilder2();
System.out.println("methodStringBuilder2() 的返回值 :" + s2 + "\n");
String s1 = meghodReturn();
System.out.println("methodReturn() 的返回值 : " + s1 + "\n");
method3();
}
}
throws
在方法名后面使用,抛给调用该方法的地方,在调用者那里进行处理,可以try进行处理,也可以接着往上级抛。
在 main 线程中 throws 异常的话,就是给启动 main 线程的 JVM 进行处理了,一般进行处理的方法都是中断运行,打印错误信息到控制台。。。
throw
我们可以自己人为的,手动的抛出一个异常,就可以用 throw 来完成。(抛出的是 : 异常的实例化对象)
有时候我们需要处理不受检查的异常,也就是运行时异常,就可以使用 throw 来进行操作,把异常抛给上一级,和 throws 的功能有点像。
当然你可以在 try 里面 throw 出来异常,再在catch 里进行处理,但这样的行为感觉很傻。一般 throw 出来的异常,目的就是给他的上一级来进行处理。。。
自定义异常
从 Java 推出至今,官方指定了很多很多种的异常类型。
我们可以基于我们写程序的特定场景,自己定义一个符合场景的异常类,该类可继承自 Exception, 或者 RuntimeException, 就看理解上面 那个 Java 的异常体系了决定了,当然也可以继承另外的更具体的异常。
一般我们自己定义的异常类,都会去重写 只有1个参数的构造方法,传入的打印出来的异常信息。。。