第9章 Java异常处理与反射机制
程序在运行中总会有一些意料不到的情况或出现异常情况,如接收电子邮件的时候发现密码错误、被0除、对负数进行平方根计算。还有可能出现能致命的错误,如内存不足或内存溢出。异常处理功能提供了处理这些运行时出现的任何意外或异常情况的方法。异常处理使用try、catch和finally关键字来尝试可能未成功的操作,处理失败以及在事后清理资源。本章介绍如何处理异常以及自定义异常类。
实例73 运用throws、throw、try与catch
在调试航班信息时,可能会遇到这样的情况:应用程序运行过程中,突然中止,屏幕上出现一大堆英文,让人不知所措。在许多城市,银行AT M机随处可见,取款非常方便,在ATM取款机上必须按步骤操作,若操作有误,将会出现一些错误信息,如:“密码输出错误,请重新输入”。这些都认为是程序出了异常。本实例运用关键字介绍如何捕获异常、抛出异常以及对异常的控制。
技术要点
运用throws、throw、try与catch关键字的技术要点如下:
• throws用来声明一个方法可能抛出的所有异常信息。通常不用显式地捕获异常,可由系统自动将所有捕获的异常信息抛给上级方法。throws的Exception的取值范围要大于方法内部异常的最大范围。通常throws抛出的是类。
• throw是指抛出的一个具体的异常类型。throw需要用户自己捕获相关异常,而后再对其进行相关包装,最后再将包装后的异常信息抛出。throw用来抛出实际的异常,后面要跟一个异常对象(实例),是一个实际的语句。
• try出现在方法体中,它自身是一个代码块,表示尝试执行代码块的语句,如果在执行过程中有某条语句抛出异常,那么代码块后面的语句将不被执行。
• catch出现在try代码块的后面,是考虑到try包含这段代码可能会遇到这种异常,直接用catch捕获处理,catch包含的代码为处理代码。catch关键字后面紧接着它能捕获的异常类型,所有异常类型的子类异常也能被捕获。
实现步骤
(1)新建一个类名为TextThrowAndThrows.java。
(2)代码如下所示:
package com.zf.s9; //创建一个包 class Animal { //创建动物类 String aName; //动物名称 int aAge; //动物年龄 } public class TextThrowAndThrows { //操作捕获异常抛出异常等的类 Animal aObj[]; public TextThrowAndThrows() { aObj = new Animal[3]; try { for (int i = 0; i < 3; i++) { //循环对小动物初始化 aObj[i] = new Animal(); aObj[0].aName = "cat"; aObj[0].aAge = 1; aObj[1].aName = "sheep"; aObj[1].aAge = 2; aObj[2].aName = "horse"; aObj[2].aAge = 3; } System.out.println("动物初始化......"); } catch (NullPointerException e){ //捕获空指针异常 System.out.println("空指针异常"); } } private void showArith(int num1, int num2)throws ArithmeticException{//处理异常(被0除) int result = 0; try { result = num1 / num2; } catch (ArithmeticException e) { //捕获算术异常 System.out.println("除数不能为0"); throw e; //将异常抛给方法调用者进行处理 } System.out.println("result的结果: " + result); } private void showAnimal(){ //显示小动物 for(int i=0;i<3;i++){ //循环显示小动物信息 System.out.println(aObj[i].aName+", "+aObj[i].aAge+"岁"); } } public static void main(String []args){ //Java程序主入口处 TextThrowAndThrows text=new TextThrowAndThrows();//实例化对象 System.out.println("1.处理算术异常:不能被0除"); try{ text.showArith(6,0); //调用方法,其中6被0除 }catch(ArithmeticException ar){ System.out.println("捕获方法抛出的异常"); } System.out.println("2.处理空指针异常"); try{ text.showAnimal(); //调用方法 }catch (NullPointerException e){ //捕获空指针异常 System.out.println("空指针异常"); }catch(Exception e1){ System.out.println("捕获其他异常"); } } }
(3)运行结果如下所示:
空指针异常 1. 处理算术异常:不能被0除 除数不能为0 捕获方法抛出的异常 2. 处理空指针异常 cat, 1岁 空指针异常
源程序解读
(1)showArith()方法是处理不能被0除的异常。其中在主方法main()中调用该方法并传入为0的参数,方法捕获算术异常,不执行try块中的程序,显示catch块中的信息。在catch块中又将异常抛出给方法调用者对异常进行处理。
(2)在该类的构造方法中对动物类进行初始化,在循环初始化时程序自上而下运行,一旦有异常执行,无论是否被捕获,都不会运行第20行的语句。showAnimal()方法中显示动物的具体信息。在捕获并处理异常时,为catch语句块指定处理的异常类型。同时可以用一个catch语句捕获多种类型的异常。规定如下:
指定处理的异常类型如果没有任何子类,则只能捕获指定的异常类型。如果指定处理的异常类型有子类,则指定类型及其子类的异常都可以捕获。
(3)在main()方法中,运用多个catch块捕获异常,其中Exception类是所有异常类的父类,所以当捕获的异常不是空指针异常时,catch块中包含捕获Exception异常时可以捕获所有不是空指针的异常。如果多个catch语句块中所指定的异常类型相互之间有派生关系,则必须将子类型的异常写在上面,父类型的异常写在下面。否则编译不通过。
实例74 throws声明异常的实例
声明异常是指一个方法不处理它产生的异常,而是向上传递,谁调用这个方法,这个异常就由谁处理。本实例将演示如何利用throws声明异常。
技术要点
throws是用来声明一个方法可能抛出的所有异常信息。通常不用显式地捕获异常,可由系统自动将所有捕获的异常信息抛给上级方法。throws的Exception的取值范围要大于方法内部异常的最大范围。通常throws抛出的是类。
实现步骤
(1)新建一个类名为AbnormalTest.java。
(2)代码如下所示:
public class AbnormalTest{ //声明异常 public void catchThrows(int str) throws ArrayIndexOutOfBoundsException, ArithmeticException, NullPointerException { //声明catchThrows方法的同时指出可以出现的异常类型 System.out.println(str); if (str == 1) { int[] a = new int[3]; a[5] = 5; } else if (str == 2) { int i = 0; int j = 5 / i; } else if (str == 3) { String s[] = new String[5]; s[0].toLowerCase(); } else { System.out.println("正常运行,没有发现异常"); } } public static void main(String args[]) { AbnormalTest yc = new AbnormalTest (); try { yc.catchThows(0); } catch (Exception e) { System.out.println("异常:" +e); //捕获Exception异常,并打印出相应的异常信息 } try { yc.catchThows(1); } catch (ArrayIndexOutOfBoundsException e) { System.out.println("异常:" + e); //捕获ArrayIndexOutOfBoundsException异常,并打印出相应的异常信息 } try { yc.catchThows(2); } catch (ArithmeticException e) { System.out.println("异常:" + e); //捕获ArithmeticException异常,并打印出相应的异常信息 } try { yc.catchThows(3); } catch (Exception e) { System.out.println("异常:" +e); //捕获Exception异常,并打印出相应的异常信息 } } }
(3)运行结果如下所示:
0 正常运行,没有发现异常 1 异常:java.lang.ArrayIndexOutOfBoundsException: 5 2 异常:java.lang.ArithmeticException: / by zero 3 异常:java.lang.NullPointerException
源程序解读
在本程序中,需要注意的是在方法中必须要声明可能发生的所有异常,同时在调用该方法的main方法中定义try/catch语句来捕获该异常。
实例75 自定义异常类
Java中已经存在很多异常,其中有需要检查的异常,也有不需要检查的异常,用户可以在应用中随意使用这些现有的异常。但并不是所有的异常都在产生的时候就能够得到立即处理。如权限检查的时候需要抛出一个异常,但系统中的异常都不符合这个异常的要求,这就需要根据自己的需求自定义一个异常。本实例介绍如何自定义异常及如何使用自定义的异常。
技术要点
自定义异常类的技术要点如下:
• 当系统中已有的异常类型不能满足使用要求时,可以根据自己的需要抛出自定义的异常对象。自定义的异常类一般通过充当捕获异常的角色,需要从Exception类或其他的捕获异常类继承。语法格式:class 类名 extends Exception{相关操作;}或class 类名 extends Throwable{相关操作;}。
• 自定义的异常类一般都会编写必要的构造方法和适合的功能方法。一般会有两个构造方法:一个是默认构造方法;另一个是以字符串为传入参数的构造方法。以字符串为参数的构造方法可以在有出错信息的情况下创建异常对象。
实现步骤
(1)创建一个类名为TextCustomException.java。
(2)代码如下所示:
package com.zf.s9; //创建一个包 class CommonException extends Exception{ //该类继承父类Exception public CommonException(){ //默认构造方法 super(); //继承父类的的默认方法 } public CommonException(String info){ //含有字符串参数的构造方法 super(info); } public CommonException(Throwable cause){ //含有参数的构造方法 super(cause); } } class ThrowException extends Throwable{ //该类继承Throwable类 public ThrowException(){ //默认构造方法 super(); //继承父类的的默认方法 } public ThrowException(String info){ //含有字符串参数的构造方法 super(info); } public ThrowException(String info,Throwable cause){ super(info,cause); } } public class TextCustomException { //操作自定义异常的类 public static void commonException() throws CommonException{ throw new CommonException("继承Exception类的commonException()方法发生异常!"); } public static void throwException() throws ThrowException{ throw new ThrowException("继承Throwable类的throwException()方法发生异常!"); } public static void main(String[] args) { //Java程序主入口处 try { commonException(); throwException(); } catch (CommonException e1){ //捕获指定异常 System.out.println("Exception: " + e1.getMessage()); e1.printStackTrace(); //打印输出异常信息 } catch (ThrowException e2){ //捕获指定异常 System.out.println("Exception: " + e2.getMessage()); e2.printStackTrace(); //打印输出异常信息 } } }
(3)运行结果如下所示:
Exception: 继承Exception类的commonException() 方法发生异常! com.zf.s9.CommonException: 继承Exception类的commonException() 方法发生异常! at com.zf.s9.TextCustomException.commonException(TextCustomException.java:26) at com.zf.s9.TextCustomException.main(TextCustomException.java:34)
源程序解读
(1)CommonException类通过继承Exception类实现自定义异常类。可以通过多种方式构造CommonException异常对象,在每个构造方法中都是调用Exception类的相应的构造方法。
(2)ThrowException类通过继承Throwable实现自定义异常,也实现了多种构造方法,在每个构造方法中都是调用Throwable的相应的构造方法。
(3)当一个try块后面跟着多个catch块时,如果发生的异常匹配第一个catch块的参数,便将异常处理权利交给第一个catch块,如果不匹配,便与第二个catch块进行匹配。依次下去,如果到最后依然无法匹配该异常,便说明该方法无法处理发生的异常,需要在方法声明中添加一条throws语句,将异常抛出。
实例76 使用finally避免资源漏洞
finally关键字是对Java异常处理模型的最佳补充。finally结构使代码总会执行,而不管有无异常发生。使用finally可以维护对象的内部状态,并可以清理非内存资源。如果没有finally,代码也许会很费解。本实例介绍如何释放非内存资源来避免资源漏洞。
技术要点
使用finally避免资源漏洞的技术要点如下:
• finally关键字用在try和catch语句之后,表示无论是否发生异常,都会执行finally块中的代码。
• 由于finally块中的代码总会被执行,所以常常将关闭资源如关闭数据库连接等的语句放在finally块中。
• 在finally块中不能使用return等跳转语句,这会导致跳转语句功能失效。
实现步骤
(1)创建一个类名为TextFinally.java。
(2)代码如下所示:
package com.zf.s9; //创建一个包 public class TextFinally { //操作finally的类 public static double getSqrt(String nStr) throws CommonException, ThrowException { //计算平方根 if (nStr == null) { throw new CommonException("输入的字符串不能为空!"); } double n = 0; try { n = Double.parseDouble(nStr); } catch (NumberFormatException e) { throw new CommonException("输入的字符串必须能够转化成数字!"); } if (n < 0) { throw new ThrowException("输入的字符串转化成的数字必须大于等于0!"); } return Math.sqrt(n); } public static double mustConvertInt(String str) {//验证输入的字符串必须能够转化为数字 try { System.out.println("Try块!"); return getSqrt(str); } catch (CommonException e1) { //捕获异常 System.out.println("捕获CommonException异常!"); System.out.println("异常: " + e1.getMessage()); return -1; } catch (ThrowException e2) { //捕获异常 System.out.println("捕获ThrowException异常!"); System.out.println("异常: " + e2.getMessage()); return -2; } finally { //内容总执行 System.out.println("内容总执行,可释放资源"); } } @SuppressWarnings("finally") public static double numGreaterthanZero(String str) {//转化成的数字必须>=0 try { System.out.println("Try块!"); return getSqrt(str); } catch (CommonException e1) { //捕获异常 System.out.println("捕获 CommonException 异常!"); System.out.println("异常: " + e1.getMessage()); return -1; } catch (ThrowException e2) { //捕获异常 System.out.println("捕获 ThrowException 异常!"); System.out.println("异常: " + e2.getMessage()); return -2; } finally { //内容总执行 System.out.println("内容总执行,可释放资源"); return 0; } } public static void main(String[] args) { //Java程序主入口处 System.out.println("1.输入的字符串必须能够转化成数字"); System.out.println("未发生异常时输出:"); mustConvertInt("12"); System.out.println("发生异常时输出:"); mustConvertInt("aa"); System.out.println("2.输入的字符串转化成的数字必须大于等于0"); System.out.println("未发生异常时输出:"); numGreaterthanZero("3"); System.out.println("发生异常时输出:"); numGreaterthanZero("-3"); } }
(3)运行结果如下所示:
1.输入的字符串必须能够转化成数字 未发生异常时输出: Try块! 内容总执行,可释放资源 发生异常时输出: Try块! 捕获 CommonException 异常! 异常: 输入的字符串必须能够转化成数字! 内容总执行,可释放资源 2.输入的字符串转化成的数字必须大于等于0 未发生异常时输出: Try块! 内容总执行,可释放资源 发生异常时输出: Try块! 捕获 ThrowException 异常! 异常: 输入的字符串转化成的数字必须大于等于0! 内容总执行,可释放资源
源程序解读
(1)getSqrt()方法实现计算平方根的功能。当传入参数不能转化为数字时,抛出CommonException类异常,当字符串转化的数字为负数时,抛出ThrowException类异常,因为负数与非数字没有平方根。
(2)mustConvertInt()方法中是正常使用try/catch/finally异常处理机制的。在try块中调用getSqrt()方法,如果发生CommonException异常,则执行catch块中包含异常CommonException的代码并返回结果1,最后总执行finally块中的代码。如果发生ThrowException异常,则执行catch块中包含异常ThrowException的代码并返回结果-2,最后总执行finally块中的代码。
(3)numGreaterthanZero()方法中finally块中使用跳转语句return 0。由于finally块的语句总被执行,因为finally块的语句在try和catch中的跳转语句之前被执行,在执行finally语句中的跳转语句后,将无法执行try和catch中的跳转语句了。所以该方法总是返回0。
实例77 反射机制
反射主要是指程序可以访问、检测和修改它本身状态或行为的一种能力。Java反射机制是在进行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法。本实例介绍反射类的方法以及如何获得和调用其他类的属性、构造方法和方法的信息。
技术要点
运用反射的技术要点如下:
反射机制提供以下功能:在运行时判断任意一个对象所属的类;在运行时构造任意一个类的对象;在运行时判断任意一个类所具有的成员变量和方法;在运行时调用任意一个对象的方法生成动态代理。
实现步骤
(1)创建一个类名为TextReflect.java。
(2)代码如下所示:
package com.zf.s9; //创建一个包 import java.lang.reflect.*; //引入类 class Customer { //用户类 private Long id; //用户编号 private String name; //用户名称 private int age; //年龄 public Customer() { //默认构造方法 } public Customer(String name, int age) { //带参数构造方法 this.name = name; this.age = age; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public void showInfo(String salary) { System.out.println("薪水: " + salary + "RMB"); } } public class TextReflect { public Object getProperty(Object obj)throws Exception { //取得参数对象中的属性 Class<?> classType = obj.getClass(); //获得对象的类型 System.out.println("Class:" + classType.getName()); Object objectCopy = classType.getConstructor(new Class[] {}) .newInstance(new Object[] {}); //通过默认构造方法创建一新对象 Field fields[] = classType.getDeclaredFields(); //获得对象的所有属性 for (int i = 0; i < fields.length; i++) { Field field = fields[i]; String fieldName = field.getName(); String firstLetter = fieldName.substring(0, 1).toUpperCase(); //获得属性对应getXXX()方法名字 String getMethodName="get" + firstLetter + fieldName.substring(1); //获得属性对应setXXX()方法名字 String setMethodName="set" + firstLetter + fieldName.substring(1); Method getMethod = classType.getMethod(getMethodName, new Class[] {}); //获得和属性对应的getXXX()方法 Method setMethod = classType.getMethod(setMethodName, new Class[] {field.getType()}); //获得和属性对应的setXXX()方法 //调用原对象的getXXX()方法 Object value = getMethod.invoke(obj, new Object[] {}); System.out.println(fieldName + ":" + value); //调用拷贝对象的setXXX()方法 setMethod.invoke(objectCopy, new Object[] { value }); } return objectCopy; } public void getArrayProperty() throws ClassNotFoundException { Class<?> classType = Class.forName("java.lang.String"); Object array = Array.newInstance(classType, 10); //创建字符串数组 Array.set(array, 5, "helloworld"); //把索引位置为5的元素设为hello String s = (String) Array.get(array, 5); //获得索引位置为5的元素的值 System.out.println(s); } public Object getPrivatePropertyValue(Object obj, String propertyName) throws Exception { //获取参数对象的属性值 Class cls = obj.getClass(); //获得对象的类型 Field field = cls.getDeclaredField(propertyName); //获得对象的指定属性 field.setAccessible(true); //属性允许访问 Object retvalue = field.get(obj); //获得属性值 return retvalue; //返回结果 } public Object invokeMethod(Object owner, String methodName, Object[]args) throws Exception { //执行某对象的方法 Class cls = owner.getClass(); //获得对象的类型 Class[] argclass = new Class[args.length]; //对象参数 for (int i = 0, j = argclass.length; i < j; i++) { argclass[i] = args[i].getClass(); } Method method = cls.getMethod(methodName, argclass);//获得对象方法 return method.invoke(owner, args); //执行对象方法并返回 }
public int add(int num1, int num2) { //计算和 return num1 + num2; }
public String echo(String info) { //输出 return "echo: " + info; }
public void invodeMethod() throws Exception { //调用类的方法 Class<?> classType = TextReflect.class; Object invokeTester = classType.newInstance(); Method addMethod = classType.getMethod("add", new Class[] { int.class, int.class }); //调用TextReflect对象的add()方法 Object result = addMethod.invoke(invokeTester, new Object[] { new Integer(100), new Integer(200) }); //获得方法结果 System.out.println((Integer) result); Method echoMethod = classType.getMethod("echo", new Class[] { String.class }); //调用TextReflect对象的echo()方法 result = echoMethod.invoke(invokeTester, new Object[] {"Hello"});//获得方法结果 System.out.println((String) result); }
public static void main(String[] args) throws Exception { //Java程序主入口处 TextReflect tr = new TextReflect(); //实例化对象 Customer customer = new Customer("lingda", 20); //对象初始化 customer.setId(new Long(1)); System.out.println("1.取得参数对象中的全部属性"); Customer cu = (Customer) tr.getProperty(customer); System.out.println("用户信息:编号--" + cu.getId() + ",名称--" + cu.getName() + ",年龄--" + cu.getAge()); System.out.println("2.获得反射数组的信息"); tr.getArrayProperty(); System.out.println("3.获取参数对象的属性值"); String userName = (String) tr.getPrivatePropertyValue(customer, "name"); System.out.println("用户名称: " + userName); System.out.println("4.执行某对象的方法"); tr.invokeMethod(customer, "showInfo", new Object[] { "2000" }); System.out.println("5.调用本类的方法"); tr.invodeMethod(); } }
(3)运行结果如下所示:
1.取得参数对象中的全部属性 Class:com.zf.s9.Customer id:1 name:lingda age:20 用户信息:编号--1,名称--lingda,年龄--20 2.获得反射数组的信息 helloworld 3.获取参数对象的属性值 用户名称: lingda 4.执行某对象的方法 薪水: 2000RMB 5.调用本类的方法 300 echo: Hello
源程序解读
(1)getProperty()方法首先获得对象的类型及名称,其中“?”表示可获得任意类型的对象。再通过默认构造方法创建一个新的对象。通过getDeclaredFields()方法获得传入对象的所有属性,循环遍历属性中,可以根据属性的名字获得相应属性的getXXX和setXXX方法。可以调用对象中的getXXX方法,接收该方法的返回值。将获得的返回值传入setXXX方法中。
(2)getArrayProperty()方法获得字符串对象类型,创建一个长度为10的字符串数组。根据Array.set()方法为指定索引的元素赋值。再根据Array.get()方法获得指定索引处的元素。
(3)getPrivatePropertyValue()方法根据传入的对象获得对象类型,根据传入的属性获得对象的指定属性。当访问的属性的访问修饰符是private时,需要设置setAccessible的值为true。这样便获得属性对应的值。
(4)在含有参数的invokeMethod()方法中,根据传入的对象、对象方法以及对象方法需要的参数获得相应的对象类型、带参数的方法。根据invoke()方法获得方法执行后返回的结果。
(5)在没有参数的invodeMethod()方法中创建TextReflect对象类型。根据getMethod()方法调用此对象的add()并设置方法的参数是整型,调用此对象的echo()方法并设置方法的参数是对象。根据invoke()方法获得方法返回的结果。