
try{
//一些列操作
}catch(捕获操作可能产生的异常类型 变量){
//处理异常
}catch(捕获操作可能产生的异常类型 变量){
//处理异常
}catch(捕获操作可能产生的异常类型 变量){
//处理异常
}
...
优势:正常流程和错误流程分开,程序员更关注正常流程,代码也更清晰,异常处理的核心思想就是EAFP。
public class Test {
public static void main(String[] args) {
func(0);
}
public static void func(int a) {
if (a == 0) {
throw new NullPointerException();
}
}
}
运行结果:

分析:一般在运行结果中提示行数的最上面一行(本例中的第8行)就是我们需要解决的,一般的当解决这个异常后,下面的异常行数就也随之解决了。
②抛出编译时异常(受查异常),用户必须处理,否则无法通过编译

如图所示,CloneNotSupportedException是一个编译时异常,所以在编写代码的时候必须处理这个异常(本例中采用throws声明异常)。
public static void main(String[] args) throws CloneNotSupportedException {
Person person = new Person();
Person person1 = (Person) person.clone();
}
③异常一旦抛出,其后的代码不会被执行
public static void main(String[] args) {
func1(0);
}
public static void func1(int a) {
if (a == 0) {
throw new NullPointerException();
}
System.out.println("方法结束");
}
运行结果:

分析:打印语句 System.out.println("方法结束");因为在它之前抛出了异常,所以这条语句没有被执行。
异常的具体处理方式,也就是异常的捕获,主要有2种方式:异常声明throws以及try-catch捕获处理。
异常声明throws
①throws可以理解为一种较为不负责的做法,它只是告诉调用者我这里有异常,声明了异常,但是并没有真正的去解决处理这个异常。即,当方法种抛出编译时异常,用户不想处理该异常,此时就可以借助throws将异常抛给方法的调用者来处理。当我们没有解决这个异常的时候,就会把这个异常交给JVM处理,一旦交给JVM,程序就崩溃了。
②throws的位置是在方法声明参数列表之后
③throw声明的异常必须是Exception或者Exception子类。
public static void main(String[] args) throws NullPointerException,ArithmeticException{
func(null,0);
}
public static void func(int[] array, int a) throws NullPointerException,ArithmeticException{
if (array == null) {
throw new NullPointerException();
}
if (a == 0) {
throw new ArithmeticException();
}
}
分析:在func中可能会抛出NullPointerException,ArithmeticException两种异常,所以在func的参数列表后面紧跟着使用throws进行声明,并且方法内部如果抛出了多个异常,throws之后必须跟多个异常类型,之间用逗号隔开,假如抛出的多个异常之间具有父子关系,直接声明父类就可以。
public static void main(String[] args) {
try {
func(0);
System.out.println("我会不会被执行呢?");
}catch (ArithmeticException e){
System.out.println("捕获到异常了");
}
System.out.println("捕获异常处理后正常流程的语句");
}
public static void func(int a){
if (a == 0) {
throw new ArithmeticException();
}
}
运行结果:

分析:try块内抛出异常位置之后的代码 System.out.println("我会不会被执行呢?");,不会被执行。
②问题:try-catch能不能处理编译时异常(受查异常)?
答:可以的,对于受查异常来说,当try中没有当初catch的受查异常的时候,catch检测不到就会报错。因此,对于catch中如果写受查异常,则try中一定要出现受查异常。
public static void main(String[] args) {
try {
func(0);
System.out.println("我会不会被执行呢?");
}catch (ArithmeticException e){
System.out.println("捕获到异常了");
}catch (CloneNotSupportedException e){
System.out.println("受查异常被捕获");
}
System.out.println("捕获异常处理后正常流程的语句");
}
public static void func(int a) throws CloneNotSupportedException{
if (a == 0) {
throw new CloneNotSupportedException();
}
}
运行结果:

分析:在func中会抛出受查异常CloneNotSupportedException,但是func并没有处理这个受查异常,所以在func方法参数列表之后使用throws声明这个异常,因此真正的处理时在main中的catch捕获这个异常的时候,这个时候才叫真正意义上的处理异常。
③如果try抛出异常类型和catch的异常类型不匹配,则异常也不会被成功捕获,也就不会被处理,则会继续往外抛,直到JVM处理,一旦交给JVM处理,程序就会立即中止。【异常是按照类型来进行捕获的】
public static void main(String[] args) {
try {
int[] array = null;
int a = array.length;
}catch (ArithmeticException e) {
System.out.println("算术异常被捕获");
}
}
运行结果:

分析:try当中抛出的是空指针异常,而catch捕获处理的是算术异常,因此最终异常会交给JVM处理,程序中止。
④问:try会不会在同一时间同时抛出2个及以上的异常?
答:不会的,当try当中存在多个异常的时候,从上往下执行,谁先抛出异常就捕获哪个异常。
public static void main(String[] args) {
try {
int[] array = null;
int a = array.length;
int ret = 10 / 0;
}catch (ArithmeticException e) {
System.out.println("算术异常被捕获");
}catch (NullPointerException e){
System.out.println("空指针异常被捕获");
}
System.out.println("正常流程语句");
}
运行结果:

