Java学习-异常

一、概述

1.异常概念:在程序运行时,发生的不被期望的事件,阻止了程序按照程序员的预期正常执行;
2.异常处理(exception handing)
1)向用户报告错误信息;
2)保存所有的工作结果;
3)允许用户以妥善的形式退出程序;
3.异常结构
Java中Throwable类异常类型的顶层父类,然后Throwable又派生出Error类和Exception类;
在这里插入图片描述
3.1根据Java异常产生原因区分:
1) Error:Error类以及他的子类的实例,代表了JVM本身的错误。错误不能被程序员通过代码处理,Error很少出现,因此程序员应该关注Exception为父类的分支下的各种异常类。
2) Exception:Exception以及他的子类,代表程序运行时发送的各种不期望发生的事件,可以被Java异常处理机制使用,是异常处理的核心。

3.2根据JavaC(编译器)对异常处理要求区分:
Unchecked Exception(非检查异常):Error 和 RuntimeException 以及他们的子类。Javac在编译时,不会提示和发现这样的异常,不强制要求在程序处理这些异常,这样的异常发生原因多半是代码写的有问题。

Checked Exception(受检查异常):除了Error 和 RuntimeException之外的其它异常。Javac强制要求程序员为这样的异常做预备处理工作(使用try…catch…finally直接处理 或 throws向上抛出)。这样的异常原因是程序无法选择运行环境或控制用户如何操作,需要对可能产生的异常进行预处理。

二、异常产生过程
1.非检查异常-示例:

public class show {
	public static void main(String[] args) {
		
		System.out.println(multiply());
	}
	
	public static int obj(String str) {
		return str.length();
	}
	
	public static int multiply() {
		String s = null;
		int len = obj(s);
		return len;
	}
}
console结果:
Exception in thread "main" java.lang.NullPointerException
	at testexception.show.obj(show.java:10)
	at testexception.show.multiply(show.java:15)
	at testexception.show.main(show.java:6)

分析:当obj函数遇到空指针异常时,此时obj函数无法处理此异常,把异常提交给其caller即multiply函数,multiply也无法处理,又提交给其caller即main函数,main也无法处理,最终提交给JVM进入终止模式termination model (程序在异常抛出点停止执行,并输出异常信息);

2.受检查异常-示例:

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class show {
	public static void main(String[] args) throws IOException {
		File file = new File("hello.txt");//此处的hello.txt实际不存在的;
		FileInputStream fis = new FileInputStream(file);
		byte[] b = "hello".getBytes();
		
		fis.read(b);
		
		fis.close();
	}
}
console结果:
Exception in thread "main" java.io.FileNotFoundException: hello.txt (系统找不到指定的文件。)
	at java.io.FileInputStream.open0(Native Method)
	at java.io.FileInputStream.open(Unknown Source)
	at java.io.FileInputStream.<init>(Unknown Source)
	at testexception.show.main(show.java:11)

分析:此处的hello.txt实际不存在的,会抛出FileNotFoundException,所以也是不能开启流进行写入和关闭,会抛出IOException异常,FileNotFoundException是IOException的子类,所以main函数只需要抛出IOException异常即可,main抛出异常后无法处理依然只能提交给JVM执行终止模式;

三、异常处理机制

1.try…catch或try…catch…finally

1.1 简单示例:

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class show {
	public static void main(String[] args){
		File file = new File("hello.txt");
		
		try {
			System.out.println("今日阳光明媚,程序跑的哇哇的!");
			FileInputStream fis = new FileInputStream(file);
			System.out.println("测试出错了这里还能跑不!");
			byte[] b = "hello".getBytes();
			fis.read(b);
			fis.close();
		} catch (IOException e) {
			System.out.println("大事不好了catch到异常了!先让我把数据保存一下!");
		}finally {
			System.out.println("烂摊子我finally来收尾!");
		}
		
		System.out.println("上面折腾完了继续运行!");
	}
}
	console结果:
	今日阳光明媚,程序跑的哇哇的!
	大事不好了catch到异常了!先让我把数据保存一下!
	烂摊子我finally来收尾!
	上面折腾完了继续运行!

分析:当想要开启输入流时发生异常,这条语句的后面的语句不会再执行,执行流跳转到最近的匹配的异常处理catch块去执行,异常被处理完后,执行流会接着在<处理了这个异常的catch块>后面接着执行,先执行finally块,再往下继续执行;

1.2 语法详解

try{
     //try块中放可能发生异常的代码。
     //如果执行完try没有发生异常,忽略catch块,若存在finally块则接着去执行finally块。
     //如果执行完try发生异常,则尝试去匹配catch块。

}catch(异常类型 异常参数){
 ...
 
}catch(xxxException e){
    //每一个catch块用于捕获并处理一个特定的异常,或者这异常类型的子类。
    //在catch块中可以使用这个块的异常参数来获取异常的相关信息,异常参数是这个catch块中的局部变量,其它块不能访问。
    //Java7中可以将多个异常声明在一个catch中。
    
}finally{
  //finally块不是用来处理异常的,也不会捕获异常,主要做一些清理工作如流的关闭,数据库连接的关闭等。 
  //无论异常是否发生,异常是否匹配被处理,finally都会执行;只有一种方法让finally块不执行:System.exit()。

}

