1、什么是异常
- 异常(Exception)是程序运行过程中发生的事件,该事件可以中断程序指令的正常执行流程
异常的处理机制(重点)
- 当Java程序运行出现问题时,系统会自动检测到该错误,并立即生成一个与该错误对应的异常对象
- 然后把该异常对象提交给Java虚拟机
- Java虚拟机会自动寻找相应的处理代码来处理这个异常,如果没有找到,则由Java虚拟机做一些简单的处理后,程序被强行终止!
- 程序员可以自己编写代码来捕捉可能出现的异常,并编写代码来处理相应的异常
throw
- throw用来抛出异常
- 格式:
- throw new 异常名(参数)
- 假设f方法抛出了A类异常,则f方法有两种方式来处理A异常
- throws A
- 谁调用f方法,谁处理A类异常,f方法本身不处理A类异常
- try{··· ···}catch( ){··· ···}
- f方法本身自己来处理A类异常
/**这个程序建议看完全篇再回来看*/
class DivisorIsZeroException extends Exception
{
public DivisorIsZeroException(String errormessage)
{
super(errormessage);
}
}
class A
{
int divide(int a, int b) throws DivisorIsZeroException
{
// try
// {
// if(0 == b)
// {
// throw new DivisorIsZeroException("除数不能为零!");
// }
// }
// catch(DivisorIsZeroException e)
// {
// e.printStackTrace();//打印错误信息的路径
// }
if(0 == b)
{
throw new DivisorIsZeroException("除数不能为零!");
}
int m = a/b;
return m;
}
}
public class TestA
{
public static void main(String[] args)
{
A aa = new A();
// aa.divide(6,2);
}
}
- 要抛出的异常必须得是Throwable的子类
- 例子:
void f() throws A
{
··· ···
··· ···
}
- throws A表示调用f方法时f方法可能会抛出A类异常,建议您调用f方法时最好对f方法可能抛出的A类异常进行捕捉
- throws A不表示调用f方法时一定会抛出A类异常
throws A,f方法也可以不抛出A类异常 - thorws A不表示调用f方法时,必须的对A异常进行捕捉
- 假设A是RuntimeException的子类异常
- 由于RuntimeException的子类异常可以处理也可以不处理,所以编译器允许你调用f方法时,对f方法抛出的RuntimeException的子类异常不进行处理
- 强烈建议你
对throws出的所有异常进行处理
如果一个方法内部已经对A异常进行了处理,则就不要再throws A
2、为什么需要异常
我们再引入异常处理之前先举一个例子:
C语言中的异常
#include <stdio.h>
int divide(int a,int b)
{
int m;
m = a/b;
return m;
}
int main()
{
// printf("%d\n",divide(6,3));
printf("%d\n",divide(6,0));
return 0;
}
执行以下命令:
————————————————————————
➜ JAVA gcc Test46.c
➜ JAVA ./a.out
————————————————————————
程序运行示例:
————————————————————————
[1] 1647 floating point exception ./a.out
————————————————————————
- 因为除数为0,所以导致C程序在运行的时候崩溃
Java 中的异常
class A
{
int divide(int a,int b)
{
//System.out.printf("1111\n");
int m = a/b;
// System.out.printf("2222\n");
return m;
}
}
public class Test46
{
public static void main(String[] args)
{
A aa = new A();
aa.divide(6,0); //编译时,无错,运行时,有错
System.out.printf("今天我很高兴,因为我和世界的关系很nice\n");
}
}
执行以下命令:
————————————————————————
➜ JAVA javac Test46.java
➜ JAVA java Test46
————————————————————————
程序运行示例:
————————————————————————
Exception in thread "main" java.lang.ArithmeticException: / by zero
at A.divide(Test46.java:5) //程序第五行异常出错
at Test46.main(Test46.java:16) //程序第十六行异常出错
————————————————————————
- 因为这个程序也是除数是0,所以程序运行出现异常
**我们在程序的第六行的前后分别插入: **
System.out.printf("1111\n"); //插入第六行的前面
System.out.printf("2222\n"); //插入第六行的后面
执行以下命令:
————————————————————————
➜ JAVA javac Test46.java
➜ JAVA java Test46
————————————————————————
程序运行示例:
————————————————————————
1111
Exception in thread "main" java.lang.ArithmeticException: / by zero
at A.divide(Test46.java:6)
at Test46.main(Test46.java:17)
————————————————————————
- 再次证明程序在第六行异常出错终止程序
3、异常的处理机制
class A
{
int divide(int a,int b)
{
// System.out.printf("1111\n");
int m = a/b;
// System.out.printf("2222\n");
return m;
}
}
public class Test46
{
public static void main(String[] args)
{
A aa = new A();
// aa.divide(6,0); //编译时时,无错,运行时,有错
try
{
aa.divide(6,0);
}
catch(ArithmeticException e) //算术异常 e用来接收23行抛出的异常对象
{
e.printStackTrace();//可以简单理解为输出该异常的具体信息
System.out.printf("除零错误,你的程序出错了!\n");
}
System.out.printf("今天我很高兴,因为我和世界的关系很nice\n");
}
}
执行以下命令:
————————————————————————
➜ JAVA javac Test46.java
➜ JAVA java Test46
————————————————————————
程序运行示例:
————————————————————————
java.lang.ArithmeticException: / by zero
at A.divide(Test46.java:6)
at Test46.main(Test46.java:20)
除零错误,你的程序出错了!
今天我很高兴,因为我和世界的关系很nice
————————————————————————
- 把可能会出错的语句放入语句
try
{
··· ···
}
catch
{
}
中的try{ } 语句块。如果该语句符合规范的,则不会对该语句进行处理,即程序会自动跳过==catch{}==语句执行其下一行语句,示例:
class A
{
int divide(int a,int b)
{
int m = a/b;
return m;
}
}
public class Test46
{
public static void main(String[] args)
{
A aa = new A();
// aa.divide(6,0); //编译时时,无错,运行时,有错
try
{
aa.divide(6,2);
}
catch(ArithmeticException e) //算术异常 e用来接收23行抛出的异常对象
{
e.printStackTrace();//可以简单理解为输出该异常的具体信息
System.out.printf("除零错误,你的程序出错了!\n");
}
System.out.printf("这是catch语句的下一条语句,因为返回值可以正常输出\n");
System.out.printf("今天我很高兴,因为我和世界的关系很nice\n");
}
}
执行以下命令:
————————————————————————
➜ JAVA javac Test46.java
➜ JAVA java Test46
————————————————————————
程序运行示例:
————————————————————————
这是catch语句的下一条语句,因为返回值可以正常输出
今天我很高兴,因为我和世界的关系很nice
————————————————————————
补充:
public class Test48
{
public static void main(String[] args)
{
int m = 99;
try
{
m = 2;
}
catch(Exception e)
{
}
System.out.printf("m = %d\n",m);
}
}
执行以下命令:
————————————————————————
➜ JAVA javac Test48.java
➜ JAVA java Test48
————————————————————————
程序运行示例:
————————————————————————
m = 2
————————————————————————
若把第五行的m未初始化,即修改第五行为
int m;
执行以下命令:
————————————————————————
➜ JAVA javac Test48.java
————————————————————————
程序运行示例:
————————————————————————
Test48.java:14: 错误: 可能尚未初始化变量m
System.out.printf("m = %d\n",m);
^
1 个错误
————————————————————————
为什么?
倘若我们把14行的输出语句放入==try{}==语句内,即
public class Test48
{
public static void main(String[] args)
{
int m;
try
{
m = 2;
System.out.printf("m = %d\n",m);
}
catch(Exception e)
{
}
// System.out.printf("m = %d\n",m);
}
}
执行以下命令:
————————————————————————
➜ JAVA javac Test48.java
————————————————————————
程序运行示例:
————————————————————————
m = 2
————————————————————————
同样是局部变量m,为什么这个m可以在try{}块内部输出,但是却不能在try{}块之外输出呢?
- 因为try{}语句存放程序中可能存在风险的语句,换句话说,对于其花括号里的语句,运行程序时并不一定保证能够顺利执行,否则程序员也就不会把好好的语句朝==try{}语句块里塞了,综上,倘若try{}==语句未能顺利执行,则对于让JAVA输出未初始化的的局部变量是不允许的。
异常的分类:
Throwable(抛出)
- Error(严重的错误)
- 由Java虚拟机生成并抛出,包括动态链接失败、虚拟机错误等,Java程序无法对此错误进行处理。
- Exception(此集合中的异常必须处理)
- 一般程序中可预知的问题,其产生的异常可能会带来意想不到的结果,因此==Java编译器要求Java程序必须捕获或声明所有的非运行时异常==
- RuntimeException(此集合的异常可处理可不处理)
- Java虚拟机在运行时生成的异常,如被0除等系统错误、数组下标越界等,其产生比较频繁,处理麻烦,对程序可读性和运行效率影响太大。因此由系统检测,用户可不做处理,系统将它们交给缺省的异常处理程序(当然,必要时,用户可对其处理)
- Error 是系统错误,程序员无法处理这些异常
- Exception 是程序员可以捕获并处理的异常
- RuntimeException的子类异常, 是你可以处理也可以不处理的异常
- 凡是继承自Exception但又不是RuntimeException子类的异常我们都必须的捕获并进行处理
4、异常处理步骤:
- thorw try catch finally throws
try
{
可能出现异常的代码块
}
catch(ExceptionName1)
{
当产生ExceptionName1 异常时的处理措施
}
catch(ExceptionName2)
{
当产生ExceptionName2 异常时的处理措施
}
··· ···
finally
{
无论是否捕捉到异常都必须处理的代码
}
- 首先在出现异常的语句周围查找是否有**try{} 和catch{} **语句
- 其次在主调函数中的调用语句附近查找是否有**try{} 和catch{} **语句
- 如果都没有,则有Java虚拟机简单处理**(终止程序)**
5、自定义异常
- ==public Exception(String message)==含义
- 异常与重写关系
6、异常的优缺点
下面说一些目前为止关于异常的困惑:
- 目前为止if····else···语句似乎完全可以替代异常处理机制
- 显然,在某些方法上似乎是的,但是这不是完全的,下面就具体列举一些例子:
import java.util.*;
public class Test50
{
public static void main(String[] args)
{
Scanner sc = null;
try
{
sc = new Scanner(System.in);
int i = sc.nextInt();//输入的字符类型a与int类型不匹配
System.out.printf("%d\n",i);
}
catch(InputMismatchException e)
{
System.out.println("读取错误,程序将终止");
}
}
}
程序运行示例:
——————————————————————
➜ JAVA javac Test50.java
➜ JAVA java Test50
a
读取错误,程序将终止
➜ JAVA java Test50
34
34
➜ JAVA
——————————————————————
- 本程序出现的问题是无法通过逻辑判断来解决的问题Java提供的异常处理机制可以很好的解决这个问题
异常的优点
- 没有错误处理的程序:
- openTheFile
- determine its size
- Allocate that much memory
- read-file
- closeTheFile
- 以常规方法处理错误:
openFile:
if(the FileOpen)
{
determine the length of the file;
if(gotTheFileLength)
{
allocate that much memory;
if(gotEnoughtMemory)
{
read the file into memory;
if(readFailed) errorCode = -1;
else errorCode = -2;
}
else
errorCode = -3;
}
else
errorCode = -4;
}
else
errorCode = -5;
以常规方法处理错误存在的问题
- 观察前面的程序,大家会发现大部分精力花在出错处理上了
- 只把能够想到的错误考虑到,对以外的情况无法处理
- 程序可读性差,大量的错误处理代码混杂在程序中
- 出错返回信息量少,无法更确切的了解错误状况或原因
- 用异常的形式处理错误:
{
try
{
openFile;
determine the length of the file;
allocate that much memory;
read-File;
closeTheFile;
}
catch(fileopenFailed) {dosomething}
catch(sizeDetemineFailed) {dosomething}
catch(memoryAllocatedFailed) {dosomething}
catch(readFailed) {dosomething}
catch(fileCloseFailed) {dosomething}
finally {dosomething}
}
优点
- 强制程序员考虑程序的安全性与健壮性
- 增强了程序员对程序的可控性
- 有利于代码的调试
- 把错误处理代码从常规代码中分离出来
注意:
- 异常并不一定能够使程序的逻辑更清晰
- 因为有时我们必须得编写代码捕捉异常,所以可能会导致程序的逻辑非常混乱
- 异常并不能解决所有的问题
7、常见异常举例:
常见异常之空指针异常
class Person
{
public int age;
}
public class TestNullPointerException
{
public static void main(String[] args)
{
Person p = null; //定义指针赋为空
System.out.println(p.age);
}
}
程序运行示例:
——————————————————————
➜ JAVA javac TestNullPointerException.java
➜ JAVA java TestNullPointerException
Exception in thread "main" java.lang.NullPointerException
at TestNullPointerException.main(TestNullPointerException.java:10)
——————————————————————
解决办法:
class Person
{
public int age;
}
public class TestNullPointerException
{
public static void main(String[] args)
{
// Person p = null;
Person p = new Person();
System.out.println(p.age); //age在未初始化的情况下,系统自动为其赋值为0
}
}
程序运行示例:
——————————————————————
➜ JAVA javac TestNullPointerException.java
➜ JAVA java TestNullPointerException
0
——————————————————————
常见异常之下标越界异常
public class TestIndexOutOf
{
public static void main(String[] args)
{
String friends[] = {"Lisa","Bily","Kessy"};
for(int i = 0; i < 5; i++)
{
System.out.println(friends[i]);
}
System.out.println("\nthis is the end");
}
}
程序运行示例:
——————————————————————
➜ JAVA javac TestIndexOutOf.java
➜ JAVA java TestIndexOutOf
Lisa
Bily
Kessy
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
at TestIndexOutOf.main(TestIndexOutOf.java:8)
——————————————————————
常见异常之除数为0异常
class A
{
int divide(int a, int b)
{
return a/b;
}
}
public class TestArithExcep
{
public static void main(String[] args)
{
A aa = new A();
int i = aa.divide(3,0);
System.out.println(i);
}
}
程序运行示例:
——————————————————————
➜ JAVA javac TestArithExcep.java
➜ JAVA java TestArithExcep
Exception in thread "main" java.lang.ArithmeticException: / by zero
at A.divide(TestArithExcep.java:5)
at TestArithExcep.main(TestArithExcep.java:13)
——————————————————————
8、异常处理的两种方式:
import java.io.*;
class A
{
public void f() throws IOException //throws语句表示交给主调函数处理 //第二种方法
{
/* 第一种方法
try
{
throw new IOException(); //throw不表示抛异常
}
catch(IOException e)
{
}
*/
throw new IOException(); //throw不表示抛异常
}
}
public class TestExcep
{
public static void main(String[] args) throws IOException /*throws语句表示交给Java虚拟机处理*/ //第二种方法
{
A aa = new A();
aa.f();
// try //第一种方法
// {
// aa.f();
// }
// catch(IOException e)
// {
// }
}
}
9、Finally的作用
- 无论try所指定的程序块中是否抛出异常,也无论catch语句的异常类型是否与所抛弃的异常的类型一致,finally中的代码一定会得到执行
- finally语句为异常处理提供一个统一的出口,使得在控制流程转到程序的其他部分以前,能够对程序的状态作统一的管理
- 通常在finally语句中可以进行资源的清除工作,如关闭打开的文件、删除临时文件等
10、注意问题
- 所有的catch只能有一个被执行
- 有可能所有的catch都没有执行
- 先catch子类异常再catch父类异常
- 如果先catch父类异常再catch子类异常,则编译出错
- catch与catch之间不能有其他代码
- 重写方法抛出的异常的范围不能大于被重写方法排除的异常范围