分析:在try中会存在抛出空指针异常和算术异常两种情况,所以必须用多个catch来捕获,即多种异常,多次捕获。但是由于try中先会抛出空指针异常,所以不会继续抛其后的算术异常,但是我们发现catch的算术异常是写在空指针异常前面的,这也说明catch中程序的书写不影响异常的捕获,而是取决于try中抛哪个异常。
⑤如果多个异常的处理方式是完全相同的,可以使用 | 连接。
catch (ArithmeticException | NullPointerException e) {
System.out.println("算术异常或者空指针异常被捕获");
}
⑥问:能不能把Exception异常写在最前面
答:不可以,因为所有的异常都是继承Exception的,此时Exception作为所有异常的父类,它能捕获所有的异常,如果把它放到catch的第一个,那么后续的异常没有任何作用了。
错误写法:Exception放在最前面

正确做法:把Exception放到最后面。

⑦可以通过一个catch捕获所有的异常,catch Exception,因为Exception是所有异常类的父类,因此可以用这个一类型表示捕获所有异常。
注意:catch进行类型匹配的时候,不光会匹配相同类型的异常对象,也会捕获目标异常类型的子类对象。
try {
int[] array = null;
int a = array.length;
int ret = 10 / 0;
}catch (Exception e) {
System.out.println("异常被捕获");
}
分析:这种做法不推荐,不可取。
try{
//可能会发生异常的代码
}catch(异常类型 e){
//对捕获到的异常进行处理
}finally{
//此处的语句无论是否发生异常,都会被执行
}
//如果没有抛出异常,或者异常被捕获处理了,这里的代码也会被执行
注意:fnally中的代码一定会执行,一般在finnally中进行一些资源清理的扫尾工作。
分析:下面程序输出结果:
// 下面程序输出什么?
public static void main(String[] args) {
System.out.println(func());
}
public static int func() {
try {
return 10;
} finally {
return 20;
}
}
答:输出20,因为本来要返回10,但是finally中的return语句要在方法结束之前执行,所以在返回10之前,变成了返回20。
分析:finally执行的时机是方法返回之前(try或者catch中如果由return,会在这个return之前执行finally),但是如果finally中存在return语句,那么就会执行finally中的return,从而不会执行到try中原有的return。【一般不建议在finally中写return语句,会被编译器当成一个警告。】并且finally中的语句一定会被执行。
异常处理流程总结
①程序先执行try中的代码
②如果try中的代码出现异常,就会结束try中的代码,看和catch中的异常类型是否匹配
③——Ⅰ:如果找到匹配的异常类型,就会执行catch中的代码
③——Ⅱ:如果没有找到匹配的异常类型,就会将异常向上传递到上层调用者
④无论是否找到匹配的异常类型,finally中的代码都会被执行到(在该方法结束之前执行)
⑤如果上层调用者也没有处理异常,就继续向上传递
⑥一直到main方法也没有合适的代码处理异常,就会交给JVM来进行处理,此时程序就会异常终止
自定义异常:为什么要自己取定义异常?
Java中虽然已经内置了丰富的异常类,但是并不能完全表示实际开发中所遇到的一些异常,此时就需要维护符合实际情况的异常结构,基于已有的异常类进行扩展(继承)创建和业务相关的异常类。
具体方式:
①自定义异常类,继承Exception(编译时异常)或者RuntimeException(运行时异常)。
②实现一个带有String类型参数的构造方法,参数含义:出现异常的原因。
注意事项:
①自定义异常通常会继承Exception类或者RuntimeException类
②继承自Exception的异常默认是受查异常
③继承自RuntimeException的异常默认是非受查异常
实现:实现一个简单的控制台版用户登陆程序, 程序启动提示用户输入用户名密码. 如果用户名密码出错, 使用自定义异常的方式来处理
用户名异常类:
public class NameExcepetion extends RuntimeException{
public NameExcepetion(String message) {
super(message);
}
public NameExcepetion() {
}
}
密码异常类:
public class PasswordExcepetion extends RuntimeException{
public PasswordExcepetion() {
}
public PasswordExcepetion(String message) {
super(message);
}
}
测试类:
import java.util.Scanner;
public class Test {
//异常练习
public static void login() {
String name = "123";
String password = "567";
Scanner scanner = new Scanner(System.in);
System.out.println("请输入用户名");
String uname = scanner.nextLine();
System.out.println("请输入密码");
String upassword = scanner.nextLine();
if (!name.equals(uname)) {
throw new NameExcepetion("兄弟用户名错误了");
}
if (!password.equals(upassword)) {
throw new PasswordExcepetion("兄弟密码错误了");
}
}
public static void main(String[] args) {
try {
login();
} catch (NameExcepetion e) {
e.printStackTrace();
System.out.println("用户名错误");
} catch (PasswordExcepetion e) {
e.printStackTrace();
System.out.println("密码错误");
}
}
}
运行结果:

分析:自定义异常类是一个闪电的图标。