1.3 注意

  • 一个try至少要有一个catch块,否则, 至少要有1个finally块;
  • try块中的局部变量和catch块中的局部变量(包括异常变量),以及finally中的局部变量,他们之间不可共享使用。
    在try…catch…finally语句块内部定义的变量,作用域在语句块内部,外部不可见。在try…catch…finally语句块外部定义的变量,在语句块内部可以对变量进行修改
  • 如果当前try块中发生的异常在后续的所有catch中都没捕获到,则先去执行finally,然后到这个函数的外部caller中去匹配异常处理器。
  • 如果方法中try,catch,finally中没有return返回语句,则会调用这三个语句块之外的return结果。
  • finally中最好不要包含return,否则会覆盖try或catch中保存的返回值,也会覆盖try中异常或catch到的异常。
  • finally中最好不要抛出异常,否则会覆盖try中异常或catch到的异常;
  • 异常匹配是按照catch块的顺序从上往下寻找的,只有第一个匹配的catch会得到执行。匹配时,不仅运行精确匹配,也支持父类匹配,因此,如果同一个try块下的多个catch异常类型有父子关系,应该将子类异常catch块放在前面,父类异常catch块放在后面,这样保证每个catch块都有存在的意义;

2.throw和throws

2.1 简单示例

//throw写在函数中,throw 异常对象
public class show {
	public static void main(String[] args) {
		try {
			int i = 0;
			if(i == 0) 
				throw new NumberException("i = 0");
		}catch(NumberException num){
			num.printStackTrace();
		}
	}
}

//throws写方法函数头,可以抛出多条异常,每个类型的异常中间用逗号隔开
public class show {
	public static void main(String[] args) throws FileNotFoundException,IOException {
		File file = new File("hello.txt");
		FileInputStream fis = new FileInputStream(file);
		
		byte[] b = "hello".getBytes();
		fis.read(b);
		fis.close();
	}
}

四、栈轨迹(StackTrace)

1.概念图:
栈轨迹( stack trace ) :是一个方法调用过程的列表, 它包含了程序执行过程中方法调用的特定位置;
异常抛出点:调用序列中的最后一个调用方法,是异常对象Throwable创建和抛出之处;
在这里插入图片描述
2.简单示例:

public class demo {

	public static void main(String[] args) {
		a();
		b();
	}
	public static void a() {
		try {
			throw new Exception();
		} catch (Exception e) {
			for (StackTraceElement ste : e.getStackTrace()) {
				System.out.println(ste.getMethodName());
			}
			for (StackTraceElement ste : e.getStackTrace()) {
				System.out.println(ste);
			}
			e.printStackTrace();
		}
	}
	
	public static void b() {
		a();
	}

}
	console结果:
	java.lang.Exception
		at testexception.demo.a(demo.java:11)
		at testexception.demo.main(demo.java:6)
	java.lang.Exception
		at testexception.demo.a(demo.java:11)
		at testexception.demo.b(demo.java:24)
		at testexception.demo.main(demo.java:7)
	a
	main
	testexception.demo.a(demo.java:11)
	testexception.demo.main(demo.java:6)
	a
	b
	main
	testexceptione.demo.a(demo.java:11)
	testexception.demo.b(demo.java:24)
	testexception.demo.main(demo.java:7)

分析
2.1 printStackTrace()方法
printStackTrace()是Throwable 类的方法,异常处理中用来打印栈轨迹的文本描述信息;

2.2 getStackTrace()方法
getStackTrace()也是Throwable 类的方法, 返回的是StackTraceElement对象(StackTraceElement 类含有能够获得文件名、当前执行的代码行号、类名和方法名的方法)的一个数组,数组内存放的栈轨迹元素,索引0(数组第一个元素)是栈顶(抛出点),索引末尾(数组的最后一个元素)是栈底(调用序列中的第一个方法调用);

3.重抛异常
重抛异常:将异常再次抛给上一级异常处理程序,同一个try块的后续catch块将会被忽略;
3.1 示例

public class demo {

	public static void main(String[] args) {
		b();
	}
	public static void a() throws Exception {
		try {
			throw new Exception();//java:10
		} catch (Exception e) {
			e.printStackTrace();
			throw e;//java:13
		}
	}
	
