一、异常
(一)异常的概念和体系
1、异常的概念
- 异常: 指的是程序在执行过程中,出现的非正常情况,最终会导致JVM非正常停止。
在Java等面向对象的编程语言中,异常本身是一个类,产生异常是创建异常对象并抛出了一个异常对象。Java处理异常的方式是中断处理。
- 注意:异常指的并不是语法错误,若编译错误,则不会产生字节码文件,根本不能运行。
2、异常的体系
- 异常的根类是java.lang.Throwable,其下有两个子类:java.lang.Error与java.lang.Exception,平常所说的异常指的是java.lang.Exception。
- Error:不能处理只能避免 Exceptio:使用不当导致,可以避免。
(二)异常的分类
- java.lang.Throwable类是Java语言中所有错误或异常的超类。
1、Exception
- 编译器异常,进行编译Java程序出现的问题。异常处理掉后,程序可以继续执行。
- RuntimeException:运行期异常,Java程序运行过程中出现的问题。
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class e1 {
public static void main(String[] args) /*throws ParseException*/ {
//异常处理两种方式:thorw 和 try catch
//Exception:编译器异常,编译Java程序出现的问题
SimpleDateFormat sdf = new SimpleDateFormat("yyy-MM-dd");//格式化日期
Date date = null;
try {
date = sdf.parse("1999-09-09");
} catch (ParseException e) {
e.printStackTrace();
}
System.out.println(date);
//RuntimeException:运行期异常,Java程序运行过程出现的问题
int[] arr = {1, 2, 3};
try {
//可能出现异常的代码
System.out.println(arr[3]);/*Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
at Demo12.e1.main(e1.java:21)*/
} catch (Exception e) {
//异常处理的逻辑
System.out.println(e);
}
}
}
Thu Sep 09 00:00:00 CST 1999
java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
2、Error:错误
- 必须修改源代码,程序才可以继续执行。
/*错误:创建超过JVM分配内存大小的数组
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at Demo12.e1.main(e1.java:30)
只能通过修改代码避免错误
int [] arr1 = new int[100000*100000];*/
int [] ar = new int[1024];
System.out.println("后续代码!");
(三)异常的产生过程解析
二、异常的处理
-
Java处理异常的五个关键字:try、catch、finall、throw、throws。
(一)throw关键字
1、作用
- 可以使用throw关键字在指定的方法中抛出指定的异常
2、使用格式
throw new xxxException(异常产生的原因)
3、注意
- throw关键字必须写在方法的内部
- throw关键字后边new的对象必须是Exception或者Exception的子类对象
- throw关键字抛出指定的异常对象,我们就必须处理这个异常对象
- throw关键字后边创建的是RuntimeException或者是其子类对象我们可以不处理默认交给JVM处理(打印异常对象然后中断程序)
- throw关键字后边创建的是编译异常,我们就必须处理这个异常,要么throws,要么try...catch
4、实例
- 在实际操作中,我们首先必须对方法传递过来的参数进行合法性校验。如果参数不合法,那么我们就必须使用抛出异常的方法告知方法的调用者传递的参数有问题。
public class e2 {
public static void main(String[] args) {
/*int [] arr1 = null;
int e1=getarr(arr1,0);
System.out.println(e1);
Exception in thread "main" java.lang.NullPointerException: 传递的数组为空!*/
int [] arr2 =new int[3];
int e2=getarr(arr2,5);
System.out.println(e2);
//Exception in thread "main" java.lang.NullPointerException: 传递的数组为空!
}
public static int getarr(int [] arr, int index){
/*对传递的数组参数进行合法性检验,若传递的数组为NULL,则抛出空指针异常
若传递的数组索引越界则抛出数组索引越界异常。
*/
if(arr==null){
throw new NullPointerException("传递的数组为空!");
}
if(index<0||index>arr.length-1){
throw new ArrayIndexOutOfBoundsException("数组索引越界");
}
int ele = arr[index];
return ele;
}
}
(二)Objects非空判断
- Objects类中的静态方法
public static <T> requireNonNull( T obj ):查看指定的引用对象是否为null
import java.util.Objects;
public class e3 {
public static void main(String[] args) {
method(null);//Exception in thread "main" java.lang.NullPointerException: 传递对象为null!
}
public static void method(Object obj){
Objects.requireNonNull(obj,"传递对象为null!");
}
}
(三)声明异常throws
- 异常处理的第一种方式,交给别人处理
1、作用
- 当方法内部抛出异常对象的时候,那么我们就必须处理这个异常对象
- 可以使用throws关键字处理异常对象,会把异常对象声明抛出给方法的调用者处理,最终交给JVM处理——>中断处理
2、使用格式:在方法声明时使用
修饰符 返回值类型 方法名(参数列表)throws AAAException,BBBException...{
throw new AAAException("产生原因");
throw new BBBException("产生原因");
...
}
3、注意
- throws关键字必须写在方法声明处
- throws关键字后边生命的异常必须是Exception或者是Exception子类
- 方法内部如果抛出了多个异常对象,那么throws后边必须也声明多个异常,如果抛出的异常对象有子父类关系,那么直接声明父类异常即可(所有异常均是Exception子类,声明只声明Exception即可)。
- 调用一个声明抛出异常的方法,我们就必须处理声明的异常。要么继续使用throws声明抛出交给方法的调用者处理最终交给JVM。要么try...catch自己处理异常。
import java.io.FileNotFoundException;
import java.io.IOException;
public class e3{
public static void main(String[] args)throws Exception {
readfile("C\\a.txt");
}
/*定义一个文件,对文件的路径和格式进行判断*/
// public static void readfile(String filename)throws FileNotFoundException,IOException
public static void readfile (String filename)throws Exception{
//文件路径异常处理
if(!filename.equals("C\\a.txt")){
throw new FileNotFoundException("文件路径异常!");
}//Exception in thread "main" java.io.FileNotFoundException: 文件路径异常!
//文件格式异常处理
if(!filename.endsWith(".txt")){
throw new IOException("文件格式异常!");
}//Exception in thread "main" java.io.IOException: 文件格式异常!
System.out.println("文件读取正常。");//文件读取正常。
}
}
注意:FileNotFoundException extends IOException extends Exception
(四)捕获异常 try...catch
- 异常处理的第二种方式,自己处理异常
1、格式
try{
可能产生异常的代码
}catch(定义一个异常的变量,用来接收try中抛出的异常对象){
异常的处理逻辑,接收异常对象之后,怎么处理异常
一般在实际中,会把异常的信息记录到日志中
}
...
catch(异常类名 变量名){
}
2、注意
- try中可能会抛出多个异常对象,那么就可以使用多个catch来处理这些异常对象
- 如果try中产生异常,那么就会执行catch中的异常处理逻辑,执行完catch中的处理逻辑,继续执行try...catch之后的代码。如果try中没有产生异常,那么就不会执行catch中异常的处理逻辑,执行完try中的代码,继续执行try...catch之后的代码。
import java.io.IOException;
public class e3{
public static void main(String[] args) {
try {
readfile("c:\\a.tx");
} catch (IOException e) {
e.printStackTrace();
}//java.io.IOException: 文件格式异常!
System.out.println("后续代码!");//后续代码正常执行
}
public static void readfile(String filename)throws IOException{
if(!filename.endsWith(".txt")){
throw new IOException("文件格式异常!");
}
System.out.println("文件读取正常");
}
}
(五)Throwable类中处理异常的三个方法
String getMessage() 返回此 throwable 的简短描述
String toString() 返回此 throwable 的详细消息字符串
void printStackTrace() JVM打印异常对象,打印的异常信息最为全面
try {
readfile("c:\\a.tx");
} catch (IOException e) {
// System.out.println(e.getMessage());//文件格式异常!
// System.out.println(e.toString());//java.io.IOException: 文件格式异常!
// System.out.println(e);//java.io.IOException: 文件格式异常!
e.printStackTrace();
// java.io.IOException: 文件格式异常!
// at Demo12.e3.readfile(e3.java:20)
// at Demo12.e3.main(e3.java:8)
}
(六)finally代码块
1、格式
try{
可能产生异常的代码
}catch(定义一个异常的变量,用来接收try中抛出的异常对象){
异常的处理逻辑,接收异常对象之后,怎么处理异常
一般在实际中,会把异常的信息记录到日志中
}
...
catch(异常类名 变量名){
}finally{
无论是否出现异常都会执行
}
2、注意
- finally不能单独使用,必须和try一起使用
- finally一般用于资源释放(资源回收),无论程序是否出现异常,最后都要资源释放
try {
readfile("c:\\a.tx");
} catch (IOException e) {
e.printStackTrace();
}finally {
System.out.println("资源释放!");
}//资源释放!正常运行
}
(七)异常的注意事项
-
多异常的捕获处理
- 多个异常分别处理
- 多个异常一次捕获,多次处理
- 多个异常一次捕获一次处理
//多个异常分别处理
public class e3{
public static void main(String[] args) {
try{
int[] arr =new int[3];
System.out.println(arr[3]);
}catch(ArrayIndexOutOfBoundsException e)
{
e.printStackTrace();
}
try {
List<Integer> list = List.of(1,2,3);
System.out.println(list.get(3));
}catch (IndexOutOfBoundsException e){
e.printStackTrace();
}
System.out.println("后续代码!");
}
}
/*java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
at Demo12.e3.main(e3.java:11)
java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
at java.base/java.util.ImmutableCollections$ListN.get(ImmutableCollections.java:547)
at Demo12.e3.main(e3.java:19)
后续代码!*/
import java.util.List;
/*一次捕获多次处理
注意:异常变量若存在父子类关系则子类变量必须写在上面,否则会报错
原因:try中抛出的异常对象会从上到下依次赋值给catch中定义的变量(多态)
*/
public class e4{
public static void main(String[] args) {
try{
int[] arr =new int[3];
System.out.println(arr[3]);
List<Integer> list = List.of(1,2,3);
System.out.println(list.get(3));
}catch(ArrayIndexOutOfBoundsException e)
{
e.printStackTrace();
}
catch (IndexOutOfBoundsException e){
e.printStackTrace();
}System.out.println("后续代码!");
}
// java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
// at Demo12.e3.main(e3.java:13)
// 后续代码
}
//一次捕获一次处理
public class e5 {
public static void main(String[] args) {
try {
int[] arr = new int[3];
System.out.println(arr[3]);
List<Integer> list = List.of(1, 2, 3);
System.out.println(list.get(3));
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("后续代码!");
}
}
//java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
// at Demo12.e3.main(e3.java:12)
// 后续代码!
注意:运行时异常被抛出可以不处理,默认给虚拟机处理,终止程序,什么时候不抛出运行异常了在来继续执行程序。
-
finally有return语句,永远返回finally中结果,避免出现该情况。
public class e6 {
public static void main(String[] args) {
int a = getnumber(10);
System.out.println(a);
}//输出为100
public static int getnumber(int a){
try{
a = 10;
return a;
}catch (Exception e){
System.out.println(e);
}finally {
a=100;
return a;
}
}
}
-
子父类异常
- 如果父类抛出了多个异常,子类重写父类方法时,抛出和父类相同的异常或者是父类异常的子类或者不抛出异常。
- 父类方法没有抛出异常,子类重写父类该方法时也不可抛出异常,此时子类产生该异常,只能捕获处理,不能声明抛出。
- 注意:父类异常什么样,子类异常就是什么样。
//父类不抛出异常子类产生该异常,只能捕获处理,不能声明抛出
public class Fu{
public void show01() {}
}
class zi extends Fu{
public void show01() {
try{
throw new Exception("异常");
}catch (Exception e){
e.printStackTrace();
}
}
}
三、自定义异常
(一)概述
- Java提供的异常类,不够我们使用,需要自己定义一些异常类。
1、格式
public class XXXException extends Exception | RuntimeException{
添加一个空参数的构造方法
添加一个带异常信息的构造方法
}
2、注意
- 自定义异常类一般都是以Exception结尾,说明该类是一个异常类
- 自定义异常类必须继承Exception或者RuntimeException
- 继承Exception:自定义的异常类就是一个编译器异常,如果方法内部抛出了编译器异常,就必须处理这个异常,要么throws,要么try...catch
- 继承RuntimeException:自定义的异常类就是一个运行期异常,交给虚拟机处理(中断处理)。
(二)自定义异常类的练习
-
要求:模拟注册操作,如果用户名已经存在,则抛出异常并提示:该用户名已被注册
-
分析:
- 使用数组保存已经注册过的用户名(数据库)
- 使用Scanner获取用户输入的注册的用户名(前端,页面)
- 定义一个方法,对用户输入注册的用户名进行判断,遍历存储已经注册过用户名的数组获取每一个用户名,使用获取到的用户名和用户输入的用户名进行比较。
- true:用户名已经存在,抛出RegisterException异常,告知用户“用户名已被注册”
- false:继续遍历比较。如果循环结束还未找到重复的用户名,提示用户“恭喜您,注册成功!”
-
代码实现
//自定义异常类
public class RegisterException extends Exception {
public RegisterException() {
super();
}
public RegisterException(String message){
super(message);
}
}
import java.util.Scanner;
public class Register {
//存储用户数据
static String[] usernames={"张三","李四","王麻子"};
public static void main(String[] args) {
//让用户输入用户名
Scanner sc = new Scanner(System.in);
System.out.println("请输入要注册的用户名");
String username=sc.next();
check(username);
}
/*利用方法判断用户名是否与原有用户数据重合
如果没有则注册成功,如果有重复则抛出异常。
*/
private static void check(String username) {
for (String s : usernames) {
if(s.equals(username)){
try {
throw new RegisterException("该用户名已被注册!");
} catch (RegisterException e) {
e.printStackTrace();
return;//停止方法运行
}
}
} System.out.println("恭喜您,注册成功!");
}
}
请输入要注册的用户名
张三
该用户名已被注册!
请输入要注册的用户名
赵四
恭喜您,注册成功!