黑马程序员——Java之异常机制

时间:2022-03-03 11:55:39
------ Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
内容提要:
        异常概述
        异常体系
        异常类型
        异常处理
        throws和throw的区别
        调用者对抛出异常的处理
        RuntimeException
        异常处理方式和原则
        多异常处理
        自定义异常
        继承中的异常处理
        异常练习


        异常是Java中的重要机制,使用了面向对象的思想进行封装。
        异常,简而言之就是程序中可能出现的错误或者问题。就像人会生病,不一定经常有,但总有生病的时候,而生病的原因各有不同,性质不一样,对其的治疗手法自然不同。

异常概述
        对于计算机程序而言,情况更复杂,没有人能保证自己写的程序永远不会错,就算程序没有错误,能保证用户总是按你的意愿来输入?能保证运行该程序的操作系统永远稳定?能保证运行该程序的硬件不会突然坏掉?能保证网络永远通畅?
        Java在设计异常体系时,将容易出现的情况都封装成了对象。
        异常就是程序在运行时出现的不正常情况。
        异常的由来:问题也是现实生活中一个具体的事物,也可以java的类的形式进行描述,并封装成对象;其实异常就是Java对不正常情况进行描述后的对象体现体现在于: “一个问题产生后,既有问题的原因,又有问题的现象,还有一些其他的问题信息,这些内容都可用封装的类进行体现并提取。”
        对于问题的划分:两种,一种是严重的问题,另一种是非严重问题。对于严重的,java通过Error类进行描述,对于Error一般不编写针对性的代码对其进行处理(是JVM搞不定的部分)对于非严重问题,Java通过Exception类进行描述。而Exception可以使用针对性的处理方式进行处理(JVM能够搞定的部分,也是JVM能够识别的部分,JVM处理结果:JVM立刻停止运行)
        无论Error或者Exception都具有一些共性内容,比如不正常情况的信息、引发原因等。
        异常的好处:将问题进行封装;将正常流程代码和问题处理代码相隔离,方便于阅读。
异常体系
        此时将两者共性向上抽取形成了Throwable类,如下是Java的异常体系:
        Throwable
                |—— Error 通常出现重大问题,如运行的类不存在或者内存溢出等。
                |—— Exception 运行时出现的一些情况
                        |—— RuntimeException 特殊异常类,抛出时不需要声明
        Error和Exception的子类名都是以父类名作为后缀。
        异常体系的特点:
        其一、异常体系中的所有类以及建立的对象都具备可抛性;
        其二、异常体系中的建立的对象可以被throw和throws关键字操作;
        其三、只有异常体系具备这个特点。(什么意思?这里的异常指的是Exception吗?)
异常类型
        异常有两种:
        其一:编译时被检查异常。
        该异常在编译时,如果没有处理(没有抛出也没有被try),则编译失败;该异常被标识,代表着可以被处理。详细地说明:在JAVAC编译时期,会去检测,如果方法中抛出了非RuntimeException 或者其子类,在方法中没有标示抛出异常,就视为有安全隐患的,表示这个异常是可以被处理的,并让调用者做出处理动作,在标示出异常的同时,调用者也必须有对应的处理方式,要么继续抛异常,要么try;
        其二:运行时异常(编译时不检查)。
        在编译时,不需要处理,编译器不检查。该异常的发生,建议不处理,并让程序停止,以此让程序员对代码进行修正。如RuntimeException以及其子类。凡是RuntimeException或者其子类会做一个判断instanceof RuntimeException,假若是true,则不管,目的在于让程序停止。