	public static void b() {
		try {
			a();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
	console结果:
	java.lang.Exception
		at testexception.demo.a(demo.java:10)
		at testexception.demo.b(demo.java:20)
		at testexception.demo.main(demo.java:6)
	java.lang.Exception
		at testexception.demo.a(demo.java:10)
		at testexception.demo.b(demo.java:20)
		at testexception.demo.main(demo.java:6)

分析:两次printStackTrace()输出结果是一样的,第一次异常抛出点在(java:10),重抛异常后输出的仍是第一次异常抛出点的栈轨迹,如果想将第二次异常抛出点(java:13)的栈轨迹也表示出来,需要用到fillInStackTrace()方法;

3.2 fillInStackTrace()方法
原理:在当前线程下将最新抛出的异常对象的栈轨迹信息保存下来,重新返回一个Throwable异常对象,这样再printStackTrace()就会输出最新的栈轨迹信息,printStackTrace()方法是给它什么异常对象就输出此对象的异常信息;
使用:将throw e;改成throw (Exception)e.fillInStackTrace();等同于Throwable fst = e.fillInStackTrace(); throw (Exception)fst;

public class demo {
	public static void main(String[] args) {
		b();
	}
	public static void a() throws Exception {
		try {
			throw new Exception();
		} catch (Exception e) {
			e.printStackTrace();
			Throwable fst = e.fillInStackTrace();
			throw (Exception)fst;//注意强制转换
		}
	}
	public static void b() {
		try {
			a();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
	console结果:
	java.lang.Exception
		at testexception.demo.a(demo.java:10)
		at testexception.demo.b(demo.java:19)
		at testexception.demo.main(demo.java:6)
	java.lang.Exception
		at testexception.demo.a(demo.java:13)
		at testexception.demo.b(demo.java:19)
		at testexception.demo.main(demo.java:6)

五、异常链

异常链:在捕获一个异常后,抛出另一个异常,此时将原始异常信息通过Throwable类型的对象作为cause参数传递给新的异常,而Throwable子类构造器支持cause作为参数传入,这样可以形成异常链(类似链表结构,每个异常既存有上一个异常对象信息,也携带自己的异常对象信息),即使当前位置创建并抛出新的异常,也能通过异常链追踪到异常发生的最初位置;

示例

public class demo {
	public static void main(String[] args) {
		b();
	}
	
	public static void a() throws Exception {
		try {
			throw new IOException("IO");
		} catch (IOException e) {
			e.printStackTrace();
			Exception ex = new Exception("EX",e);
			throw ex;
		}
	}
	
	public static void b() {
		try {
			a();
		} catch (Exception e) {
			e.printStackTrace();
			e.getCause();
		}
	}
}
	console结果:
	java.io.IOException: IO
		at testexception.demo.a(demo.java:12)
		at testexception.demo.b(demo.java:22)
		at testexception.demo.main(demo.java:7)
	java.lang.Exception: EX
		at testexception.demo.a(demo.java:15)
		at testexception.demo.b(demo.java:22)
		at testexception.demo.main(demo.java:7)
	Caused by: java.io.IOException: IO
		at testexception.demo.a(demo.java:12)
		... 2 more

分析
注意
1)getCause()方法用来获取原始异常信息;
2)Throwable子类中只有Erro、Exception和RuntimeException这3个类提供了带cause参数的构造函数,如果要把其他类型的异常 链接起来,使用initCause()方法;

六、自定义异常

在Java 中自定义异常需要注意以下几点:

  • 所有异常都必须是 Throwable 的子类;
  • 如果想写一个受检查异常类,则继承 Exception类;
  • 如果想写一个运行时异常类,则继承 RuntimeException类;
  • 一般尽量将构造函数都写全,参考下例;
    示例
public class IOException extends Exception
{
    static final long serialVersionUID = 7818375828146090155L;

    public IOException()
    {
        super();
    }

    public IOException(String message)
    {
        super(message);
    }

    public IOException(String message, Throwable cause)
    {
        super(message, cause);
    }

    public IOException(Throwable cause)
    {
        super(cause);
    }
}

七、异常机制使用技巧
7.1 异常处理不能代替简单的测试
与执行简单的测试相比, 捕获异常所花费的时间大大超过了前者, 因此使用异常的基本规则是:只在异常情况下使用异常机制。

简单测试语句:if (!s.empty()) s.popO;

异常处理:
try{
s.popO;
}catch (EmptyStackException e){
}

7.2 不要过分地细化异常
新手容易将每一条语句都分装在一个独立的 try语句块中,导致易读性变差,代码量增多。因此有必要将整个任务包装在一个 try语句块中,这样当任何一个操作出现问题时, 整个任务都可以取消。

一句一异常写法:
Stack s;
for (i = 0;i < 100; i++){
	try{
	n = s.popO;
	}catch (EmptyStackException e){
	//stack was empty
	}
	
	try{
	out.writelnt(n);
	}catch (IOException e){
	//problem writing to file
	}
}
改进后:
try{
	for (i = 0; i < 100; i++){
	n = s.popO ;
	out.writelnt(n);
	}
}catch (IOException e){
// problem writing to file
}
catch (EmptyStackException e){
//stack was empty
}

7.3 利用异常层次结构
不要只抛出 RuntimeException 异常,应该寻找更加适当的子类或创建自己的异常类。
不要只捕获 Throwable 异常, 否则会使程序代码更难读、 更难维护。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值