1、什么是异常?
以下两种情况不是异常:
语法错误:编译不通过
逻辑错误:编译通过,运行也有结果,就是结果不对
当语法正确,逻辑也正确时,在运行期间因为一些其他的因素,例如:用户输入的数据不合法,网络不通畅,硬盘容量不够等,
导致程序无法正常运行。这样的不正常问题称为异常。
2、在Java中如何表示不同的异常问题?
Java用对象表示,Java把不同的问题(异常)用不同的Java异常类型来表示,当发生异常时,就会创建对应类型的“对象”。
3、在Java中如何处理异常问题?
(1)遇到异常,就停止运行。程序中没有编写处理的相关代码。
(2)可以处理异常,让程序继续运行,就需要用到try...catch结构
异常对象创建完之后,需要“抛”出,“抛出”后由“try...catch结构”中的catch进行“捕获/抓”,然后处理。
“抛”<-->"捕获“成功 程序继续
“抛”<-->“捕获”失败 程序挂了
4、异常的体系结构
(1)在Java中所有异常的根类型是java.lang.Throwable类。当然Throwable的父类仍然是Object。
看API:Throwable 类是 Java 语言中所有错误或异常的超类。
A:只有当对象是此类(或其子类之一)的实例(对象)时,才能通过 Java 虚拟机或者 Java throw 语句抛出。 “抛”
B:只有此类或其子类之一才可以是 catch 子句中的参数类型。 “捕获”
(2)Java中所有异常类型可以分为两大类,即Throwable分为两大派别:
java.lang.Error:用于指示合理的应用程序不应该试图捕获的严重问题。
例如:VirtualMachineError 虚拟机错误,包括 OutOfMemoryError(堆内存溢出错误), StackOverflowError(栈内存溢出的错误)等。
java.lang.Exception:它指出了合理的应用程序想要捕获的条件。
例如:ArrayIndexOutOfBoundsException,NullPointerException,ClassCastException,ArithmeticException等等
(3)Exception又可以分为两大类:
运行时异常(非受检异常):RuntimeException系列,即RuntimeException类型或它的子类类型
无论程序运行时是否会真的发生xx运行时异常,在“编译”期间,编译器是不会给出提示的,即编译器不会强制程序必须处理xx异常。
直到程序运行时,异常真的发生了,才知道,可能发生xx异常。
例如:ArrayIndexOutOfBoundsException,NullPointerException,ClassCastException,ArithmeticException等等都是运行时异常。
编译时异常(受检异常):除了运行时异常以外的异常类型
无论程序运行时是否会真的发生xx编译时异常,在“编译”期间,编译器是会给出提示,告诉程序员,某个代码可能发生xx异常,请做出处理,否则编译不通过。
例如: java.io.FileNotFoundException等
问题:遇到一个新的异常,能否判断它是编译时异常还是运行时异常。
看编译器是否提示,不提示就是运行时异常,提示就是编译时异常。
public class TestExceptionType {
public static void main(String[] args) {
//运行时异常
int[] arr = {1,2,3,4,5};
System.out.println(arr[0]);//运行时正常的,编译器不会给我们提示,下标是合理范围
// System.out.println(arr[6]);//运行时会发生ArrayIndexOutOfBoundsException,但是编译器并不会给我们提示下标不在合理范围
System.out.println("-----------------------");
//编译时异常
try {
//File:单词代表文件,InputStream单词代表输入的数据流
//想要从"d:/1.txt"读取数据
//以下这句代码,编译器检测到 new FileInputStream("d:/1.txt") “可能发生” java.io.FileNotFoundException文件找不到异常
FileInputStream fis = new FileInputStream("d:/1.txt"); //必须对这句代码做出处理,以免真的发生异常,否则编译不通过
} catch (FileNotFoundException e) {
System.out.println("发生异常了");
}
System.out.println("-------------------------------");
//判断某个异常是编译时异常还是运行时异常
Scanner input = new Scanner(System.in);
System.out.print("请输入一个整数:");
int num = input.nextInt(); //当输入“张”,发生了java.util.InputMismatchException
System.out.println("num = " + num);
System.out.println("-------------------------------");
//判断某个异常是编译时异常还是运行时异常
// Thread.sleep(1000);//InterruptedException 编译时异常,程序还没运行,编译器就提示我,需要处理
}
}
5、异常的抛出机制
描述一下过程:
当某句代码发生异常后,会在这句代码处先“停”下来,然后根据当前的“问题”的情况,创建对应类型的“异常对象”,
接着把这个异常对象就“抛”出来。
如果刚刚发生异常的代码的外围没有try...catch可以“捕获”它,那么该句代码所在方法就会结束,
并且把异常抛给方法的“调用者”,依次类推,如果一直到达main方法,都没有处理,那么就会导致程序挂了。
如果某句代码发生异常后,当“异常对象”抛出后,外围有try...catch可以“捕获”它,那么try...catch下面的代码仍然会继续,
程序不会挂掉。
public class TestExceptionStackTrace {
public static void main(String[] args) {
a(); //已经是最上级了,到main,仍然没有处理,就会导致程序挂了。
System.out.println("main");//不执行
}
public static void a(){
b();
System.out.println("a");//不执行
}
public static void b(){
c();
System.out.println("b");//不执行
}
public static void c(){
System.out.println(1/0);//发生异常
System.out.println("c"); //不执行
}
}
6、异常处理的方式:try...catch
(1)语法结构
它也是属于复合语句中的一种,其他复合语句有:if...else,switch..case,循环等
try{
可能发生异常的代码
}catch(异常的类型1 参数名){
处理异常的代码
}catch(异常的类型2 参数名){
处理异常的代码
}catch(异常的类型3 参数名){
处理异常的代码
}...
(2)一段可能发生异常的代码需要加try...catch结构,可以用快捷键:Ctrl + Alt + T
选中代码,然后按Ctrl + Alt + T快捷键
(3)执行特点
A:try中如果没有发生异常,那么catch全部都不执行
B:try中如果发生异常,发生异常的代码处停下来后,try中剩下的代码就不走了,转而去找匹配的catch执行。
catch的匹配过程是从上往下依次比较,找到就进入这个catch执行,后面的catch就不看了。
C:如果try中如果发生异常,并且所有catch都没有匹配,那么就会导致当前方法结束,把异常抛给“调用者”,
如果已经是main的话,那么程序就挂了。
(4)catch中()的类型只能是异常类型,Throwable或它的子类类型。
catch中()的类型如果是并列关系,顺序无所谓。
catch中()的类型如果是包含关系(有父子类关系),必须子上父下
如果多个catch的{}中处理代码一致,可以简写成如下形式(JDK1.7后支持)
catch(异常类型1 | 异常类型2 | 异常类型3 参数名){ //要求这些异常是没有父子类关系的。
}
(5)在catch的{}中经常看到输出异常的语句
A:标准异常输出: e.printStackTrace(); 这里的e是catch()中的参数名,它代表“捕获的”异常对象。
printStackTrace()方法是将此 throwable 及其追踪输出至标准错误流。
里面包含异常的对象类型、异常的描述信息message、异常对象经过的方法栈的路线(痕迹)等。
B:标准的错误流:
System.err.println(要打印的异常信息);
C:普通信息打印
System.out.println("发生问题了");
说明:System.err和 e.printStackTrace()都是标准的错误流, 他和System.out打印信息的顺序是不能保证的
public class TestTryCatch {
public static void main(String[] args) {
try {
System.out.println("args[0] = " +args[0]);
System.out.println("args[1] = " +args[1]);
//从命令行接收2个整数,并且求它们的商
int a = Integer.parseInt(args[0]);
int b = Integer.parseInt(args[1]);
System.out.println(a/b);
} catch(NumberFormatException e){//当命令行参数输入的不是整数时,会报NumberFormatException数字格式化异常
e.printStackTrace();
// System.err.println("数字格式化异常");
// System.err.println(e);//java.lang.NumberFormatException: For input string: "张三"
// System.out.println("发生问题了");
} catch(ArithmeticException e){//当第二个命令行参数输入0时,会发生ArithmeticException
e.printStackTrace();
} catch (ArrayIndexOutOfBoundsException e) { //没有传入命令行参数,或者命令行参数的个数小于2
//args[0]或args[1]发生下标越界异常
e.printStackTrace();
} catch(Exception e){ //父在下,子在上
e.printStackTrace();
}
/* try {
System.out.println("args[0] = " +args[0]);
System.out.println("args[1] = " +args[1]);
//从命令行接收2个整数,并且求它们的商
int a = Integer.parseInt(args[0]);
int b = Integer.parseInt(args[1]);
System.out.println(a/b);
} catch(NumberFormatException | ArithmeticException | ArrayIndexOutOfBoundsException e){
e.printStackTrace();
}*/
System.out.println("下面的代码可以继续执行...");
}
}
7、异常处理的方式:try...catch...finally
(1)语法结构
它也是属于复合语句中的一种,其他复合语句有:if...else,switch..case,循环等
try{
可能发生异常的代码
}catch(异常的类型1 参数名){
处理异常的代码
}catch(异常的类型2 参数名){
处理异常的代码
}catch(异常的类型3 参数名){
处理异常的代码
}...
finally{
当某些代码,无论try中是否发生异常,也不管catch是否可以捕获异常,甚至就算try或catch中有return语句,
也要求它必须执行,这样的代码放到finally中。
例如:资源关闭和释放等相关代码(IO流,网络连接等)
除了一种情况不执行,在try..catch中遇到了System.exit(0);
}
public class TestTryCatchFinally {
public static void main(String[] args) {
try {
System.out.println("args[0] = " +args[0]);
System.out.println("args[1] = " +args[1]);
//从命令行接收2个整数,并且求它们的商
int a = Integer.parseInt(args[0]);
int b = Integer.parseInt(args[1]);
System.out.println(a/b);
// return ; //结束main方法
System.exit(0); //退出JVM
} catch(NumberFormatException e){//当命令行参数输入的不是整数时,会报NumberFormatException数字格式化异常
e.printStackTrace();
} catch(ArithmeticException e){//当第二个命令行参数输入0时,会发生ArithmeticException
e.printStackTrace();
} finally{
System.out.println("就算天塌下来,也要执行,除非JVM退出,才不执行");
}
}
}
8、异常处理的关键字:throws
(1)什么情况下需要用到throws关键字?
当某个方法体中,可能发生xx异常,但是在这个方法中,又不想处理(或处理不了)时,可以把异常抛给调用者,
并且在方法签名中用throws声明可能发生的异常类型。这样的话,调用者在调用方法时,就知道需要处理什么异常了。
(2)throws后面可以是编译时异常,也可以是运行时异常。
如果throws后面是编译时异常,那么调用者调用这个方法时,编译器会要求调用者必须做出处理;
如果throws后面是运行时异常,那么调用者调用这个方法时,编译器不会要求调用者必须做出处理,如果调用者想要处理时,就有据可依。
(3)throws语法
【修饰符】 返回值类型 方法名(【形参列表】)throws 异常列表 {
}
说明:异常列表可以是多个异常,没有顺序要求,异常类型之间使用逗号分隔
(4)throws只是表名当前方法可能发生xx异常,当前方法没有处理,让调用者处理,如果调用者也不处理,一直到main也throws,就会导致程序挂了。
结论:Java中真正处理异常的是try..catch,throws只是让调用者处理而已或者延迟处理,不是表示不用处理。
(5)父类的方法被重写时,关于throws有要求
A:如果父类的被重写方法,没有通过throws声明要抛出 xx 编译时异常,那么子类重写该方法时,也不能通过throws声明抛出编译时异常
B:如果父类的被重写方法,通过throws声明要抛出 xx 编译时异常,那么子类重写该方法时,只能通过throws声明抛出同样的编译时异常或其子类异常。
C:父类被重写方法是否抛出运行时异常,和子类重写时是否抛出运行时异常无关。
方法重写的要求总结:
方法名:必须相同
形参列表:必须相同
返回值类型:
基本数据类型和void:必须相同
引用数据类型:<=
权限修饰符:>=,不能是private,跨包不能是缺省的
throws异常类型:如上
其他修饰符:不能是static, final
public class TestThrows {
public static void main(String[] args) {
// copyFile1("d:/1.txt","d:/atguigu/1.txt");
copyFile2("d:/1.txt","d:/atguigu/1.txt");//编译器没有检测到copyFile2方法可能发生xx异常
//如果此时加try...catch,它知道要处理什么异常
System.out.println("--------------------");
Father f = new Son();
try {
f.test();
} catch (IOException e) { //此时catch异常类型是看父类Father中的test方法throws的异常类型,运行时子类Son中不能抛出比它大的异常,否则就有问题了
e.printStackTrace();
}
}
/**
* 复制文件功能,例如:想要把"d:/1.txt"文件复制一份“d:/atguigu/1.txt",
* srcFile参数就是 "d:/1.txt"
* destFile参数就是 "d:/atguigu/1.txt"
* @param srcFile String 源文件的路径+文件名
* @param destFile String 目标文件的路径+文件名
*/
public static void copyFile1(String srcFile, String destFile) throws FileNotFoundException, IOException,RuntimeException {
//...里面代码省略
//这里面在读取源文件的内容时,发现源文件找不到,或者读取文件失败(可能没有权限,或者损坏),发生异常,需要把异常交给调用者处理
}
/**
* 复制文件功能,例如:想要把"d:/1.txt"文件复制一份“d:/atguigu/1.txt",
* srcFile参数就是 "d:/1.txt"
* destFile参数就是 "d:/atguigu/1.txt"
* @param srcFile String 源文件的路径+文件名
* @param destFile String 目标文件的路径+文件名
*/
public static void copyFile2(String srcFile, String destFile) {
//...里面代码省略
//这里面在读取源文件的内容时,发现源文件找不到,或者读取文件失败(可能没有权限,或者损坏),发生异常,需要把异常交给调用者处理
}
}
class Father{
public void method(){
//...省略
}
public void test()throws IOException{
}
}
class Son extends Father{
/* @Override
public void method() throws Exception{//错误,因为method在父类中没有抛出编译时异常
//...省略
}*/
@Override
public void method() throws RuntimeException{//运行时异常没有限制
//...省略
}
/* public void test()throws IOException{//throws 一样可以
}*/
/* public void test()throws FileNotFoundException{//throws 小的异常, FileNotFoundException是IOException的子类
}*/
/* public void test()throws Exception{//throws 大的异常,错误
}*/
}
8、异常处理的关键字:throws
(1)什么情况下需要用到throws关键字?
当某个方法体中,可能发生xx异常,但是在这个方法中,又不想处理(或处理不了)时,可以把异常抛给调用者,
并且在方法签名中用throws声明可能发生的异常类型。这样的话,调用者在调用方法时,就知道需要处理什么异常了。
(2)throws后面可以是编译时异常,也可以是运行时异常。
如果throws后面是编译时异常,那么调用者调用这个方法时,编译器会要求调用者必须做出处理;
如果throws后面是运行时异常,那么调用者调用这个方法时,编译器不会要求调用者必须做出处理,如果调用者想要处理时,就有据可依。
(3)throws语法
【修饰符】 返回值类型 方法名(【形参列表】)throws 异常列表 {
}
说明:异常列表可以是多个异常,没有顺序要求,异常类型之间使用逗号分隔
(4)throws只是表名当前方法可能发生xx异常,当前方法没有处理,让调用者处理,如果调用者也不处理,一直到main也throws,就会导致程序挂了。
结论:Java中真正处理异常的是try..catch,throws只是让调用者处理而已或者延迟处理,不是表示不用处理。
(5)父类的方法被重写时,关于throws有要求
A:如果父类的被重写方法,没有通过throws声明要抛出 xx 编译时异常,那么子类重写该方法时,也不能通过throws声明抛出编译时异常
B:如果父类的被重写方法,通过throws声明要抛出 xx 编译时异常,那么子类重写该方法时,只能通过throws声明抛出同样的编译时异常或其子类异常。
C:父类被重写方法是否抛出运行时异常,和子类重写时是否抛出运行时异常无关。
方法重写的要求总结:
方法名:必须相同
形参列表:必须相同
返回值类型:
基本数据类型和void:必须相同
引用数据类型:<=
权限修饰符:>=,不能是private,跨包不能是缺省的
throws异常类型:如上
其他修饰符:不能是static, final
public class TestThrows {
public static void main(String[] args) {
// copyFile1("d:/1.txt","d:/atguigu/1.txt");
copyFile2("d:/1.txt","d:/atguigu/1.txt");//编译器没有检测到copyFile2方法可能发生xx异常
//如果此时加try...catch,它知道要处理什么异常
System.out.println("--------------------");
Father f = new Son();
try {
f.test();
} catch (IOException e) { //此时catch异常类型是看父类Father中的test方法throws的异常类型,运行时子类Son中不能抛出比它大的异常,否则就有问题了
e.printStackTrace();
}
}
/**
* 复制文件功能,例如:想要把"d:/1.txt"文件复制一份“d:/atguigu/1.txt",
* srcFile参数就是 "d:/1.txt"
* destFile参数就是 "d:/atguigu/1.txt"
* @param srcFile String 源文件的路径+文件名
* @param destFile String 目标文件的路径+文件名
*/
public static void copyFile1(String srcFile, String destFile) throws FileNotFoundException, IOException,RuntimeException {
//...里面代码省略
//这里面在读取源文件的内容时,发现源文件找不到,或者读取文件失败(可能没有权限,或者损坏),发生异常,需要把异常交给调用者处理
}
/**
* 复制文件功能,例如:想要把"d:/1.txt"文件复制一份“d:/atguigu/1.txt",
* srcFile参数就是 "d:/1.txt"
* destFile参数就是 "d:/atguigu/1.txt"
* @param srcFile String 源文件的路径+文件名
* @param destFile String 目标文件的路径+文件名
*/
public static void copyFile2(String srcFile, String destFile) {
//...里面代码省略
//这里面在读取源文件的内容时,发现源文件找不到,或者读取文件失败(可能没有权限,或者损坏),发生异常,需要把异常交给调用者处理
}
}
class Father{
public void method(){
//...省略
}
public void test()throws IOException{
}
}
class Son extends Father{
/* @Override
public void method() throws Exception{//错误,因为method在父类中没有抛出编译时异常
//...省略
}*/
@Override
public void method() throws RuntimeException{//运行时异常没有限制
//...省略
}
/* public void test()throws IOException{//throws 一样可以
}*/
/* public void test()throws FileNotFoundException{//throws 小的异常, FileNotFoundException是IOException的子类
}*/
/* public void test()throws Exception{//throws 大的异常,错误
}*/
}
9、自定义异常
JRE核心类库中已经提供了很多异常的类型,但是有时候我们仍然想要声明自己的异常类型,使得异常的信息更“见名知意”,
可读性更好。
IllegalArgumentException:非法参数异常
UnsupportedOperationException:不支持该操作的异常
如何自定义异常?
(1)必须继承Throwable或它的子类。
(2)自定义异常类型中建议保留无参构造和(String message)的构造器,其他构造器自选。
(3)自定义异常类型建议实现java.io.Serializable接口(在IO中用的)
因为后期的异常对象可能被输出到日志文件,所以需要支持序列化操作,这就要求实现java.io.Serializable接口。
关于序列化的内容在IO部分讲解。
自定义异常如何抛出和处理?
(1)自定义异常对象,必须由程序员手动new,并用throw语句抛出。
(2)一旦throw之后,就和系统的其他异常对象一样了,
A:可以throws
B:可以try...catch
C:不处理会导致系统崩溃
public class TestDefineException {
public static void main(String[] args) {
Account a = new Account();
try {
a.withdraw(100);
System.out.println("取款成功");
} catch (BalanceNotEnoughException e) {
System.out.println("取款失败");
e.printStackTrace();
}
}
}
//余额不足异常
class BalanceNotEnoughException extends Exception implements Serializable {
public BalanceNotEnoughException() {
}
public BalanceNotEnoughException(String message) {
super(message);//调用父类的构造器,message在父类中声明的成员变量
}
}
//钱数是负数异常
class MoneyIsNegativeException extends RuntimeException implements Serializable{
public MoneyIsNegativeException() {
}
public MoneyIsNegativeException(String message) {
super(message);
}
}
class Account{
private double balance;//这里只关注余额了,没有关注其他的属性
public double getBalance() {
return balance;
}
public void save(double money){
if(money<0){
// throw new IllegalArgumentException(money +"参数有问题,存款金额不能为负数");
throw new MoneyIsNegativeException(money +"参数有问题,存款金额不能为负数");
/*
MoneyIsNegativeException是继承RuntimeException,说明它是运行时异常,在save()方法中,编译器不会要求我们必须处理
*/
}
balance += money;
}
public void withdraw(double money) throws BalanceNotEnoughException {
if(money<0){
throw new IllegalArgumentException(money +"参数有问题,取款金额不能为负数");
}
if(money>balance){
// throw new UnsupportedOperationException("取款金额" + money +"超过余额,余额不足");
throw new BalanceNotEnoughException("取款金额" + money +"超过余额,余额不足");
/*
BalanceNotEnoughException是继承Exception,它是编译时异常,在withdraw方法中,编译器就会要求我们必须处理
*/
}
balance -= money;
}
}