异常处理
        Java内部的新建文件操作,是基于系统资源调用,即借用了Windows或者Linux的系统操作实现。
        Java提供了特有的语句进行异常处理,try…catch(异常类 变量)…finally…,以上由三个部分组成。
        Try…catch…finall…的其他调用格式:第一种,try…catch…;第二种:try…catch…finally…;第三种:try…finally…;第四种:try…catch…catch…等。注意:catch是用于处理异常的,如果没有catch语句块,就代表异常没有被处理过,如果该异常是检测时异常,那必须声明。
        其中try部分写需要被检测的代码,catch部分写处理异常的代码,也就是处理方式,finally是一定会执行的语句。
        异常处理的流程:try块里面写需要检测的代码块,如果不写这部分,一旦出现了异常会直接将异常抛给JVM,JVM会启动虚拟机的默认异常处理机制,并导致程序中断。而try块就是为了防止将异常抛给JVM,而将异常抛给catch代码块处理。
        需要注意的是:
        1.finally语句块(数据库主机服务器(CPU资源限制)的连接数也是有限的,)中的内容是一定会执行的部分,通常用于关闭资源(或者一定要执行的代码),否则增加运行压力。
        2.finally语句只有一种情况不会执行,当执行到System.exit(0)时,finally语句将不被执行。
        对捕获到的异常进行常见方法操作:e.getMessage():输出异常;e.toString():异常名称:异常信息;e.printStackTrace():异常名称,异常信息以及出现的位置。
        JVM默认的异常处理机制就是调用printStackTrace,以此打印异常的堆栈跟踪信息。
        一旦try部分出现异常,出现异常的代码之后的部分将不执行。
public class ExceptionDemo {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Demo d = new Demo();
		try {
			int x = d.div(4, 0);
			System.out.println("x = " + x);
		} catch (Exception e) { // Exception e = new ArithmeticException();
			System.out.println("除零了");
		}

		System.out.println("over");
	}

}

class Demo {
	int div(int a, int b) {
		return a / b;
	}
}
        代码分析:异常的处理,在try语句块中写入需要“被检测”的代码,若通过,则不执行catch语句块中的代码,若抛出异常,则执行catch语句块,并在结束后执行catch语句块之后的代码。但是需要指出的是:一旦参数传入有误,而引起了异常(下一步执行catch语句时),将导致程序停止。这种方式是“不友好”的方式。
        在功能上通过throws关键字声明该功能有可能出现异常,“未报告的异常错误,必须对其进行捕获或声明以便抛出!”对抛出的异常进行处理,否则编译失败。
public class ExceptionDemo2 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Demo2 d = new Demo2();
		int x = d.div(4, 1); //有可能发生问题!!因为已经throws Exception
		//假如不处理,则编译不能通过
		
		System.out.println("x = " + x);

		System.out.println("over");
	}

}

class Demo2 {
	int div(int a, int b) throws Exception{ //在功能上通过throws声明该功能有可能会出现问题
		return a / b;
	}
}
         代码分析:throws的作用是告诉该方法的调用者:这个方法有可能引发异常,希望得到调用者的注意。如果调用者对这个问题“毫不在意”,编译器将直接发出错误信息,导致编译无法通过。有两种方式:捕获或声明以便抛出,进行处理该问题。
public class ExceptionDemo2 {

	public static void main(String[] args) throws Exception{
		// TODO Auto-generated method stub
		Demo2 d = new Demo2();
		int x = d.div(4, 1); //有可能发生问题!!因为已经throws Exception
		//假如不处理,则编译不能通过
		
		System.out.println("x = " + x);

		System.out.println("over");
	}

}

class Demo2 {
	int div(int a, int b) throws Exception{ //在功能上通过throws声明该功能有可能会出现问题
		return a / b;
	}
}
        代码分析:将异常抛出给JVM,由JVM代为处理。
public class ExceptionDemo2 {

	public static void main(String[] args){
		// TODO Auto-generated method stub
		Demo2 d = new Demo2();
		try{
			int x = d.div(4, 0); //有可能发生问题!!因为已经throws Exception
			//假如不处理,则编译不能通过
			
			System.out.println("x = " + x);
		}
		catch(Exception e)
		{
			System.out.println("有问题了!");
			System.out.println(e.toString()); //异常处理方式有很多
		}

		System.out.println("over");
	}

}

class Demo2 {
	int div(int a, int b) throws Exception{ //在功能上通过throws声明该功能有可能会出现问题
		return a / b;
	}
}
        代码分析:这种方式并不是向JVM抛出异常,而是自己处理这个异常。
throws和throw的区别
        throws使用在函数上,用于抛出异常类,可以抛出多个异常,并用逗号隔开。
        throw使用在函数内部,用于抛出异常对象。
        throw单独存在时,下面不要定义语句,因为执行不到。
        当函数内有throw抛出的异常对象,并未进行try处理,必须要在函数上进行声明,否则编译失败。 注意:RuntimeException除外,也就是说,函数内部要是抛出了RuntimeException异常,函数上可以不需要声明。
        如果函数声明了异常,调用者需要进行处理,处理方法可以throws或者throw。
