1. 异常的概念
在Java中异常被当做对象来处理,根类是java.lang.Throwable类。所有异常类分为两大类:Error和Exception
- Error是无法处理的异常,比如OutOfMemoryError(内存),一般发生这种异
常,JVM会选择终止程序。因此我们编写程序时不需要关心这类异常
- Exception,也就是我们经常见到的一些异常情况,这些异常是我们可以处理的
2. 常见的异常及捕获
下表中列出了几个常见的异常,其中所有的异常都是Exception的子类:
我们使用try catch块来捕获异常,最基本的用法如下,我们捕获一个NullPointerException异常:
Ps:请留意异常的打印到栈的用法。
public class Ex1 {
public static void main(String[] args) {
// TODO Auto-generated method stub
try {
/*
* 1.抛出到main的外面
* 2.捕获
*/
// int a = 10/0;
String string = null;
System.out.println(string.length());
System.out.println("不报错");
} catch (Exception e) {
// TODO: handle exception
//异常的打印到栈
e.printStackTrace();
System.out.println("报错了");
}
System.out.println("程序结束!!");
}
}
//输出:
//java.lang.NullPointerException
//报错了
//程序结束!!
// at Ex1.main(Ex1.java:15)
我们还可以捕获一个日期类型的异常:
这里我们顺便复习了如何用Date类去统计某一段程序的运行时间。
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Ex2 {
public static void main(String[] args) {
//统计运行时间
Date d1=new Date();
System.out.println(d1);
long t1 = d1.getTime();
for(int i = 0;i < 100000000 ;i++) {
int a = i;
for(int j = 0;j < 100000000 ;j++) {
int b = 1;
}
}
Date d2 = new Date();
long t2 = d2.getTime();
System.out.println(t2-t1);
//异常
SimpleDateFormat s = new SimpleDateFormat("yyyy-MM-dd");
try {
Date d3 = s.parse("2020-12-aaaaaaa");
System.out.println(s.format(d3));
System.out.println("okokoko");
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
/*
Sun Apr 12 22:19:29 CST 2020
78
java.text.ParseException: Unparseable date: "2020-12-aaaaaaa"
at java.base/java.text.DateFormat.parse(Unknown Source)
at Ex2.main(Ex2.java:26)
* */
*/
上面这段程序的异常类型是ParseException类型,我们可以从parse函数的源码中知道为什么。
public Date parse(String source) throws ParseException
{
ParsePosition pos = new ParsePosition(0);
Date result = parse(source, pos);
if (pos.index == 0)
throw new ParseException("Unparseable date: "" + source + """ ,
pos.errorIndex);
return result;
}
下面我们捕获一个ClassCastException异常;
public class Man {
public static void main(String[] args) {
// TODO Auto-generated method stub
try {
//向上转换
Man m1 = new Teacher();
Student s1 = (Student)m1;
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
System.out.println("运行结束");
}
}
/*
java.lang.ClassCastException: Teacher cannot be cast to Student
运行结束
at Man.main(Man.java:12)
*/
public class Student extends Man{
}
public class Teacher extends Man{
}
下面是一个ArrayIndexOutOfBoundsException异常,同样的还有字符串对应的IndexOutOfBoundsException(不演示了);
public class Ex3 {
public static void main(String[] args) {
// TODO Auto-generated method stub
try {
int[] num = new int[3];
num[4] = 2;
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
//
//java.lang.ArrayIndexOutOfBoundsException: 4
//at Ex3.main(Ex3.java:9)
下面捕获一个输入异常InputMismatchException,如果我们输入了一个字符串,基本的情况如下;
import java.util.Scanner;
public class Ex4 {
public int getInputNum() {
Scanner s = new Scanner(System.in);
return s.nextInt();
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Ex4 ex4 = new Ex4();
System.out.println(ex4.getInputNum());
}
}
/**
Exception in thread "main" java.util.InputMismatchException
at java.base/java.util.Scanner.throwFor(Unknown Source)
at java.base/java.util.Scanner.next(Unknown Source)
at java.base/java.util.Scanner.nextInt(Unknown Source)
at java.base/java.util.Scanner.nextInt(Unknown Source)
at Ex4.getInputNum(Ex4.java:7)
at Ex4.main(Ex4.java:14)
*/
我们想要让这段代码具有更高的容错率,如果我们输入了字符串或其他,应该让我们重复输入,直到我们输入了正确的int类型:
import java.util.Scanner;
public class Ex4 {
public int getInputNum() {
while (true) {
try {
System.out.println("请输入数字:");
Scanner s = new Scanner(System.in);
return s.nextInt();
} catch (Exception e) {
// TODO: handle exception
System.out.println("您的输入不是数字");
}
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Ex4 ex4 = new Ex4();
System.out.println(ex4.getInputNum());
}
}
/**
请输入数字:
sen
您的输入不是数字
请输入数字:
12
12
*/
3. 异常的抛出
异常可以大致分为两类:编译时异常(显式异常)和运行时异常(隐式异常)
首先我们来看一个显式异常的例子:
这里的parseException是一个显式异常,必须在编译期处理,类似的还有SQLException和IOException
我们对于这种异常,必须在编写程序的时候添加抛出(throws ParseException),不然编译器就会爆红;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Ex5 {
public Date getDate(String sDate) throws ParseException {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
return simpleDateFormat.parse(sDate);
}
//parseException 显式异常,必须在编译期处理
//类似的还有SQLException和IOException
public static void main(String[] args) {
Ex5 ex5 = new Ex5();
try {
System.out.println(ex5.getDate("2020-1-3"));
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
下面再来看一个隐式异常:
这里如果分母是0的话会出现异常,这种异常在编译期间是不处理的,编译期间不报错,如果不处理,一旦发生异常,就会造成程序的终止。
public class Ex6 {
public int math(int a,int b) {
return a/b;
}
/**
* 隐式异常(运行时异常)的处理
* 这种异常在编译期间是不处理的,编译期间不报错;
* 如果不处理,一旦发生异常,就会造成程序的终止
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Ex6 ex6 = new Ex6();
ex6.math(10, 0);
System.out.println("all is ok");
}
}
/**
Exception in thread "main" java.lang.ArithmeticException: / by zero
at Ex6.math(Ex6.java:6)
at Ex6.main(Ex6.java:17)
*/
在处理异常的时候我们有两个关键字 throws和throw。
throws的用法就是在函数的方法名后面可以抛出异常,throw可以用来转换异常。
我们可以看出throw是动词,所以是一个转换的动作。
注意,异常的抛出和截获要保持一致。
这里利用throw把ParseException转换成IOException。(并没有什么用,仅仅为了演示用法)
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.imageio.IIOException;
public class Ex5 {
public Date getDate(String sDate) throws IOException {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
try {
return simpleDateFormat.parse(sDate);
} catch (ParseException e) {
//利用throw把ParseException转换成IOException
//同时,throws对应的异常也要改。
throw new IOException();
}
}
//parseException 显式异常,必须在编译期处理
//类似的还有SQLException和IOException
public static void main(String[] args) {
Ex5 ex5 = new Ex5();
try {
System.out.println(ex5.getDate("AAA-1-3"));
//异常的抛出和截获要保持一致。
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
/**
java.io.IOException
at Ex5.getDate(Ex5.java:18)
at Ex5.main(Ex5.java:28)
*/
4. 自定义异常
我门可以通过继承Exception来自定义异常,自定义异常实际上是实际上用的最多的地方。
我门假设以下情况:
这里我们主要模拟这里的用户异常和系统异常。
首先先创建系统异常和用户异常两个异常类,继承与Exception。
package demo2;
//自定义异常--用户级异常
public class appException extends Exception{
// 异常码和异常信息
private int ErrorCode;
private String ErrMessags;
/**
* 有参数的构造器
* @param errorCode
* @param errMessags
*/
public appException(int errorCode, String errMessags) {
super();
ErrorCode = errorCode;
ErrMessags = errMessags;
}
public int getErrorCode() {
return ErrorCode;
}
public void setErrorCode(int errorCode) {
ErrorCode = errorCode;
}
public String getErrMessags() {
return ErrMessags;
}
public void setErrMessags(String errMessags) {
ErrMessags = errMessags;
}
}
package demo2;
//自定义异常--系统级异常
public class systemException extends Exception{
// 异常码和异常信息
private int ErrorCode;
private String ErrMessags;
/**
* 有参数的构造器
* @param errorCode
* @param errMessags
*/
public systemException(int errorCode, String errMessags) {
super();
ErrorCode = errorCode;
ErrMessags = errMessags;
}
public int getErrorCode() {
return ErrorCode;
}
public void setErrorCode(int errorCode) {
ErrorCode = errorCode;
}
public String getErrMessags() {
return ErrMessags;
}
public void setErrMessags(String errMessags) {
ErrMessags = errMessags;
}
}
随后我们模拟上图中的两种异常:
建立主函数来调用我们创建好的两个异常:
package demo2;
public class Ex8 {
public int math(int a,int b) throws appException, systemException {
try {
// 数组越界异常,利用系统异常抛出
int[] nums = new int[3];
nums[3] = 8;
// 数学运算异常,利用用户异常抛出
return a/b;
} catch (ArithmeticException e) {
//转换成自定义异常为用户级异常;
throw new appException(1001, "b不能为0!");
}catch (IndexOutOfBoundsException e) {
// TODO: handle exception
throw new systemException(999, "系统异常!");
}
}
public static void main(String[] args) {
Ex8 ex8 = new Ex8();
try {
ex8.math(20, 0);
} catch (appException e) {
System.out.println(e.getErrMessags());
}catch (systemException e) {
System.out.println(e.getErrMessags());
}
System.out.println("finished!");
}
}
//系统异常!
//finished!