学习笔记之JavaSE(20)--面向对象编程11

今天学习的内容是异常


在程序中,错误可能产生于程序员没有预料到的各种情况,或者是超出程序员可控范围的环境因素,比如用户的坏数据、试图打开一个根本不存在的文件等等。在Java中这种在程序运行时可能出现的一些问题称为异常,它中断了程序的继续运行。其实异常就是Java通过面向对象的思想将问题封装成对象,用异常类对其进行描述。异常的知识点如下:

  • Java异常体系中的顶级父类是Throwable类,该类和该类的所有子类都具有可抛性
  • Throwable类的两个子类分别是Error类和Exception类
  • Error类及其子类描述的是系统内部错误以及资源耗尽错误,这种问题无法在程序中处理,必须直接修改程序
  • Exception类及其子类描述的是可处理的问题,程序员通常关心的是Exception
  • 所有标准Exception类都有两个构造函数:一个是无参构造函数;另一个是接受字符串作为参数的构造函数,用于初始化本类中继承于Throwable类的detailMessage字符串变量,并可以通过getMessage()方法获取
  • Exception类的子类可以根据问题发生的原因分为运行时异常(RuntimeException类及其子类)被检查异常(除了RuntimeException的异常类)
  • 被检查异常会被编译器检测到,并要求在程序中对此异常进行针对性处理,否则编译失败
  • 运行时异常不会被编译器检测到,这种问题实际上属于编程错误,更多是因为调用者的原因导致的,或者引发了内部状态的改变导致的。这种问题一般不做处理当然也可以对运行时异常进行处理,不过声明抛出没有必要,捕获处理则会造成隐患
  • 如果遇到标准Exception类没有描述的问题,可以自定义异常。自定义异常的方法就是继承Exception类或RuntimeException类,自定义异常同样也分为被检查异常和运行时异常
  • 处理异常的方法:声明抛出捕获处理
  • 声明抛出:使用throw关键字将异常对象抛给调用方,并在方法的参数列表后面使用throws关键字声明要抛出的异常。从效果上看,抛出异常类似返回异常,抛出异常之后方法弹栈,throw语句之后的代码不再执行
  • 除非要抛出自定义异常或者想要通过有参构造函数自定义异常信息,否则不用显式地throw异常
  • 运行时异常不需要声明抛出,而被检查异常如果没有进行捕获处理,就必须声明抛出
  • 如果一直向调用方抛异常对象而没有进行捕获处理,那么最终会由main()方法将异常对象抛给JVM,JVM会强行终止程序并调用异常对象的printStackTrace()方法,异常的详细信息会被输出到标准错误流RuntimeException的默认处理方式
  • 异常对象的printStackTrace()方法有三种重载版本,无参版本默认输出到标准错误流,另外两种版本允许选择要输出的流
  • 捕获处理:try块放置需要被检测是否发生异常的代码,如果try块中抛出了异常对象,会被catch块捕获并进行针对性处理,此时认为异常已经得到了处理,程序可以继续运行。如果try块中的代码没有抛出异常,则不会执行catch块;如果try块中抛出异常,则会跳过try块中的剩余语句,转而执行catch块
  • 异常声明和catch块的异常参数都是多态的,注意不同异常参数的catch块的定义顺序必须是子类上,父类下
  • 可以在try/catch块后面添加finally块,无论try块中是否会发生异常,finally块中的代码都会被执行。finally的作用就是关闭释放除内存之外的资源,比如关闭数据库连接(增删查改时出现SQLException)
  • 如果try/catch块中存在return指令,程序流程也会先跳到finally再执行return
  • 如果try块和catch块中都添加相同的代码,那还要finally块干嘛?一是由于finally可以使代码更加简洁;二是因为如果try块中发生的某种RuntimeException并没有对应的catch块进行捕获处理,那么关闭释放资源的代码就会被跳过,必须使用finally块确保资源的关闭释放
  • 以下特殊情况finally不会被执行:finally块中发生了异常;在之前的代码中使用了System.exit(0)退出JVM;程序所在的线程死亡;关闭CPU
  • try/catch/finally的三种组合方式:try/catch/finally 需要捕获异常和关闭释放资源;try/catch 当没有资源需要被关闭释放时,不用定义finally块;try/finally  当异常无法直接被捕获处理,且资源需要关闭释放时,不用定义catch块,但是必须在方法声明异常
  • 注意:catch和finally不能没有try;try块和catch块中间不能有代码
  • 异常处理原则:方法内部如果抛出被检查异常,必须声明抛出或捕获处理(最好声明抛出,只进行捕获处理会存在隐患);如果调用声明异常的方法,也必须声明抛出或捕获处理,能捕获就捕获,不能捕获就接着往上抛(被检查异常对象要在被抛给JVM之前被捕获处理掉)
  • 异常的限制:子类在覆盖父类方法时,父类方法如果声明抛出了异常,那么子类方法只能声明抛出父类的异常或该异常的子类(接口同理);如果父类声明抛出多个异常,子类只能抛出父类异常的子集,不能抛出新异常;如果父类没有声明异常,子类绝对不能声明异常。所以一般来说,在设计接口时,方法最好加上throws Exception
  • 如果在方法内部使用捕获处理异常,就会发生异常隐藏。比如,用户调用数据库添加数据的方法时,在该方法内部发生了SQLException,却在方法内部被捕获处理了,那么用户会发现:并没有发生异常,但功能却没有实现!而直接抛出SQLException的话,用户又不知该如何处理它。这时候就要用到异常转换:在方法内部使用try/catch处理掉SQLException,并在catch块继续抛出异常(比如显而易见的NoIdException)
