1、异常结构

Java中异常的父类接口为Throwable,它的两个实现类为Error和Exception。根据Javac对异常的分类可以将异常划分为两类,如下所示:
不可检查异常:Error 和 RuntimeException 以及他们的子类为不可检查异常,不要求程序员手动处理该部分异常。该部分异常多半为程序本身错误,如数组越界。
可检查异常:除Error和RuntimeException外为可处理异常,要求程序员必须手动处理。即结合try...catch...finally或者throws来进行处理。
2、对于可检查异常的处理方式
a、使用try...catch...finally代码块处理;
b、在方法上使用throws来进行异常的上抛;
c、方法里使用throw手动抛出一个异常对象;
3、finally块和return
首先一个不容易理解的事实:在 try块中即便有return,break,continue等改变执行流的语句,finally也会执行。
public static void main(String[] args)
{
int re = bar();
System.out.println(re);
}
private static int bar()
{
try{
return 5;
} finally{
System.out.println("finally");
}
}
/*输出:
finally
5
*/
也就是说:try...catch...finally中的return 只要能执行,就都执行了,他们共同向同一个内存地址(假设地址是0x80)写入返回值,后执行的将覆盖先执行的数据,而真正被调用者取的返回值就是最后一次写入的。那么,按照这个思想,下面的这个例子也就不难理解了。
finally中的return 会覆盖 try 或者catch中的返回值。
public static void main(String[] args)
{
int result;
result = foo();
System.out.println(result); /2
result = bar();
System.out.println(result); /2
}
@SuppressWarnings("finally")
public static int foo()
{
trz{
int a = 5 / 0;
} catch (Exception e){
return 1;
} finally{
return 2;
}
}
@SuppressWarnings("finally")
public static int bar()
{
try {
return 1;
}finally {
return 2;
}
}
finally中的return会抑制(消灭)前面try或者catch块中的异常
class TestException
{
public static void main(String[] args)
{
int result;
try{
result = foo();
System.out.println(result); //输出100
} catch (Exception e){
System.out.println(e.getMessage()); //没有捕获到异常
}
try{
result = bar();
System.out.println(result); //输出100
} catch (Exception e){
System.out.println(e.getMessage()); //没有捕获到异常
}
}
//catch中的异常被抑制
@SuppressWarnings("finally")
public static int foo() throws Exception
{
try {
int a = 5/0;
return 1;
}catch(ArithmeticException amExp) {
throw new Exception("我将被忽略,因为下面的finally中使用了return");
}finally {
return 100;
}
}
//try中的异常被抑制
@SuppressWarnings("finally")
public static int bar() throws Exception
{
try {
int a = 5/0;
return 1;
}finally {
return 100;
}
}
}
finally中的异常会覆盖(消灭)前面try或者catch中的异常
class TestException
{
public static void main(String[] args)
{
int result;
try{
result = foo();
} catch (Exception e){
System.out.println(e.getMessage()); //输出:我是finaly中的Exception
}
try{
result = bar();
} catch (Exception e){
System.out.println(e.getMessage()); //输出:我是finaly中的Exception
}
}
//catch中的异常被抑制
@SuppressWarnings("finally")
public static int foo() throws Exception
{
try {
int a = 5/0;
return 1;
}catch(ArithmeticException amExp) {
throw new Exception("我将被忽略,因为下面的finally中抛出了新的异常");
}finally {
throw new Exception("我是finaly中的Exception");
}
}
//try中的异常被抑制
@SuppressWarnings("finally")
public static int bar() throws Exception
{
try {
int a = 5/0;
return 1;
}finally {
throw new Exception("我是finaly中的Exception");
}
}
}
上面的3个例子都异于常人的编码思维,因此我建议:
- 不要在fianlly中使用return。
- 不要在finally中抛出异常。
- 减轻finally的任务,不要在finally中做一些其它的事情,finally块仅仅用来释放资源是最合适的。
- 将尽量将所有的return写在函数的最后面,而不是try ... catch ... finally中。
4、Java中关于异常的面试题
a、Java中什么是Exception?
简单的说异常是系统或者程序传递错误的一种方式。在java中,异常功能是通过实现比如Throwable,Exception,RuntimeException之类的类,然后还有一些处理异常时候的关键字,比如throw,throws,try,catch,finally之类的。 所有的异常都是通过Throwable衍生出来的。Throwable把错误进一步划分为 java.lang.Exception 和 java.lang.Error. java.lang.Error 用来处理系统错误,例如java.lang.StackOverFlowError 之类的。然后 Exception用来处理程序错误,请求的资源不可用等等。
b、Java中的检查型异常和非检查型异常有什么区别?
两者的主要区别在于处理方式上:检查型异常需要使用try, catch和finally关键字在编译期进行处理,否则会出现编译报错。对于非检查型异常则不需要这样做。Java中所有继承自java.lang.Exception类的异常都是检查型异常,所有继承自RuntimeException的异常都被称为非检查型异常。
c、Java中的NullPointerException和ArrayIndexOutOfBoundException之间有什么相同之处?
两者都是非检查型异常,都继承自RuntimeException。该问题可能会引出另一个问题,即Java和C的数组有什么不同之处,因为C里面的数组是没有大小限制的,绝对不会抛出ArrayIndexOutOfBoundException。
d、 throw 和 throws这两个关键字在java中有什么不同?
throws总是出现在一个函数头中,用来标明该成员函数可能抛出的各种异常, 你也可以申明未检查的异常,但这不是编译器强制的。如果方法抛出了异常那么调用这个方法的时候就需要将这个异常处理。
另一个关键字 throw 是用来抛出任意异常的,按照语法你可以抛出任意 Throwable (i.e. Throwable 或任何Throwable的衍生类) , throw可以中断程序运行,因此可以用来代替return . 最常见的例子是用 throw 在一个空方法中需要return的地方抛出 UnSupportedOperationException 代码如下 :
private static void show() {
throw new UnsupportedOperationException("Notyet implemented");
}
e、什么是“异常链”?
以一个异常对象为参数构造新的异常对象。新的异对象将包含先前异常的信息。这项技术主要是异常类的一个带Throwable参数的函数来实现的。这个当做参数的异常,我们叫他根源异常(cause)。该技术大多用于将“ 受检查异常” ( checked exception)封装成为“非受检查异常”(unchecked exception)或者RuntimeException。顺便说一下,如果因为异常你决定抛出一个新的异常,你一定要包含原有的异常,这样,处理程序才可以通过getCause()和initCause()方法来访问异常最终的根源。
f、JDK7中对异常处理做了什么改变?
JDK7中对错误(Error)和异常(Exception)处理主要新增加了2个特性,一是在一个catch块中可以出来多个异常,就像原来用多个catch块一样。另一个是自动化资源管理(ARM), 也称为try-with-resource块。这2个特性都可以在处理异常时减少代码量,同时提高代码的可读性。
g、如果执行finally代码块之前方法返回了结果,或者JVM退出了,finally块中的代码还会执行吗?
这个问题也可以换个方式问:“如果在try或者finally的代码块中调用了System.exit(),结果会是怎样”。除了在try块中执行System.exit(0);这种情况下finally中的代码块不会执行,别的情况下finally中的代码均会执行。如下代码所示:
public class Test {
public static void main(String[] args){
int re = bar();
System.out.println(re);
}
private static int bar()
{
try{
return 5;
} finally{
System.out.println("finally");
}
}
}
//输出结果为:finally
// 5
public class Test {
public static void main(String[] args){
bar();
System.out.println("test");
}
private static void bar()
{
try{
System.exit(0);
} finally{
System.out.println("finally");
}
}
}
//输出结果:JVM直接退出
h、Java中final,finalize,finally关键字的区别
final关键字:可以用于修饰类、方法、变量。
修饰类:表示该类不能被子类继承,同时该类中的所有方法都会被编译器处理为final方法,但是类中的属性可以被修改。
修饰方法:表示该方法不可以被子类重写,但是可以被子类重载。
修饰变量:修饰基本数据类型时,表示为一个常量,修饰引用变量时则表示该引用变量不可变,但是该引用对象中的属性是可变的。
public class Test {
public final void bar()
{
try{
System.exit(0);
} finally{
System.out.println("finally");
}
}
class TestExtends extends Test{
//重载
public final void bar(String str){
}
}
}
参考文章:
https://github.com/h2pl/Java-Tutorial/blob/master/docs/java/basic/10%E3%80%81Java%E5%BC%82%E5%B8%B8.md
https://www.cnblogs.com/lulipro/p/7504267.html
https://www.cnblogs.com/smart-hwt/p/8257330.html