第二十四天 注解 和 异常
一、认识注解
注解(Annotation)很重要,现在的开发模式都是基于注解的,JPA是基于注解的,从Spring基于注解的,从Hibernate也是基于注解的,注解是JDK1.5之后才有的新特性
JDK1.5之后内部提供的三个注解
@Deprecated 意思是“废弃的,过时的”
@Override 意思是“重写、覆盖”
@SuppressWarnings 意思是“压缩警告”,作用:用于抑制编译器产生警告信息。
通过例子,用一下这三个jdk原生注解,演示注解的基本应用:
public class Demo { public static void main(String[] args) { show(); // 这一行注解的作用就是让未用的变量不提示 @SuppressWarnings("unused")//可以写在成员变量前,主方法前,类前,作用范围不一样 String string = "ssss"; } @Deprecated public static void show() { // 表示这个方法,过时了,不推荐使用了 但可以使用 System.out.println("show"); } public static void show1() { System.out.println("show1"); } @Override //作为警示,提醒你这是一个覆盖父类的同名方法,如果不同名会报错 public String toString() { return "Demo []"; } }
总结:注解(Annotation)相当于一种标记,在程序中加入注解就等于为程序打上某种标记,没有加,则等于没有任何标记,以后,javac编译器、开发工具和其他程序可以通过反射来了解你的类及各种元素上有无何种标记,看你的程序有什么标记,就去干相应的事,标记可以加在包、类,属性、方法,方法的参数以及局部变量上。
注解就相当于一个你的源程序要调用一个类,在源程序中应用某个注解,得事先准备好这个注解类。就像你要调用某个类,得事先开发好这个类。
二、自定义注解及其应用
自定义一个最简单的注解:
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /*@Retention(RetentionPolicy.SOURCE) * 这个注解的意思是让MyAnnotation注解只在java源文件中存在,编译成.class文件后注解就不存在了 * @Retention(RetentionPolicy.CLASS) * 这个注解的意思是让MyAnnotation注解在java源文件(.java文件)中存在,编译成.class文件后注解也还存在, * 被MyAnnotation注解类标识的类被类加载器加载到内存中后MyAnnotation注解就不存在了 * Retention注解括号中的"RetentionPolicy.RUNTIME"意思是让MyAnnotation这个注解的生命周期一直到程序运行时都存在 */ //source 为.java class为.calss runtime为jvm @Retention(RetentionPolicy.RUNTIME)//@Retention这个元注解作用,指定自定义的注解类的生命周期 //Target注解决定MyAnnotation注解可以加在哪些成分上,如加在类身上,或者属性身上,或者方法身上等成分 @Target(value = {ElementType.TYPE,ElementType.METHOD,ElementType.FIELD}) public @interface MyAnnotation { //自定义类的基本属性 }
把自定义的注解加到某个类上:
@MyAnnotation public class Demo { public static void main(String[] args) { if (Demo.class.isAnnotationPresent(MyAnnotation.class)) { System.out.println("有注解"); MyAnnotation annotation =Demo.class.getAnnotation(MyAnnotation.class);//获取注解的对象 System.out.println(annotation);//@coursetest.MyAnnotation() }else { System.out.println("没有注解"); } } }
三、@Retention元注解
根据反射的测试的问题,引出@Retention元注解的讲解:其三种取值:
RetentionPolicy.SOURCE、RetentionPolicy.CLASS、RetentionPolicy.RUNTIME分别对应:Java源文件(.java文件)---->.class文件---->内存中的字节码
四、@Target元注解
@Target元注解决定了一个注解可以标识到哪些成分上,如标识在在类身上,或者属性身上,或者方法身上等成分,@Target默认值为任何元素(成分)
例如:
1 @Target(value={TYPE,FIELD,METHOD,PARAMETER,CONSTRUCTOR,LOCAL_VARIABLE}) 2 @Retention(value=SOURCE) 3 public @interface SuppressWarnings
五、为注解增加属性
注解可以看成是一种特殊的类,既然是类,那自然可以为类添加属性
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /*@Retention(RetentionPolicy.SOURCE) * 这个注解的意思是让MyAnnotation注解只在java源文件中存在,编译成.class文件后注解就不存在了 * @Retention(RetentionPolicy.CLASS) * 这个注解的意思是让MyAnnotation注解在java源文件(.java文件)中存在,编译成.class文件后注解也还存在, * 被MyAnnotation注解类标识的类被类加载器加载到内存中后MyAnnotation注解就不存在了 * Retention注解括号中的"RetentionPolicy.RUNTIME"意思是让MyAnnotation这个注解的生命周期一直到程序运行时都存在 */ //source 为.java class为.calss runtime为jvm @Retention(RetentionPolicy.RUNTIME)//@Retention这个元注解作用,指定自定义的注解类的生命周期 //Target注解决定MyAnnotation注解可以加在哪些成分上,如加在类身上,或者属性身上,或者方法身上等成分 @Target(value = {ElementType.TYPE,ElementType.METHOD,ElementType.FIELD}) public @interface MyAnnotation { //自定义类的基本属性 String color() default "blue";//为属性指定缺省值 String value();//定义一个名称为value的属性 }
import java.util.ArrayList; import java.util.List; import org.omg.CORBA.PUBLIC_MEMBER; //@MyAnnotation("xxx")//等价于@MyAnnotation(value="xxx") @MyAnnotation ("aaa")//(color = "red") //String color() default "blue";注解里定义默认后可以不写属性值 public class Demo { public static void main(String[] args) { if (Demo.class.isAnnotationPresent(MyAnnotation.class)) { System.out.println("有注解"); MyAnnotation annotation =Demo.class.getAnnotation(MyAnnotation.class);//获取注解的对象 System.out.println(annotation);//@coursetest.MyAnnotation() System.out.println(annotation.color());//red 默认值为blue }else { System.out.println("没有注解"); } } }
1.添加属性
语法:类型 属性名()
其实从代码的写法上来看,注解更像是一种特殊的接口,注解的属性定义方式就和接口中定义方法的方式一样,而应用了注解的类可以认为是实现了这个特殊的接口
2.应用属性
3.为属性指定缺省值(默认值)
语法:类型 属性名() default 默认值;
4.value属性
如果一个注解中有一个名称为value的属性,且你只想设置value属性(即其他属性都采用默认值或者你只有一个value属性),那么可以省略掉“value=”部分。
六、为注解增加高级属性
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /*@Retention(RetentionPolicy.SOURCE) * 这个注解的意思是让MyAnnotation注解只在java源文件中存在,编译成.class文件后注解就不存在了 * @Retention(RetentionPolicy.CLASS) * 这个注解的意思是让MyAnnotation注解在java源文件(.java文件)中存在,编译成.class文件后注解也还存在, * 被MyAnnotation注解类标识的类被类加载器加载到内存中后MyAnnotation注解就不存在了 * Retention注解括号中的"RetentionPolicy.RUNTIME"意思是让MyAnnotation这个注解的生命周期一直到程序运行时都存在 */ //source 为.java class为.calss runtime为jvm @Retention(RetentionPolicy.RUNTIME)//@Retention这个元注解作用,指定自定义的注解类的生命周期 //Target注解决定MyAnnotation注解可以加在哪些成分上,如加在类身上,或者属性身上,或者方法身上等成分 @Target(value = {ElementType.TYPE,ElementType.METHOD,ElementType.FIELD}) public @interface MyAnnotation { //自定义类的基本属性 String color() default "blue";//为属性指定缺省值 int[] value();//定义一个名称为value的属性 }
import java.util.ArrayList; import java.util.List; import org.omg.CORBA.PUBLIC_MEMBER; //@MyAnnotation("xxx")//等价于@MyAnnotation(value="xxx") @MyAnnotation (value = {11,22,33})//(color = "red") //String color() default "blue";注解里定义默认后可以不写属性值 public class Demo { public static void main(String[] args) { if (Demo.class.isAnnotationPresent(MyAnnotation.class)) { System.out.println("有注解"); MyAnnotation annotation =Demo.class.getAnnotation(MyAnnotation.class);//获取注解的对象 System.out.println(annotation);//@coursetest.MyAnnotation() System.out.println(annotation.color());//red 默认值为blue }else { System.out.println("没有注解"); } } }
1、数组类型的属性
· 增加数组类型的属性:int[] arrayAttr() default {1,2,4};
· 应用数组类型的属性:@MyAnnotation(arrayAttr={2,4,5})
· 如果数组属性只有一个值,这时候属性值部分可以省略大括号,如:@MyAnnotation(arrayAttr=2),这就表示数组属性只有一个值,值为2
2、枚举类型的属性
· 增加枚举类型的属性:EumTrafficLamp lamp() default EumTrafficLamp.RED;
· 应用枚举类型的属性:@MyAnnotation(lamp=EumTrafficLamp.GREEN)
3、注解的属性值又是一个注解
· MetaAnnotation annotationAttr() default @MetaAnnotation("xdp");
七、注解综合测试
public enum EumTrafficLamp { RED, // 红 YELLOW, // 黄 GREEN// 绿 }
public @interface MetaAnnotation { String value();//元注解MetaAnnotation设置有一个唯一的属性value }
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) //Retention注解决定MyAnnotation注解的生命周期 @Target({ ElementType.METHOD, ElementType.TYPE }) public @interface MyAnnotation { String color() default "blue";// 为属性指定缺省值 String value();// 定义一个名称为value的属性 // 添加一个int类型数组的属性 int[] arrayAttr() default { 1, 2, 4 }; EumTrafficLamp lamp() default EumTrafficLamp.RED; // 为注解添加一个注解类型的属性,并指定注解属性的缺省值 MetaAnnotation annotationAttr() default @MetaAnnotation("xdp"); }
@MyAnnotation( color="red", value="xxx", arrayAttr={3,5,6}, lamp=EumTrafficLamp.GREEN, annotationAttr=@MetaAnnotation("gacl") ) public class MyAnnotationTest { public static void main(String[] args) { /** * 这里是检查Annotation类是否有注解,这里需要使用反射才能完成对Annotation类的检查 */ if(MyAnnotationTest.class.isAnnotationPresent(MyAnnotation.class)) { /** * 用反射方式获得注解对应的实例对象后,在通过该对象调用属性对应的方法 * MyAnnotation是一个类,这个类的实例对象annotation是通过反射得到的,这个实例对象是如何创建的呢? * 一旦在某个类上使用了@MyAnnotation,那么这个MyAnnotation类的实例对象annotation就会被创建出来了 */ MyAnnotation annotation = (MyAnnotation) MyAnnotationTest.class.getAnnotation(MyAnnotation.class); System.out.println(annotation.color());//输出color属性的默认值:red System.out.println(annotation.value());//输出value属性的默认值:孤傲苍狼 System.out.println(annotation.arrayAttr().length);//这里输出的数组属性的长度的结果为:3,数组属性有三个元素,因此数组的长度为3 System.out.println(annotation.lamp());//这里输出的枚举属性值为:GREEN System.out.println(annotation.annotationAttr().value());//这里输出的注解属性值:gacl MetaAnnotation ma = annotation.annotationAttr();//annotation是MyAnnotation类的一个实例对象 System.out.println(ma.value());//输出的结果为:gacl } } }
这里我们只是简单地获取了一下在类上的注解对象,在通过注解对象获取了注解属性的值,以后我们讲了反射,知道怎么获取方法对象后,在通过方法对象,获取方法上的注解,获取方法和上面的雷同的!
异常简介
程序运行时,发生的不被期望的事件,它阻止了程序按照程序员的预期正常执行,这就是异常。异常发生时,是任程序自生自灭,立刻退出终止?还是输出错误给用户?
Java提供了更加优秀的解决办法:异常处理机制。
异常处理机制能让程序在异常发生时,按照代码的预先设定的异常处理逻辑,针对性地处理异常,让程序尽最大可能恢复正常并继续执行,且保持代码的清晰。
Java中的异常可以是函数中的语句执行时引发的,也可以是程序员通过throw 语句手动抛出的,只要在Java程序中产生了异常,就会用一个对应类型的异常对象来封装异常,JRE就会试图寻找异常处理程序来处理异常。
Throwable类是Java异常类型的顶层父类,一个对象只有是 Throwable 类的(直接或者间接)实例,他才是一个异常对象,才能被异常处理机制识别。JDK中内建了一些常用的异常类,我们也可以自定义异常。
Java中的异常类结构图
Java标准库内建了一些通用的异常,这些类以Throwable为顶层父类。
Throwable又派生出Error类和Exception类。
错误:Error类以及他的子类的实例,代表了JVM本身的错误。错误不能被程序员通过代码处理,Error很少出现。因此,程序员应该关注Exception为父类的分支下的各种异常类。
异常:Exception以及他的子类,代表程序运行时发送的各种不期望发生的事件。可以被Java异常处理机制使用,是异常处理的核心。
总体上我们根据Javac对异常的处理要求,将异常类分为2类。
非检查异常(unckecked exception):Error 和 RuntimeException 以及他们的子类。javac在编译时,不会提示和发现这样的异常,不要求在程序处理这些异常。所以如果愿意,我们可以编写代码处理(使用try…catch…finally)这样的异常,也可以不处理。对于这些异常,我们应该修正代码,而不是去通过异常处理器处理 。这样的异常发生的原因多半是代码写的有问题。如除0错误ArithmeticException,错误的强制类型转换错误ClassCastException,数组索引越界ArrayIndexOutOfBoundsException,使用了空对象NullPointerException等等。
检查异常(checked exception):除了Error 和 RuntimeException的其它异常。javac强制要求程序员为这样的异常做预备处理工作(使用try…catch…finally或者throws)。在方法中要么用try-catch语句捕获它并处理,要么用throws子句声明抛出它,否则编译不会通过。这样的异常一般是由程序的运行环境导致的。因为程序可能被运行在各种未知的环境下,而程序员无法干预用户如何使用他编写的程序,于是程序员就应该为这样的异常时刻准备着。如SQLException , IOException,ClassNotFoundException 等。
需要明确的是:检查和非检查是对于javac来说的,这样就很好理解和区分了。
初识异常
下面的代码会演示2个异常类型:ArithmeticException 和 InputMismatchException。前者由于整数除0引发,后者是输入的数据不能被转换为int类型引发。
public class Demo { public static void main(String[] args) { double d = 12/0;//在这里程序出现异常,程序被迫终止了 System.out.println("sss"); System.out.println(d); } }
异常是在执行某个函数时引发的,而函数又是层级调用,形成调用栈的,因为,只要一个函数发生了异常,那么他的所有的caller都会被异常影响。当这些被影响的函数以异常信息输出时,就形成的了异常追踪栈。
异常最先发生的地方,叫做异常抛出点。
从上面的例子可以看出,当devide函数发生除0异常时,devide函数将抛出ArithmeticException异常,因此调用他的CMDCalculate函数也无法正常完成,因此也发送异常,而CMDCalculate的caller——main 因为CMDCalculate抛出异常,也发生了异常,这样一直向调用栈的栈底回溯。这种行为叫做异常的冒泡,异常的冒泡是为了在当前发生异常的函数或者这个函数的caller中找到最近的异常处理程序。
由于这个例子中没有使用任何异常处理机制,因此异常最终由main函数抛给JRE,导致程序终止。
异常处理
上面的代码不使用异常处理机制,也可以顺利编译,因为异常是非检查异常。但是下面的例子就必须使用异常处理机制,因为异常是检查异常。
代码中我选择使用throws声明异常,让函数的调用者去处理可能发生的异常。但是为什么只throws了IOException呢?因为FileNotFoundException是IOException的子类,在处理范围内。
public class Demo { public static void main(String[] args) { try { FileInputStream fis = new FileInputStream("D://a.txt"); } catch (FileNotFoundException e) { e.printStackTrace();//在命令行打印异常信息在程序中出错的位置及原因 System.out.println("文件没找到"); } } }
public class Demo { public static void main(String[] args) throws FileNotFoundException { FileInputStream fis = new FileInputStream("D://a.txt"); } }
异常处理的基本语法
在编写代码处理异常时,对于检查异常,有2种不同的处理方式:
1. 使用try…catch…finally语句块处理它。
2. 或者,在函数签名中使用throws 声明交给函数调用者caller去解决。
try…catch…finally语句块
try{
//try块中放可能发生异常的代码。
//如果执行完try且不发生异常,则接着去执行finally块和finally后面的代码(如果有的话)。
//如果发生异常,则尝试去匹配catch块。
}catch(SQLException SQLexception){
//每一个catch块用于捕获并处理一个特定的异常,或者这异常类型的子类。Java7中可以将多个异常声明在一个catch中。
//catch后面的括号定义了异常类型和异常参数。如果异常与之匹配且是最先匹配到的,则虚拟机将使用这个catch块来处理异常。
//在catch块中可以使用这个块的异常参数来获取异常的相关信息。异常参数是这个catch块中的局部变量,其它块不能访问。
//如果当前try块中发生的异常在后续的所有catch中都没捕获到,则先去执行finally,然后到这个函数的外部caller中去匹配异常处理器。
//如果try中没有发生异常,则所有的catch块将被忽略。
}catch(Exception exception){
//...
}finally{
//finally块通常是可选的。
//无论异常是否发生,异常是否匹配被处理,finally都会执行。
//一个try至少要有一个catch块,否则, 至少要有1个finally块。但是finally不是用来处理异常的,finally不会捕获异常。
//finally主要做一些清理工作,如流的关闭,数据库连接的关闭等。
}
public class Demo { public static void main(String[] args) { try { FileInputStream fis = new FileInputStream("D://a.txt"); } catch (FileNotFoundException e) { e.printStackTrace(); }catch(Exception e) {//多个catch 有大小级别顺序 e.printStackTrace(); } finally { } } }
需要注意的地方
1、try块中的局部变量和catch块中的局部变量(包括异常变量),以及finally中的局部变量,他们之间不可共享使用。
2、每一个catch块用于处理一个异常。异常匹配是按照catch块的顺序从上往下寻找的,只有第一个匹配的catch会得到执行。
3、java中,异常处理的任务就是将执行控制流从异常发生的地方转移到能够处理这种异常的地方去。
public class Demo { public static void main(String[] args) { try { FileInputStream fis = new FileInputStream("D://a.txt"); } catch (FileNotFoundException e) { System.out.println(e.getMessage());//D:\a.txt (系统找不到指定的文件。) } finally { } } }
public class Demo { public static void main(String[] args) { try { FileInputStream fis = new FileInputStream("D://a.txt"); int i = 1;//这里有i } catch (FileNotFoundException e) { int i = 2;//这里也有i 不冲突 } } }
throws 函数声明
throws声明:如果一个方法内部的代码会抛出检查异常(checked exception),而方法自己又没有完全处理掉,则javac保证你必须在方法的签名上使用throws关键字声明这些可能抛出的异常,否则编译不通过。
throws是另一种处理异常的方式,它不同于try…catch…finally,throws仅仅是将函数中可能出现的异常向调用者声明,而自己则不具体处理。
采取这种异常处理的原因可能是:方法本身不知道如何处理这样的异常,或者说让调用者处理更好,调用者需要为可能发生的异常负责。
public void foo() throws ExceptionType1 , ExceptionType2 ,ExceptionTypeN
{
//foo内部可以抛出 ExceptionType1 , ExceptionType2 ,ExceptionTypeN 类的异常,或者他们的子类的异常对象。
}
throw 异常抛出语句
throw exceptionObject
程序员也可以通过throw语句手动显式的抛出一个异常。throw语句的后面必须是一个异常对象。
throw 语句必须写在函数中,执行throw 语句的地方就是一个异常抛出点,它和由JRE自动形成的异常抛出点没有任何差别。
public class Demo { public static void main(String[] args) throws FileNotFoundException { throw new RuntimeException( "throw抛出异常的用法举例"); } }
简介
程序运行时,发生的不被期望的事件,它阻止了程序按照程序员的预期正常执行,这就是异常。异常发生时,是任程序自生自灭,立刻退出终止?还是输出错误给用户?
Java提供了更加优秀的解决办法:异常处理机制。
异常处理机制能让程序在异常发生时,按照代码的预先设定的异常处理逻辑,针对性地处理异常,让程序尽最大可能恢复正常并继续执行,且保持代码的清晰。
Java中的异常可以是函数中的语句执行时引发的,也可以是程序员通过throw 语句手动抛出的,只要在Java程序中产生了异常,就会用一个对应类型的异常对象来封装异常,JRE就会试图寻找异常处理程序来处理异常。
Throwable类是Java异常类型的顶层父类,一个对象只有是 Throwable 类的(直接或者间接)实例,他才是一个异常对象,才能被异常处理机制识别。JDK中内建了一些常用的异常类,我们也可以自定义异常。
Java中的异常类结构图
Java标准库内建了一些通用的异常,这些类以Throwable为顶层父类。
Throwable又派生出Error类和Exception类。
错误:Error类以及他的子类的实例,代表了JVM本身的错误。错误不能被程序员通过代码处理,Error很少出现。因此,程序员应该关注Exception为父类的分支下的各种异常类。
异常:Exception以及他的子类,代表程序运行时发送的各种不期望发生的事件。可以被Java异常处理机制使用,是异常处理的核心。
总体上我们根据Javac对异常的处理要求,将异常类分为2类。
非检查异常(unckecked exception):Error 和 RuntimeException 以及他们的子类。javac在编译时,不会提示和发现这样的异常,不要求在程序处理这些异常。所以如果愿意,我们可以编写代码处理(使用try…catch…finally)这样的异常,也可以不处理。对于这些异常,我们应该修正代码,而不是去通过异常处理器处理 。这样的异常发生的原因多半是代码写的有问题。如除0错误ArithmeticException,错误的强制类型转换错误ClassCastException,数组索引越界ArrayIndexOutOfBoundsException,使用了空对象NullPointerException等等。
检查异常(checked exception):除了Error 和 RuntimeException的其它异常。javac强制要求程序员为这样的异常做预备处理工作(使用try…catch…finally或者throws)。在方法中要么用try-catch语句捕获它并处理,要么用throws子句声明抛出它,否则编译不会通过。这样的异常一般是由程序的运行环境导致的。因为程序可能被运行在各种未知的环境下,而程序员无法干预用户如何使用他编写的程序,于是程序员就应该为这样的异常时刻准备着。如SQLException , IOException,ClassNotFoundException 等。
需要明确的是:检查和非检查是对于javac来说的,这样就很好理解和区分了。
初识异常
下面的代码会演示2个异常类型:ArithmeticException 和 InputMismatchException。前者由于整数除0引发,后者是输入的数据不能被转换为int类型引发。
package com.example;
import java. util .Scanner ;
public class AllDemo
{
public static void main (String [] args )
{
System . out. println( "----欢迎使用命令行除法计算器----" ) ;
CMDCalculate ();
}
public static void CMDCalculate ()
{
Scanner scan = new Scanner ( System. in );
int num1 = scan .nextInt () ;
int num2 = scan .nextInt () ;
int result = devide (num1 , num2 ) ;
System . out. println( "result:" + result) ;
scan .close () ;
}
public static int devide (int num1, int num2 ){
return num1 / num2 ;
}
}
/*****************************************
----欢迎使用命令行除法计算器----
0
Exception in thread "main" java.lang.ArithmeticException : / by zero
at com.example.AllDemo.devide( AllDemo.java:30 )
at com.example.AllDemo.CMDCalculate( AllDemo.java:22 )
at com.example.AllDemo.main( AllDemo.java:12 )
----欢迎使用命令行除法计算器----
r
Exception in thread "main" java.util.InputMismatchException
at java.util.Scanner.throwFor( Scanner.java:864 )
at java.util.Scanner.next( Scanner.java:1485 )
at java.util.Scanner.nextInt( Scanner.java:2117 )
at java.util.Scanner.nextInt( Scanner.java:2076 )
at com.example.AllDemo.CMDCalculate( AllDemo.java:20 )
at com.example.AllDemo.main( AllDemo.java:12 )
*****************************************/
异常是在执行某个函数时引发的,而函数又是层级调用,形成调用栈的,因为,只要一个函数发生了异常,那么他的所有的caller都会被异常影响。当这些被影响的函数以异常信息输出时,就形成的了异常追踪栈。
异常最先发生的地方,叫做异常抛出点。
从上面的例子可以看出,当devide函数发生除0异常时,devide函数将抛出ArithmeticException异常,因此调用他的CMDCalculate函数也无法正常完成,因此也发送异常,而CMDCalculate的caller——main 因为CMDCalculate抛出异常,也发生了异常,这样一直向调用栈的栈底回溯。这种行为叫做异常的冒泡,异常的冒泡是为了在当前发生异常的函数或者这个函数的caller中找到最近的异常处理程序。
由于这个例子中没有使用任何异常处理机制,因此异常最终由main函数抛给JRE,导致程序终止。
异常处理
上面的代码不使用异常处理机制,也可以顺利编译,因为异常是非检查异常。但是下面的例子就必须使用异常处理机制,因为异常是检查异常。
代码中我选择使用throws声明异常,让函数的调用者去处理可能发生的异常。但是为什么只throws了IOException呢?因为FileNotFoundException是IOException的子类,在处理范围内。
@Test
public void testException() throws IOException
{
//FileInputStream的构造函数会抛出FileNotFoundException
FileInputStream fileIn = new FileInputStream("E:\\a.txt");
int word;
//read方法会抛出IOException
while((word = fileIn.read())!=-1)
{
System.out.print((char)word);
}
//close方法会抛出IOException
fileIn.close();
}
异常处理的基本语法
在编写代码处理异常时,对于检查异常,有2种不同的处理方式:
1. 使用try…catch…finally语句块处理它。
2. 或者,在函数签名中使用throws 声明交给函数调用者caller去解决。
try…catch…finally语句块
try{
//try块中放可能发生异常的代码。
//如果执行完try且不发生异常,则接着去执行finally块和finally后面的代码(如果有的话)。
//如果发生异常,则尝试去匹配catch块。
}catch(SQLException SQLexception){
//每一个catch块用于捕获并处理一个特定的异常,或者这异常类型的子类。Java7中可以将多个异常声明在一个catch中。
//catch后面的括号定义了异常类型和异常参数。如果异常与之匹配且是最先匹配到的,则虚拟机将使用这个catch块来处理异常。
//在catch块中可以使用这个块的异常参数来获取异常的相关信息。异常参数是这个catch块中的局部变量,其它块不能访问。
//如果当前try块中发生的异常在后续的所有catch中都没捕获到,则先去执行finally,然后到这个函数的外部caller中去匹配异常处理器。
//如果try中没有发生异常,则所有的catch块将被忽略。
}catch(Exception exception){
//...
}finally{
//finally块通常是可选的。
//无论异常是否发生,异常是否匹配被处理,finally都会执行。
//一个try至少要有一个catch块,否则, 至少要有1个finally块。但是finally不是用来处理异常的,finally不会捕获异常。
//finally主要做一些清理工作,如流的关闭,数据库连接的关闭等。
}
需要注意的地方
1、try块中的局部变量和catch块中的局部变量(包括异常变量),以及finally中的局部变量,他们之间不可共享使用。
2、每一个catch块用于处理一个异常。异常匹配是按照catch块的顺序从上往下寻找的,只有第一个匹配的catch会得到执行。
3、java中,异常处理的任务就是将执行控制流从异常发生的地方转移到能够处理这种异常的地方去。
public static void main(String[] args){
try {
foo();
}catch(ArithmeticException ae) {
System.out.println("处理异常");
}
}
public static void foo(){
int a = 5/0; //异常抛出点
System.out.println("为什么还不给我涨工资!!!"); //////////////////////不会执行
}
throws 函数声明
throws声明:如果一个方法内部的代码会抛出检查异常(checked exception),而方法自己又没有完全处理掉,则javac保证你必须在方法的签名上使用throws关键字声明这些可能抛出的异常,否则编译不通过。
throws是另一种处理异常的方式,它不同于try…catch…finally,throws仅仅是将函数中可能出现的异常向调用者声明,而自己则不具体处理。
采取这种异常处理的原因可能是:方法本身不知道如何处理这样的异常,或者说让调用者处理更好,调用者需要为可能发生的异常负责。
public void foo() throws ExceptionType1 , ExceptionType2 ,ExceptionTypeN
{
//foo内部可以抛出 ExceptionType1 , ExceptionType2 ,ExceptionTypeN 类的异常,或者他们的子类的异常对象。
}
throw 异常抛出语句
throw exceptionObject
程序员也可以通过throw语句手动显式的抛出一个异常。throw语句的后面必须是一个异常对象。
throw 语句必须写在函数中,执行throw 语句的地方就是一个异常抛出点,它和由JRE自动形成的异常抛出点没有任何差别。
public void save(User user)
{
if(user == null)
throw new IllegalArgumentException("User对象为空");
//......
}