示例程序:
public class Test35 {

	public static void main(String[] args) {//运行时异常不需要声明抛出,因为没有必要
		/*
		 * Error类及其子类描述的是系统内部错误以及资源耗尽错误,这种问题无法在程序中处理,必须直接修改程序
		 * int[] arr = new int[1024*1024*800*1021];  本语句编译通过,但运行时会发生Error:java.lang.OutOfMemoryError: Java heap space
		 */
		
		//测试运行时异常
		int[] arr_1 = new int[]{1,2,3};
		int[] arr_2 = null;
		int index_true = 1;
		int index_out = 3;
		int index_negative = -1;
		//main()方法会把接收到的异常对象再抛给JVM,JVM拿到异常对象就会执行异常处理机制,将异常信息打印在控制台
		arrayExceptionThrow(arr_1, index_true);//如果参数选择index_out,会抛出有自定义异常信息的ArrayIndexOutOfBoundsException
		arrayExceptionNotThrow(arr_1, index_true);//如果参数选择index_out,会抛出JVM创建的ArrayIndexOutOfBoundsException
		arrayExceptionThrow(arr_1, index_true);//如果参数选择arr_2,会抛出有自定义异常信息的NullPointerException
		arrayExceptionNotThrow(arr_1, index_true);//如果参数选择arr_2,会抛出JVM创建的NullPointerException
		arrayExceptionThrow(arr_1, index_true);//如果参数选择index_negative,会抛出自定义的NegativeIndexException
		arrayExceptionNotThrow(arr_1, index_negative);//如果参数选择index_negative,会抛出JVM创建的ArrayIndexOutOfBoundsException(Java没有数组下标为负异常)
		System.out.println("abc in main");//如果抛出异常main()方法弹栈,此语句不会执行
		
	}
	