调用者对抛出异常的处理
        当在函数内部出现了throw抛出异常对象,那么就必须要给对应的处理动作。要么在内部try...catch...处理,要么在函数上声明让调用者自行处理。
        一般情况下,函数内出现异常,函数上需要声明。在功能上通过throws的关键字声明该功能可能会出现异常的类型。
RuntimeException
        Exception中有一个特殊的子类异常RuntimeException运行时异常,
        1)如果在函数内部抛出该异常,函数可以不用声明,编译一样通过;
        2)如果在函数上声明了该异常,调用者可以不用进行处理,编译一样通过。
        为什么?之所以不用在函数上声明,是因为不需要让调用者处理。当该异常发生时,希望程序停止,因为在运行时出现了无法继续运算的情况,希望停止程序后对代码进行修正。
异常处理方式和原则
        如果函数声明了异常,调用者需要进行处理:try或者throws。
        当捕获到的异常,本功能处理不了时,可以继续在catch中抛出。
        如果该异常处理不了,但并不属于该功能出现的异常,可以将异常转换后,再抛出和该功能相关的异常。
        当在函数内部出现了throw抛出异常对象,下一步就必须给出对应的处理动作,要么在内部进行try…catch…处理,要么在函数上声明(通过关键字throws声明,通知调用者:这个方法有可能产生异常)让调用者处理(要么try,要么抛出异常,并让其他人进行处理。)。
多异常处理
        声明异常时,建议声明更为具体的异常,这样可以更加具体地对异常进行处理;
        一般声明几个异常,就对应有几个catch块,不要定义多余的catch块,即有针对性的处理异常。
        如果多个catch块中的异常出现继承关系,父类异常catch块放在最下面。
        建议:在进行catch块处理时,catch中一定要定义具体处理方式,不要简单地书写一条输出语句。
        硬盘文件:异常日志文件,记录着软件出现的bug,软件开发人员根据日志文件进行软件调试和除错。
public class ExceptionDemo3 {

	public static void main(String[] args){
		// TODO Auto-generated method stub
		Demo3 d = new Demo3();
		try{
			int x = d.div(4, 1);
			System.out.println("x = " + x);
		}
		catch(ArithmeticException e)
		{
			System.out.println(e.toString());
			System.out.println("除零了!");
		}
		catch(ArrayIndexOutOfBoundsException e)
		{
			System.out.println(e.toString()); 
			System.out.println("越界了!");
		}
		catch(Exception e)
		{
			System.out.println(e.toString());
		}
		
		System.out.println("over");
	}

}

class Demo3 {
	int div(int a, int b) throws ArithmeticException, ArrayIndexOutOfBoundsException{
		int[] arr = new int[a];
		
		System.out.println(arr[4]);
		return a / b;
	}
}
        代码分析:多异常的处理这对于不同的异常情况,有针对性的进行处理。
自定义异常
        含义:“对本项目涉及到的异常进行自我封装”。
因为项目中会出现一些特有问题,而这些问题并未被Java所描述并封装对象,所以对于这些特有的问题可以按照Java的封装思想,将特有问题进行自定义异常封装。
        自定义的异常Java不认识,这个时候需要我们编写代码手动抛出异常(自动抛出异常由Java捕获)。
       自定义异常必须让其具备两个属性:1.为了让该自定义类具备可抛性;2.让该类具备操作异常的共性方法。
        在自定义异常时:分析该问题出现时能不能处理,如果不能处理,需要修正代码的时候,就继承RuntimeException;假若问题出现时,可以处理,处理完后程序还能继续运行,那就继承Exception。 要么继承RuntimeException,要么继承Exception。
        发现打印的自定义异常中只有异常名称,却没有异常信息;因为自定义的异常并未定义信息。
        如何定义异常信息呢?因为在父类中已经把异常信息的操作都实现了,所以子类只要在构造时,将异常信息传递给父类通过super语句调用,就可以直接通过getMessage()方法获取自定义的异常信息了。
        为什么要继承Exception?异常体系有一个特点,因为异常类和异常对象(也就是Exception类和对象)都需要被抛出,他们都具备可抛性,这个可抛性是Throwable这个体系中独有特点,只有这个体系中的类和对象才可以被throws和throw操作。
