异常的基本概念
在程序运行过程中出现的错误,称为异常。
Java中异常以类和对象的形式存在,有利于增强程序的健壮性。
异常的分类
异常的层次结构
Throwable下有两个分支:Error(不可处理,直接退出JVM)和Exception(可处理的)
Exception下有两个分支:
Exception的直接子类:编译时异常(要求程序员在编写程序阶段必须预先对这些异常进行处理,如果不处理编译器报错,因此得名编译时异常。)。
RuntimeException:运行时异常。(在编写程序阶段程序员可以预先处理,也可以不管,都行。)
异常的分类
异常主要分为:错误、一般性异常(编译时异常、受控异常)、运行期异常(非受控异常)
- 错误:应用程序出现Error将无法恢复,只能重新启动应用程序
- 受控异常:该异常必须显示处理,不显示处理Java程序将无法编译通过
- 非受控异常:该异常可以不用显示处理,如被0除异常。
编译时异常和运行时异常均发生在运行阶段,因为异常的发生就是new异常对象,只有程序运行才可以new对象。
try、catch和finally
异常的捕获和处理需要采用try和catch处理,具体格式如下:
try{
// 可能产生异常的代码
}catch(){
// catch可以有多个,catch中是需要捕获的异常
}catch(){
// try中代码出现异常时,异常下面的代码不会执行,
// 马上会跳转到相应的catch语块中,如果没有异常不会跳转到catch中
}finally{
// finally表示不管是否存在异常,此处的代码都将执行。
// finally和catch可以分开使用,但必须与try一起使用。
}
getMessage和printStackTrace()
取得异常对象的具体信息,常用的方法主要有两种:
- 取得异常的描述信息:getMessage()
- 取得异常的堆栈信息(适合于程序调试阶段):printStackTrace()
public class ExceptionTest {
public static void main(String[] args) {
int a=100;
int b=0;
try{
int res=a/b;
}catch(ArithmeticException ae){
// ae是一个引用,指向堆中的ArithmeticException
// 通过getMessage可以取得异常的描述信息
// 通过printStackTrace可以打印栈结构
System.out.println(ae.getMessage());
ae.printStackTrace();
}
}
}
/ by zero
java.lang.ArithmeticException: / by zero
at InterfaceTest.ExceptionTest.main(ExceptionTest.java:8)
受控异常
public class ExceptionTest {
public static void main(String[] args) {
FileInputStream fis =new FileInputStream("test.txt");
}
}
此处会抛出异常:Error:(7, 30) java: 未报告的异常错误java.io.FileNotFoundException; 必须对其进行捕获或声明以便抛出。从此可以看出,程序无法编译,此类异常叫做“受控异常”。
处理FileNotFoundException异常:Alt+Enter选择捕捉(try catch)或抛出(throws)
import java.io.FileInputStream;
import java.io.FileNotFoundException;
public class ExceptionTest {
public static void main(String[] args) {
try {
FileInputStream fis =new FileInputStream("test.txt");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
finally
finally在任何情况下都会执行,因此通常在finally关闭资源
public class ExceptionTest {
public static void main(String[] args) {
// fis作用域问题,需要放在try语句块外
// 局部变量必须给定初始值
FileInputStream fis=null;
try{
fis=new FileInputStream("test.txt");
}catch (FileNotFoundException e){
e.printStackTrace();
}finally {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
只有Java虚拟机退出(System.exit(-1)
)不会执行finally,其他任何情况都会执行finally!
异常的处理
第一种方式:在方法声明的位置上,使用throws关键字,抛给上一级。
谁调用我,我就抛给谁。抛给上一级。
第二种方式:使用try…catch语句进行异常的捕捉。
这件事发生了,谁也不知道,因为我给抓住了。
Java中异常发生之后如果一直上抛,最终抛给了main方法,main方法继续向上抛,抛给了调用者JVM,JVM知道这个异常发生,只有一个结果即终止java程序的执行。
如何声明异常
在方法定义处采用throws声明异常,如果声明的异常为受控异常,那么调用方法必须处理该异常。
- 声明受控异常
public class ExceptionTest {
public static void main(String[] args){
// throws FileNotFoundException,IOException{ //可以在此声明异常,交给Java虚拟机处理,不建议在此处声明
// throws Exception{ //可以采用此种方式声明异常,因为Exception是两个异常的父类
try {
readFile(); // 声明异常后,调用者必须处理
} catch (FileNotFoundException e) {
e.printStackTrace();
}catch (IOException e) {
e.printStackTrace();
}
}
private static void readFile() throws FileNotFoundException,IOException{
FileInputStream fis=null;
try {
fis=new FileInputStream("test.txt");
}finally {
fis.close();
}
}
}
- 声明非受控异常
public class ExceptionTest {
public static void main(String[] args){
// 不需要try catch,因为声明的是非受控异常
// method1();
// 也可以拦截非受控异常
try {
method1();
}catch (ArithmeticException e){
e.printStackTrace();
}
}
private static void method1() throws ArithmeticException{
int a=10;
int b=0;
int res=a/b;
System.out.println(res);
}
}
如何手动抛出异常
public class ExceptionTest {
public static void main(String[] args) {
try {
int res = method(1000, 5);
System.out.println(res);
}catch (IllegalArgumentException e){
e.printStackTrace();
}
}
private static int method(int a,int b){
if (b==0){
// 手动抛出异常
throw new IllegalArgumentException("除数为0");
}
if (!(a>0 && a<=100)){
throw new IllegalArgumentException("被除数应介于1~100之间");
}
return a/b;
}
}
throws和throw的区别?throws是声明异常,throw是抛出异常。
异常的捕获顺序是从小到大的,先截获子异常再截获父异常,因此catch时将子异常放到前面。
如何自定义异常
自定义异常通常继承于Exception或RuntimeException,具体视情况而定。
- 自定义受控异常
public class ExceptionTest {
public static void main(String[] args) {
try {
method(1000, 0);
}catch (MyException e){
// 必须拦截,拦截后给出处理
// 否则属于吃掉了该异常,系统将不给任何提示,使程序调试变得困难
e.printStackTrace();
}
}
private static int method(int a,int b)
throws MyException{ // 如果是受控异常必须声明
if (b==0){
throw new MyException("除数为0");
}
return a/b;
}
}
// 自定义受控异常
class MyException extends Exception{
public MyException(){
// 调用父类的默认构造方法
super();
}
public MyException(String message){
// 手动调用父类的构造方法
super(message);
}
}
- 自定义非受控异常
public class ExceptionTest {
public static void main(String[] args) {
method(100,0);
}
private static int method(int a,int b) {
if (b==0){
// 抛出非受控异常
throw new MyException("除数为0");
}
return a/b;
}
}
// 自定义非受控异常
class MyException extends RuntimeException{
public MyException(){
// 调用父类的默认构造方法
super();
}
public MyException(String message){
// 手动调用父类的构造方法
super(message);
}
}
方法覆盖与异常
子类方法不能抛出比父类方法更多的异常,但可以抛出父类方法异常的子异常。