	//当要抛出自定义异常或者想要通过有参构造函数自定义异常信息,可以显式地throw异常
	public static void arrayExceptionThrow(int[] arr,int index){//运行时异常不需要声明抛出,因为没有必要
		if(arr==null){
			throw new NullPointerException("无法操作null对象");//显式地throw异常可以介入异常对象的初始化过程(自定义异常信息)
			//!System.out.println("abc"); 如果抛出异常arrayExceptionThrow()方法弹栈,此语句不会执行(此语句永远不会被执行,所以编译不通过)
		}
		if(index>=arr.length){
			throw new ArrayIndexOutOfBoundsException("数组下标不能越界");
			//!System.out.println("abc");
		}
		if(index<0){
			throw new NegativeIndexException("数组下标不能为负数");
			//!System.out.println("abc");
		}
		System.out.println(arr[index]);//如果发生异常此语句不会执行
		System.out.println("abc in arrayExceptionThrow");//如果发生异常此语句不会执行
	}
	
	//除非要抛出自定义异常或者想要通过有参构造函数自定义异常信息,否则不用显式地throw异常
	public static void arrayExceptionNotThrow(int[] arr,int index){
		System.out.println(arr[index]);
		System.out.println("abc in arrayExceptionNotThrow");
	}
}

//自定义运行时异常(数组下标为负异常)
class NegativeIndexException extends RuntimeException{
	/**
	 * 
	 */
	private static final long serialVersionUID = -1446907878404353058L;
	
	public NegativeIndexException(){}
	public NegativeIndexException(String message){
		super(message);
	}
}

public class Test36 {

	public static void main(String[] args) {
		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
		System.out.print("请输入数组下标:");
		int index;
		//try块放置需要被检测是否发生异常的代码
		try {
			index = Integer.parseInt(in.readLine());//有可能产生NumberFormatException或IOException
			go(index);//有可能产生NegativeIndexException
			System.out.println("index="+index);//如果发生异常,这条语句将不会执行,转而执行对应的catch块
		} catch (NumberFormatException | IOException e) {
			e.printStackTrace();
			System.out.println("捕捉到NumberFormatException或IOException");
		} catch (NegativeIndexException e){
			System.out.println("捕捉到NegativeIndexException");
			//JVM的默认异常处理机制就是调用异常对象的printStackTrace()方法
			e.printStackTrace();//com.grc.NegativeIndexException: 数组角标为负异常
								//at com.grc.Test36.main(Test36.java:23)
			System.out.println("getStackTrace:"+e.getStackTrace());//[Ljava.lang.StackTraceElement;@1f33b16a
			System.out.println("getMessage:"+e.getMessage());//getMessage:数组角标为负异常
			System.out.println("toString:"+e);//toString:com.grc.NegativeIndexException: 数组角标为负异常
			return;//如果本catch块捕捉到了异常,会先执行finally块,然后再返回来执行return
			//System.exit(0); 退出JVM 这种情况不会执行finally块
		} 
		catch (Exception e){
			e.printStackTrace();
			System.out.println("捕捉到其他异常");
		}finally{
			System.out.println("finally");
		}
	}
	
	public static void go(int index) throws NegativeIndexException{//被检查异常如果没有进行捕获处理,就必须声明抛出
		if(index < 0){
			throw new NegativeIndexException("数组角标为负异常");
		}
	}
}

//自定义被检查异常(数组下标为负异常)
class NegativeIndexException extends Exception{
	/**
	 * 
	 */
	private static final long serialVersionUID = -4618617674002460321L;
	public NegativeIndexException(){}
	public NegativeIndexException(String message){
		super(message);
	}
}

面试题:
1.受检查异常和运行时异常的区别?
答:异常表示程序运行过程中可能出现的非正常状态,运行时异常表示虚拟机的通常操作中可能遇到的异常,是一种常见运行错误,只要程序设计得没有问题通常就不会发生。受检异常跟程序运行的上下文环境有关,即使程序设计无误,仍然可能因使用的问题而引发。Java编译器要求方法必须声明抛出可能发生的受检异常(如果没有捕获处理),但是并不要求必须声明抛出未被捕获的运行时异常。
2.常见的运行时异常
答:IndexOutOfBoundsException、NullPointerException、IllegalArgumentException,ArithmeticException、ClassCastException、NumberFormatException、SecurityException
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值