public class ExceptionDemo4 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Negative n = new Negative();
		try {
			int x = n.div(4, -1);
			System.out.println("x = " + x);
		} catch (FuShuException e) {
			System.out.println(e.toString());
			// System.out.println("出现负数了!");
			System.out.println("错误的负数是:" + e.getValue());
		}

		System.out.println("over");
	}

}

class FuShuException extends Exception { // 自定义异常
	private int value;

	FuShuException() {
		super();
	}

	FuShuException(String msg, int value) {
		super(msg);
		this.value = value;
	}

	public int getValue() {
		return value;
	}
}

class Negative {
	int div(int a, int b) throws FuShuException { // 将抛出的异常,在调用者(方法)上进行抛出
		if (b < 0)
			throw new FuShuException("出现了除数是负数的情况", b); // 手动通过throw关键字抛出自定义异常

		return a / b;
	}
}
        代码分析:自定义异常的定义以及应用。
继承中的异常处理
        子类在覆盖父类时,如果父类的方法抛出异常,那么子类的覆盖方法,只能抛出父类的异常或该异常的子类,或者不抛出异常。
        如果父类方法抛出多个异常,那么子类在覆盖该方法时,只能抛出父类异常的子集。
        原则只有一个:子类抛出的异常必须是父类的异常的子集或者子类,父类能够处理的异常。
        如果父类或者接口的方法中没有异常抛出,那么子类在覆盖方法时,也不可以抛出异常。如果子类方法发生了异常就必须要进行try…catch…处理,绝对不能抛异常。
/*
 * 继承关系: 
 * Exception 
 * 		|--AException 
 * 			|--BException 
 * 		|--CException
 */
class AException extends Exception {

}

class BException extends Axception {

}

class CException extends Exception {

}

class Fu {
	void show() throws AException { //父类抛出AException异常
	}
}

class Test {
	void function(Fu f) {
		try {
			f.show();
		} catch (AException e) {
			
		}
	}
}

class Zi extends Fu{
	void show() throws CException{ //父类抛出CException异常(和AException同级别),编译无法通过
		//父类中的catch无法处理该异常
	}
}
异常练习
/*
 * 老师使用电脑讲课
 * 描述电脑:
 * 		电脑运行;
 * 		电脑重启;
 * 描述电脑的问题:
 * 		电脑蓝屏;
 * 		电脑起火;
 * 
 * 描述老师:
 * 		老师使用电脑;
 * 		老师讲课;
 * 描述老师可能出现的问题:
 * 		老师不能继续讲课,让学生做练习。*/
public class ExceptionTest {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Teacher t = new Teacher("毕老师");
		try {
			t.teach();
		} catch (StopTeachException e) {
			System.out.println(e.toString());
			System.out.println("换老师或放假");
		}
	}

}

class Teacher {
	private String name;
	private Computer cpt;

	Teacher(String name) {
		this.name = name;
		cpt = new Computer();
	}

	public void teach() throws StopTeachException {
		try {
			cpt.run();
		} catch (BlueScreenException e) {
			System.out.println(e.getMessage());
			cpt.reset();
		} catch (FireBreakingException e) {
			test();
			throw new StopTeachException("Teacher_stopTeach:" + e.getMessage());
		}

		System.out.println(name + "Teacher_teaching");
	}

	void test() {
		System.out.println("电脑坏了,学生做练习");
	}
}

class Computer {
	int start = 3;

	void run() throws BlueScreenException, FireBreakingException {
		if (start == 2)
			throw new BlueScreenException("Computer_BlueScreen");
		else if (start == 3)
			throw new FireBreakingException("Computer_FireBreaking");

		System.out.println("Computer running");
	}

	void reset() {
		start = 1;
		System.out.println("Computer_reset");
	}
}

class BlueScreenException extends Exception {
	BlueScreenException(String msg) {
		super(msg);
	}
}

class FireBreakingException extends Exception {
	FireBreakingException(String msg) {
		super(msg);
	}
}

class StopTeachException extends Exception {
	StopTeachException(String msg) {
		super(msg);
	}
}