Java异常处理知识点

Java异常体系

Throwable
Erro
Exception
......
IOException
RuntimeException
EOFException
FileNotFoundException
ArithmeticException
NullPointerException
UnknowTypeException
ClassNotFoundException
MissingResourceException
ArrayIndexOutOfBoundsException
......

从图中可以看出所有异常类型都是内置类Throwable的子类,因而Throwable在异常类的层次结构的顶层。
接下来Throwable分成了两个不同的分支,一个分支是Error,它表示不希望被程序捕获或者是程序无法处理的错误。另一个分支是Exception,它表示用户程序可能捕捉的异常情况或者说是程序可以处理的异常。其中异常类Exception又分为运行时异常(RuntimeException)和非运行时异常。
Java异常又可以分为不受检查异常(Unchecked Exception)和检查异常(Checked Exception)

这些异常之间的区别与联系:
  • Error:Error类对象由 Java 虚拟机生成并抛出,大多数错误与代码编写者所执行的操作无关。

  • Exception:在Exception分支中有一个重要的子类RuntimeException(运行时异常),该类型的异常自动为你所编写的程序定义ArrayIndexOutOfBoundsException(数组下标越界)、NullPointerException(空指针异常)、ArithmeticException(算术异常)、MissingResourceException(丢失资源)、ClassNotFoundException(找不到类)等异常,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生;而RuntimeException 之外的异常我们统称为非运行时异常,类型上属于Exception类及其子类,从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOExceptionSQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常

    Error和Exception的区别:Error通常是灾难性的致命的错误,是程序无法控制和处理的,当出现这些异常时,Java虚拟机(JVM)一般会选择终止线程;Exception通常情况下是可以被程序处理的,并且在程序中应该尽可能的去处理这些异常。

  • 检查异常:在正确的程序运行过程中,很容易出现的、情理可容的异常状况,在一定程度上这种异常的发生是可以预测的,并且一旦发生该种异常,就必须采取某种方式进行处理

    除了RuntimeException及其子类以外,其他的Exception类及其子类都属于检查异常,当程序中可能出现这类异常,要么使用try-catch语句进行捕获,要么用throws子句抛出,否则编译无法通过。

  • 不受检查异常:包括RuntimeException及其子类和Error。


处理异常的两种方式

