前言:当我们编写程序时,有时会遇到一些意外的情况,比如输入错误、文件找不到、或者网络连接失败。这些问题可能会让程序崩溃,但在Java中,我们有一种叫做"异常"的机制,可以帮助我们更好地应对这些问题。在学习异常之前我们还是通过一些基础案例由浅入深来讲解为什么要用异常,怎么使用异常等。
我们先通过几个案例来演示下什么是异常?
第一种情况:
import java.util.Scanner;
/**
* @Auther: themyth
*/
public class Test {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
//实现一个功能:键盘录入两个数:求商:
Scanner sc = new Scanner(System.in);
System.out.println("请录入第一个数:");
int num1 = sc.nextInt();
System.out.println("请录入第二个数:");
int num2 = sc.nextInt();
System.out.println("商:"+num1/num2);
}
}
以上这段代码看似没有什么问题,我们进行正常的输入int类型的值之后结果没问题。
但是在测试其它类型的数据,例如非int类型的数据时候,此时就会出现异常了。
或者除数为0的时候:
第二种情况:
public class Test {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
int[] arr = {1, 2, 3};
System.out.println(arr[100]);
}
}
比较经典的数组越界异常:
第三种情况:
public class Test {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
int[] arr = null;
System.out.println(arr.clone());
}
}
也是比较经典的异常之一:空指针异常
以上情况都属异常,只是列举了一些常见的而已。
异常:Exception:在程序的运行过程中,发生了不正常的现象,阻止了程序的运行,我们称之为异常。
异常其实就是一个类。
我们回想一下,当我们还没有学习异常时候,我们是如何处理这些情况的呢?通过简单的if-else来就解决。就拿上面的案例代码来说:
import java.util.Scanner;
/**
* @Auther: themyth
*/
public class Test {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
//实现一个功能:键盘录入两个数:求商:
Scanner sc = new Scanner(System.in);
System.out.println("请录入第一个数:");
if(sc.hasNextInt()){
int num1 = sc.nextInt();
System.out.println("请录入第二个数:");
if(sc.hasNextInt()){
int num2 = sc.nextInt();
if(num2 == 0){
System.out.println("对不起,除数不能为0");
}else{
System.out.println("商:"+num1/num2);
}
}else{
System.out.println("对不起,你录入的不是int类型的数据!");
}
}else{
System.out.println("对不起,你录入的不是int类型的数据!");
}
}
}
虽然我们通过if-else解决了一些常见的异常问题,但是用这种方法来堵漏洞也有很多缺点:
(1)代码臃肿,业务代码和处理异常的代码混在一起。
(2)可读性差
(3)程序员需要花费大量的经历来维护这个漏洞
(4)程序员很难堵住所有的漏洞。
正是由于if-else的处理方式不方便且有很多缺点,所以Java中专门出了一个异常处理机制:
“异常三连” try-catch-finally
try-catch
我们首先来讲try-catch部分,异常出现了我们应该怎么看?
接下来我们先用try-catch来捕获异常:
import java.util.Scanner;
/**
* @Auther: themyth
*/
public class Test2 {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
//实现一个功能:键盘录入两个数:求商:
try {
Scanner sc = new Scanner(System.in);
System.out.println("请录入第一个数:");
int num1 = sc.nextInt();
System.out.println("请录入第二个数:");
int num2 = sc.nextInt();
System.out.println("商:"+num1/num2);
}catch (Exception ex){
System.out.println("对不起,你的程序出现异常!");
}
System.out.println("----谢谢你使用计算器111");
System.out.println("----谢谢你使用计算器222");
System.out.println("----谢谢你使用计算器333");
System.out.println("----谢谢你使用计算器444");
System.out.println("----谢谢你使用计算器555");
System.out.println("----谢谢你使用计算器666");
}
}
原理:
把可能出现异常的代码放入try代码块中,然后将异常封装为对象,被catch后面的()中的那个异常对象接收,接收以后:执行catch后面的{}里面的代码,然后try-catch后面的代码,该怎么执行就怎么执行。
详细说一下:
- try中没有异常,catch中代码不执行。
- try中有异常,catch进行捕获:
如果catch中异常类型和你出的异常类型匹配的话:走catch中的代码--》进行捕获
如果catch中异常类型和你出的异常类型不匹配的话:不走catch中的代码--》没有捕获成功,程序相当于遇到异常了,中断了,后续代码不执行
public class Test {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
try {
int[] array = null;
System.out.println(array.length);
System.out.println("after...");
} catch (ArithmeticException e) {
e.printStackTrace();
System.out.println("捕捉到了 ArithmeticException 异常,进行处理异常的逻辑");
} catch (NullPointerException e) {
e.printStackTrace();
System.out.println("捕捉到了 NullPointerException 异常,进行处理异常的逻辑");
}
System.out.println("正常的逻辑");
}
}
注意:
(1)try中如果出现异常,如果用catch捕获成功的话,那么try中后续的代码是不会执行的,但try-catch后面的代码该执行还是执行没有影响
(2)try中如果出现异常,如果catch捕获异常失败(可能是其它异常,程序会中断),那么try后续的代码是不会执行的,且try-catch后面的代码也不能执行
(前提是try中有异常!如果try中没异常,那自然try中代码自然向下执行)
即无论try中异常是否被catch捕获,try中的后续代码是不会执行的!
如果捕获,那么try-catch后面的代码该执行还是执行没有影响
如果没有捕获,那么try-catch后面的代码也不能执行
catch中处理异常的几种常见方式
还是对以前的案例代码做演示:
import java.util.Scanner;
/**
* @Auther: themyth
*/
public class Test3 {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
//实现一个功能:键盘录入两个数:求商:
try {
Scanner sc = new Scanner(System.in);
System.out.println("请录入第一个数:");
int num1 = sc.nextInt();
System.out.println("请录入第二个数:");
int num2 = sc.nextInt();
System.out.println("商:"+num1/num2);
}catch (Exception ex){
//第一种处理:什么都不写,什么都不做
//第二种处理:输出自定义异常信息
//System.out.println("对不起,你的程序出现异常!");
//第三种处理:打印异常信息:
/*(1)调用toString方法,显示异常的类名(全限定路径)*/
/* System.out.println(ex);
System.out.println(ex.toString());*/
/*(2)显示异常描述信息对应的字符串,如果没有就显示null*/
/*System.out.println(ex.getMessage());*/
/*(3)显示异常的堆栈信息:将异常信息捕获以后,在控制台将异常的效果给我们展示出来,方便我们查看异常*/
/*ex.printStackTrace();*///实际上用得最多
//第四种处理:抛出异常:相当于异常重现(出现中断),异常一旦抛出,后面的代码就不会执行了
throw ex;
}
System.out.println("----谢谢你使用计算器111");
}
}
try-catch-finally
【1】在什么情况下,try-catch后面的代码不执行?
(1)throw抛出异常的情况
(2)catch中没有正常的进行异常捕获,例如:比如只能捕获输入匹配异常,如果除数为0,是另外一个异常,程序会中断
(3)在try中遇到return
【2】怎么样才可以将 try-catch后面的代码 必须执行?
只要将必须执行的代码放入finally中,那么这个代码无论如何一定执行
【3】return和finally执行顺序?
先执行finally最后执行return
【4】finally 执行的时机是在方法返回之前(try 或者 catch 中如果有 return 会在这个 return 之前执行 finally).
但是如果 finally 中也存在 return 语句, 那么就会执行 finally 中的 return, 从而不会执行到 try 中原有的 return.
一般我们不建议在 finally 中写 return (会被编译器当做一个警告)
【5】什么代码会放在finally中呢?
关闭数据库资源,关闭IO流资源,关闭socket资源
【6】有一句话代码很厉害,它可以让finally中代码不执行!
System.exit(0);//终止当前的虚拟机执行 (传入的数字无关紧要)
多重catch
【1】try中出现异常以后,将异常类型跟catch后面的类型依次比较,按照代码的顺序进行比对,执行第一个与异常类型匹配的catch语句
【2】一旦执行其中一条catch语句之后,后面的catch语句就会被忽略了!
【3】在安排catch语句的顺序的时候,一般会将特殊异常放在前面(并列),一般化的异常放在后面。
先写子类异常,再写父类异常。
import java.util.InputMismatchException;
import java.util.Scanner;
/**
* @Auther: themyth
*/
public class Test4 {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
//实现一个功能:键盘录入两个数:求商:
try {
Scanner sc = new Scanner(System.in);
System.out.println("请录入第一个数:");
int num1 = sc.nextInt();
System.out.println("请录入第二个数:");
int num2 = sc.nextInt();
System.out.println("商:"+num1/num2);
}catch(ArithmeticException ex){
System.out.println("对不起,除数不可以为0");
}catch(InputMismatchException ex){
System.out.println("对不起,你录入的数据不是int类型的数据!");
}catch(Exception ex){//写在最后
System.out.println("对不起,你的程序出现异常");
}finally{
System.out.println("----谢谢你使用计算器111");
}
}
}
【4】在JDK1.7以后,异常新处理方式:可以并列用|符号连接:
好的地方是一行代码可以写完,不好的地方:不能够知道具体是哪个异常出现了问题
通过下面代码引入问题:
public class Test {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
try {
int[] array = null;
System.out.println(array.length);
System.out.println("after...");
} catch (ArithmeticException | NullPointerException e) {
e.printStackTrace();
System.out.println("捕捉到了 ArithmeticException | NullPointerException 异常,进行处理异常的逻辑");
}
System.out.println("正常的逻辑");
}
}
上面我们学习什么是异常,以及遇到异常如何处理,下面我们再详细介绍一下异常。
异常的分类
【1】层次结构:
注意:程序中语法错误,逻辑错误 都不属于上面的Error,Exception
【2】运行时异常:
/**
* @Auther: themyth
*/
public class Test5 {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
//运行时异常:
int[] arr = {1,2,3};
System.out.println(arr.length);
/*int[] arr2 = null;
System.out.println(arr2.length);*/
System.out.println(arr[10]);
}
}
【3】检查异常:
处理方式1:try-catch嵌套try-catch
/**
* @Auther: themyth
*/
public class Test6 {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
//检查异常:
try {
try {
Class.forName("com.test01.Test").newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
处理方式2:多重catch
/**
* @Auther: themyth
*/
public class Test6 {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
//检查异常:
try {
Class.forName("com.test01.Test").newInstance();
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
}
}
处理方式3:throws类似于甩锅,将异常抛给JVM虚拟机,虚拟机其实不会处理异常,会把抛给它的异常在控制台打印出来
/**
* @Auther: themyth
*/
public class Test6 {
//这是一个main方法,是程序的入口:
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
//检查异常:
Class.forName("com.test01.Test").newInstance();
}
}
throw和throws的区别
import java.util.Scanner;
/**
* @Auther: themyth
*/
public class Test7 {
//这是一个main方法,是程序的入口:
public static void main(String[] args) throws Exception {
//实现一个功能:两个数相除,当除数为0的时候,程序出现异常。
/*try {
devide();
} catch (Exception e) {
e.printStackTrace();
}*/
devide();
}
public static void devide() throws Exception {
Scanner sc = new Scanner(System.in);
System.out.println("请录入第一个数:");
int num1 = sc.nextInt();
System.out.println("请录入第二个数:");
int num2 = sc.nextInt();
if (num2 == 0) {//除数为0,制造异常。
//制造运行时异常:
/*throw new RuntimeException();*/
//制造检查异常:
/*try {
throw new Exception();
} catch (Exception e) {
e.printStackTrace();
}*/
throw new Exception();
}
}
}
图解:
总结:
throw和throws的区别:
(1)位置不同:
throw:方法内部
throws: 方法的签名处(方法的声明处)(2)内容不同:
throw + 异常对象(检查异常,运行时异常)
注意:
- 如果抛出的是检查异常/编译时异常/受查异常,必须要进行处理,要么try catch内部捕捉,要么throws往上抛
- 如果抛出的是运行时异常/非受查异常,无需额外处理,当然可以try-catch捕获,也可以继续throws向上抛,方便后续捕捉异常(但一般不这么做)
throws + 异常的类型(可以多个类型,用,拼接)
(3)作用不同:
throw:异常出现的源头,制造异常。
throws:在方法的声明处,告诉方法的调用者,这个方法中可能会出现我声明的这些异常。然后调用者对这个异常进行处理:要么自己处理要么再继续向外抛出异常
练习:
/**
* @Auther: themyth
*/
public class Student {
private String name;
private int age;
private String sex;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) throws Exception {
if("男".equals(sex) || "女".equals(sex)){//sex.equals("男)这样写---》容易发生空指针异常
this.sex = sex;
}else{//非男非女
//解决办法1:
/*this.sex = "男";*/
//解决办法2:给个友好型提示,但是打印结果为默认的null效果
/*System.out.println("对不起,你的性别错误");*/
//解决办法3:
//制造运行时异常:也可以在内部用try-catch捕获,或者往上抛(一般多数人不会管运行时的异常)
/*throw new RuntimeException("性别错误!");*/
//制造检查异常:必须在内部用try-catch捕获,或者往上抛
/*try {
throw new Exception();
} catch (Exception e) {
e.printStackTrace();
}*/
throw new Exception();
}
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
'}';
}
public Student() {
}
public Student(String name, int age, String sex) {
this.name = name;
this.age = age;
/*this.sex = sex;*/
try {//可在内部捕获,也可以往上抛,如果往上抛,Person构造器就需要解决抛它的异常,它也可以选择往上抛,或者在内部捕获
this.setSex(sex);
} catch (Exception e) {
e.printStackTrace();
}
}
}
【异常处理流程总结】
- 程序先执行 try 中的代码 如果 try 中的代码出现异常, 就会结束 try 中的代码, 看和 catch 中的异常类型是否匹配.
- 如果找到匹配的异常类型, 就会执行 catch 中的代码
- 如果没有找到匹配的异常类型, 就会将异常向上传递到上层调用者.
- 无论是否找到匹配的异常类型, finally 中的代码都会被执行到(在该方法结束之前执行).
- 如果上层调用者也没有处理的了异常, 就继续向上传递.
- 一直到 main 方法也没有合适的代码处理异常, 就会交给 JVM 来进行处理, 此时程序就会异常终止.
重载和重写的异常
【1】重载: 抛出异常无关
/**
* @Auther: themyth
*/
public class Demo {
public void a(){
}
public void a(int age) throws RuntimeException{
}
public void a(int age,String name) throws ArithmeticException{
}
}
【2】重写:子类 <= 父类
自定义异常
Java中虽然已经内置了丰富的异常类, 但是并不能完全表示实际开发中所遇到的一些异常,此时就需要维护符合我们实际情况的异常结构。
【注意事项】 先回顾一下)
自定义异常通常会继承自 Exception 或者 RuntimeException
继承自 Exception 的异常默认是 检查异常/编译器异常/受查异常
继承自 RuntimeException 的异常则是 运行时异常/非受查异常.
1.继承运行时异常:
/**
* @Auther: themyth
*/
public class MyException extends RuntimeException{
static final long serialVersionUID = -70348971907L;
public MyException(){
}
public MyException(String msg){
super(msg);
}
}
2.继承检查异常:
/**
* @Auther: themyth
*/
public class MyException extends Exception{
static final long serialVersionUID = -70348971907L;
public MyException(){
}
public MyException(String msg){
super(msg);
}
}
练习1:用户登陆功能异常的捕获
public class Test {
private String name = "admin";
private String password = "123456";
public void login(String name, String password) {
if (!this.name.equals(name)) {
System.out.println("用户名错误!");
return;
}
if (!this.password.equals(password)) {
System.out.println("密码错误!");
return;
}
}
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
Test test = new Test();
test.login("admin", "123");
}
}
、
此时我们就需要去定义一些自定义的异常,如下:
public class UserNameException extends RuntimeException{
public UserNameException() {
}
public UserNameException(String message) {
super(message);
}
}
public class PassWordException extends RuntimeException{
public PassWordException() {
}
public PassWordException(String message) {
super(message);
}
}
public class Test {
private String name = "admin";
private String password = "123456";
public void login(String name, String password) throws UserNameException, PassWordException {//这个自定义异常继承的的运行时异常,这儿进行抛出是为了后面好捕捉这个异常
if (!this.name.equals(name)) {
throw new UserNameException("你的用户名有错!");
}
if (!this.password.equals(password)) {
throw new PassWordException("你的密码有错!");
}
}
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
Test test = new Test();
try {
test.login("admin", "123");
} catch (UserNameException e) {
e.printStackTrace();
System.out.println("用户名异常");
} catch (PassWordException e) {
e.printStackTrace();
System.out.println("密码异常");
}
}
}
练习2:三角形三边异常的捕获
import java.util.Scanner;
class IllegalTriangleException extends Exception {
String message;//定义要显示的异常信息
public IllegalTriangleException(double side1, double side2, double side3) {
message = "Invalid: " + side1 + "," + side2 + "," + side3;
}
@Override
public String getMessage() {
return message;
}
}
class Triangle {
double side1;
double side2;
double side3;
public Triangle(double side1, double side2, double side3) throws IllegalTriangleException {
if (side1 + side2 > side3 && side1 + side3 > side2 && side2 + side3 > side1) {
this.side1 = side1;
this.side2 = side2;
this.side3 = side3;
} else {
throw new IllegalTriangleException(side1, side2, side3);
}
}
@Override
public String toString() {
return "Triangle [side1=" + side1 + ", side2=" + side2 + ", side3=" + side3 + "]";
}
}
public class Main {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
while (input.hasNext()) {
double s1 = input.nextDouble();
double s2 = input.nextDouble();
double s3 = input.nextDouble();
try {
Triangle t = new Triangle(s1, s2, s3);
System.out.println(t.toString());
} catch (IllegalTriangleException ex) {
System.out.println(ex.getMessage());//调用重写后的方法
}
}
}
}
改进代码:
import java.util.Scanner;
class IllegalTriangleException extends Exception {
public IllegalTriangleException(String message) {//IDEA生成的构造方法
super(message);
}
}
class Triangle {
double side1;
double side2;
double side3;
public Triangle() {
}
public Triangle(double side1, double side2, double side3) throws IllegalTriangleException {
if (side1 + side2 > side3 && side1 + side3 > side2 && side2 + side3 > side1) {
this.side1 = side1;
this.side2 = side2;
this.side3 = side3;
} else {
throw new IllegalTriangleException("Invalid: " + side1 + "," + side2 + "," + side3);
}
}
@Override
public String toString() {
return "Triangle [side1=" + side1 + ", side2=" + side2 + ", side3=" + side3 + "]";
}
}
public class Main {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
while (input.hasNext()) {
double s1 = input.nextDouble();
double s2 = input.nextDouble();
double s3 = input.nextDouble();
try {
Triangle t = new Triangle(s1, s2, s3);
System.out.println(t.toString());
} catch (IllegalTriangleException ex) {
System.out.println(ex.getMessage());
}
}
}
}
注意:
- 如果继承的是运行时异常/非受查异常,那么在使用的时候无需额外处理,当然可以try-catch捕获,也可以继续throws向上抛,方便后续捕捉异常(但一般不这么做)
- 如果继承的是检查异常/编译时异常/受查异常,那么使用的时候需要try-catch捕获或者throws向上抛