1. 捕获异常(try-catch-finally)
try{
	//可能发生异常的代码块
}catch(可以捕获的异常1{
	//处理异常1的代码
}catch(可以捕获的异常2{
	//处理异常2的代码
}finally{
	//处理完所有异常后一定会执行的代码。
	//在这里注意的事。如果在这里没有出现异常,最终也会执行这行代码
}

注意:在这里,格式是可以发生变化的,我们可以增加catch块,也可以不需要finally块。

使用多重的catch语句:很多情况下,由单个的代码段可能引起多个异常。处理这种情况,我们需要定义两个或者更多的catch子句,每个子句捕获一种类型的异常,当异常被引发时,每个catch子句被依次检查,第一个匹配异常类型的子句执行,当一个catch子句执行以后,其他的子句将被旁路。

编写多重catch语句块注意事项
  顺序问题:先小后大,即先子类后父类
  
嵌套try语句:try语句可以被嵌套。也就是说,一个try语句可以在另一个try块的内部。每次进入try语句,异常的前后关系都会被推入堆栈。如果一个内部的try语句不含特殊异常的catch处理程序,堆栈将弹出,下一个try语句的catch处理程序将检查是否与之匹配。这个过程将继续直到一个catch语句被匹配成功,或者是直到所有的嵌套try语句被检查完毕。如果没有catch语句匹配,Java运行时系统将处理这个异常。
例如:

class NestTry{
    public static void main(String[] args){
        try{
            int a = args.length;
            int b = 42 / a;
            System.out.println("a = "+ a);
            try{
                if(a == 1){
                a = a/(a-a);
                }
                if(a == 2){
                    int c[] = {1};
                    c[42] =99;
                }
            }catch(ArrayIndexOutOfBoundsException e){
                System.out.println("ArrayIndexOutOfBounds :"+e);
            }    
        }catch(ArithmeticException e){
            System.out.println("Divide by 0"+ e);
        }
    }
}

正如程序中所显示的,该程序在一个try块中嵌套了另一个try块。
程序工作如下:当你在没有命令行参数的情况下执行该程序,外面的try块将产生一个被0除的异常。
程序在有一个命令行参数条件下执行,由嵌套的try块产生一个被0除的异常,由于内部的catch块不匹配这个异常,它将把异常传给外部的try块,在外部异常被处理。如果你在具有两个命令行参数的条件下执行该程序,将由内部try块产生一个数组边界异常。
结果:

D:\java>javac estTry.java

D:\java>>java NestTry

Divide by 0 java.lang.ArithmeticExceptio: / by zero

D:\java>java NestTry one

a = 1

Divide by 0java.lang.ArithmeticException: / by zero

D:\java>java NestTry one two

a = 2

ArrayIndexOutOfBounds :java.lang.ArrayIndexOutOfBoundsException: 42
2. 抛出异常(Throw/Throws)
  • throw

我们还可以用throw语句抛出明确的异常。
Throw的语法形式如下:

throw ThrowableInstance;

这里的ThrowableInstance一定是Throwable类类型或者Throwable子类类型的一个对象。
简单的数据类型,例如int,char,以及非Throwable类,例如String或Object,不能用作异常。

有两种方法可以获取Throwable对象:在catch子句中使用参数或者使用new操作符创建。

程序执行完throw语句之后立即停止;
throw后面的任何语句不被执行,最邻近的try块用来检查它是否含有一个与异常类型匹配的catch语句。
如果发现了匹配的块,控制转向该语句;如果没有发现,次包围的try块来检查,以此类推。
如果没有发现匹配的catch块,默认异常处理程序中断程序的执行并且打印堆栈轨迹。
例如:

class TestThrow{
	static void proc(){
		try{
			throw new NullPointerException("demo");	
		}catch(NullPointerException e){
			System.out.printlb("Caught inside proc");
			throw e;
		}
	}
	public static void main(String [] args){
        try{
            proc();
        }catch(NullPointerException e){
            System.out.println("Recaught: "+e);
    }
}

结果:

D:\java>java TestThrow

Caught inside proc

Recaught: java.lang.NullPointerException: demo

该程序两次处理相同的错误,首先,main()方法设立了一个异常关系然后调用proc()。proc()方法设立了另一个异常处理关系并且立即抛出一个NullPointerException实例,NullPointerException在main()中被再次捕获。

该程序阐述了怎样创建Java的标准异常对象,特别注意这一行:

throw new NullPointerException("demo");

此处new用来构造一个NullPointerException实例,所有的Java内置的运行时异常有两个构造方法:一个没有参数,一个带有一个字符串参数。
当用第二种形式时,参数指定描述异常的字符串。如果对象用作print()或者println()的参数时,该字符串被显示。这同样可以通过调用getMessage()来实现,getMessage()是由Throwable定义的。

  • Throws

如果一个方法可以导致一个异常但不处理它,它必须指定这种行为以使方法的调用者可以保护它们自己而不发生异常。要做到这点,我们可以在方法声明中包含一个throws子句。一个throws子句列举了一个方法可能引发的所有异常类型。这对于除了Error或RuntimeException及它们子类以外类型的所有异常是必要的。一个方法可以引发的所有其他类型的异常必须在throws子句中声明,否则会导致编译错误

Throws的语法形式如下:

public void info() throws Exception
{
   //body of method
}

Exception 是该方法可能引发的所有的异常,也可以是异常列表,中间以逗号隔开。

例如:

class TestThrows{
    static void throw1() throws IllegalAccessException {
        System.out.println("Inside throw1 . ");
        throw new IllegalAccessException("demo");
    }
    public static void main(String[] args){
        try {
            throw1();
        }catch(IllegalAccessException e ){
            System.out.println("Caught " + e);
        }
    }
}

Throws抛出异常的规则:

如果是不受检查异常(unchecked exception),即Error、RuntimeException或它们的子类,那么可以不使用throws关键字来声明要抛出的异常,编译仍能顺利通过,但在运行时会被系统抛出。
必须声明方法可抛出的任何检查异常(checked exception)。即如果一个方法可能出现受可查异常,要么用try-catch语句捕获,要么用throws子句声明将它抛出,否则会导致编译错误。
仅当抛出了异常,该方法的调用者才必须处理或者重新抛出该异常。当方法的调用者无力处理该异常的时候,应该继续抛出,而不是囫囵吞枣。
调用方法必须遵循任何可查异常的处理和声明规则。若覆盖一个方法,则不能声明与覆盖方法不同的异常。声明的任何异常必须是被覆盖方法所声明异常的同类或子类。


问题扩展(面试题):
1、try{} 里有一个 return 语句,那么紧跟在这个 try 后的 finally{} 里的 code 会不会被执行,什么时候被执行,在 return 前还是后?

答案:会执行,在方法返回调用者前执行。

注意:在finally中改变返回值的做法是不好的,因为如果存在finally代码块,try中的return语句不会立马返回调用者,而是记录下返回值待finally代码块执行完毕之后再向调用者返回其值,然后如果在finally中修改了返回值,就会返回修改后的值。显然,在finally中返回或者修改返回值会对程序造成很大的困扰,C#中直接用编译错误的方式来阻止程序员干这种龌龊的事情,Java中也可以通过提升编译器的语法检查级别来产生警告或错误,Eclipse中可以在如图所示的地方进行设置,强烈建议将此项设置为编译错误。

在这里插入图片描述

2、Java语言如何进行异常处理,关键字:throws、throw、try、catch、finally分别如何使用?

:Java通过面向对象的方法进行异常处理,把各种不同的异常进行分类,并提供了良好的接口。在Java中,每个异常都是一个对象,它是Throwable类或其子类的实例。当一个方法出现异常后便抛出一个异常对象,该对象中包含有异常信息,调用这个对象的方法可以捕获到这个异常并可以对其进行处理。Java的异常处理是通过5个关键词来实现的:trycatchthrowthrowsfinally。一般情况下是用try来执行一段程序,如果系统会抛出(throw)一个异常对象,可以通过它的类型来捕获(catch)它,或通过总是执行代码块(finally)来处理;try用来指定一块预防所有异常的程序;catch子句紧跟在try块后面,用来指定你想要捕获的异常的类型;throw语句用来明确地抛出一个异常;throws用来声明一个方法可能抛出的各种异常(当然声明异常时允许无病呻吟);finally为确保一段代码不管发生什么异常状况都要被执行;try语句可以嵌套,每当遇到一个try语句,异常的结构就会被放入异常栈中,直到所有的try语句都完成。如果下一级的try语句没有对某种异常进行处理,异常栈就会执行出栈操作,直到遇到有处理这种异常的try语句或者最终将异常抛给JVM

3、运行时异常与受检异常有何异同?

:异常表示程序运行过程中可能出现的非正常状态,运行时异常表示虚拟机的通常操作中可能遇到的异常,是一种常见运行错误,只要程序设计得没有问题通常就不会发生。受检异常跟程序运行的上下文环境有关,即使程序设计无误,仍然可能因使用的问题而引发。Java编译器要求方法必须声明抛出可能发生的受检异常,但是并不要求必须声明抛出未被捕获的运行时异常。异常和继承一样,是面向对象程序设计中经常被滥用的东西,在Effective Java中对异常的使用给出了以下指导原则:

  • 不要将异常处理用于正常的控制流(设计良好的API不应该强迫它的调用者为了正常的控制流而使用异常)
  • 对可以恢复的情况使用受检异常,对编程错误使用运行时异常
  • 避免不必要的使用受检异常(可以通过一些状态检测手段来避免异常的发生)
  • 优先使用标准的异常
  • 每个方法抛出的异常都要有文档
  • 保持异常的原子性
  • 不要在catch中忽略掉捕获到的异常

4、列出一些你常见的运行时异常?

  • ArithmeticException(算术异常)
  • ClassCastException (类转换异常)
  • IllegalArgumentException (非法参数异常)
  • IndexOutOfBoundsException (下标越界异常)
  • NullPointerException (空指针异常)
  • SecurityException (安全异常)

自定义异常

使用Java内置的异常类可以描述在编程时出现的大部分异常情况。除此之外,用户还可以自定义异常。用户自定义异常类,只需继承Exception类即可。

在程序中使用自定义异常类,大体可分为以下几个步骤:

  • 创建自定义异常类。
  • 在方法中通过throw关键字抛出异常对象。
  • 如果在当前抛出异常的方法中处理异常,可以使用try-catch语句捕获并处理;否则在方法的声明处通过throws关键字指明要抛出给方法调用者的异常,继续进行下一步操作。
  • 在出现异常方法的调用者中捕获并处理异常。

举例自定义异常:

class MyException extends Exception {
   private int detail;
   MyException(int a){
       detail = a;
   }
   public String toString(){
       return "MyException ["+ detail + "]";
   }
}
public class TestMyException{
   static void compute(int a) throws MyException{
       System.out.println("Called compute(" + a + ")");
       if(a > 10){
           throw new MyException(a);
       }
       System.out.println("Normal exit!");
   }
   public static void main(String [] args){
       try{
           compute(1);
           compute(20);
       }catch(MyException me){
           System.out.println("Caught " + me);
       }
   }
}

运行结果如下:

D:\java>java TestMyException

Called compute(1)

Normal exit!

Called compute(20)

Caught MyException [20]

在这里插入图片描述


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值