JavaEE学习日志持续更新----> 必看!JavaEE学习路线(文章总汇)
Java学习日志(九)
异常介绍
异常的概念
异常 :指的是程序在执行过程中,出现的非正常的情况,终会导致JVM的非正常停止。
在Java等面向对象的编程语言中,异常本身是一个类(存在于java.lang包),产生异常就是创建异常对象并抛出了一个异常对象。Java处理异常的方式是中断处理。
异常的继承体系:
异常与错误的区别
异常:程序出现的小问题
- 编译期异常:写代码的时候,程序出现了异常
- 运行期异常:运行程序的时候出现的异常
异常都是可以解决的,解决之后,程序继续运行
错误:程序出现了严重问题
出现了错误,必须修改源代码,让错误不再出现
示例
public class Demo01Error {
public static void main(String[] args) {
//编译器异常:写代码的时候,程序出现了异常
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date date = null;
try {
date = sdf.parse("2018-08-19");
} catch (ParseException e) {
e.printStackTrace();
}
System.out.println(date);
//运行期异常:运行程序的时候,出现的异常
try {
int[] arr = {1, 2, 3};
System.out.println(arr[3]);//ArrayIndexOutOfBoundsException
}catch (ArrayIndexOutOfBoundsException e){
e.printStackTrace();
}
//错误:程序出现了严重问题
int[] arr = new int[1024*1024*1024];//OutOfMemoryError 内存溢出,必须修改代码
System.out.println("后续代码");
}
}
异常的产生过程
如有以下代码产生了数组索引越界异常ArrayIndexOutOfBoundsException
public class Demo02 {
public static void main(String[] args) {
int[] arr = {1,2,3};
int element = getElement(3, arr);
System.out.println(element);//ArrayIndexOutOfBoundsException
}
/*
* 定义一个方法,根据数组的索引查找并返回对应元素
* 参数:
* int index
* int[] arr
* */
public static int getElement(int index,int[] arr){
int element = arr[index];
return element;
}
}
访问了数组的3索引,而数组没有3索引,这时JVM就会检查出程序会出现异常,
JVM会做三件事情:
-
会根据异常产生的原因,创建一个异常相关的对象,这个对象包含了异常产生的原因和位置
new ArrayIndexOutOfBoundsException(3);
-
JVM发现getElement方法中没有解决异常相关的逻辑,所以就会把异常对象抛出给方法的调用者main方法处理,main方法接收到了异常对象,JVM发现main方法也没有解决异常的逻辑,就会把异常对象继续抛出给main方法的调用者JVM处理
-
JVM接收到了异常对象,JVM会把异常相关的原因和位置以红色的字体打印在控制台,并且终止当前正在执行的程序(中断处理)
异常处理
throw关键字
作用:使用throw关键字,可以在指定的位置抛出指定的异常对象
格式: throw new xxxException("异常产生的原因");
注意事项:
- throw关键字必须写在方法的内部
- throw关键字后边创建的异常对象,必须是Exception或Exception的子类对象
- throw关键字后边创建的异常对象,如果是运行期异常,那么不用处理这个异常,交给JVM处理,默认处理方式就是中断处理。如果是编译期异常,那么就必须处理这个异常,要么throws抛出给别人处理,要么try…catch自己处理
示例:运行期异常
public class Demo03throw {
public static void main(String[] args) {
//int[] arr = null;//java.lang.NullPointerException: 数组为空
int[] arr = {1,2,3};//java.lang.ArrayIndexOutOfBoundsException: 参数index3超出数组索引范围
//int element = getElement(0, arr);
int element = getElement(3, arr);
System.out.println(element);
}
/*
* 定义一个方法,根据数组的索引查找并返回对应元素
* 参数:
* int index
* int[] arr
* 在工作中,会对方法进行合法性判断
* 如果传递的参数不合法,就可以使用抛出异常的方式告知方法的调用者,
* 传递参数不合法
* */
public static int getElement(int index, int[] arr) {
/*
对传递的参数数组arr进行判断,判断数组是否为null
如果传递的数组为null
那么就抛出一个异常对象,告知方法的调用者,数组的值为null
*/
if(arr == null){
throw new NullPointerException("数组为空");
}
/*
对传递的参数index进行判断,判断index是否在索引范围内
如果不在索引范围内
那么就抛出一个异常对象,告知方法的调用者,index超出数组索引范围
*/
if (index<0||index>arr.length-1){
throw new ArrayIndexOutOfBoundsException("参数index"+index+"超出数组索引范围");
}
int element = arr[index];
return element;
}
}
requireNonNull方法
Objects工具类中的静态方法
static <T> T requireNonNull(T obj)
检查指定的对象引用是否不是 null 。
static <T> T requireNonNull(T obj, String message)
检查指定的对象引用是否不是 null 。
底层源码:
public static <T> T requireNonNull(T obj, String message) {
if (obj == null) {
throw new NullPointerException(message);
} else {
return obj;
}
}
使用示例:
public class Demo04Objects {
public static void main(String[] args) {
method(null);
}
private static void method(Object obj) {
/*if(obj == null){
throw new NullPointerException("对象为空");
}*/
//简便写法
//Objects.requireNonNull(obj);//java.lang.NullPointerException
Objects.requireNonNull(obj,"对象为空");//java.lang.NullPointerException: 对象为空
}
}
throws关键字
异常处理的第一种方式(声明抛出异常对象)
作用:
当方法内部抛出异常对象时,可以使用throws关键字处理这个异常,会继续把异常对象声明抛出给方法的调用者处理
格式:
修饰符 返回值类型 方法名(参数) throws xxxException,yyyException...{
throw new xxxException();
throw new yyyException();
}
注意事项:
- throws关键字必须写在方法声明处
- throws关键字后边声明的异常类名必须是Exception或者是throws关键字的子类
一般啊方法内部抛出什么异常对象,就声明什么异常对象 - 如果方法内部抛出多个异常对象,那么throws后边就必须声明多个异常类
抛出的多个异常对象有子父类关系,那么声明父类异常即可 - 如果调用了一个声明抛出异常对象的方法,那么就必须处理这个异常对象
a.可以使用throws继续声明抛出这个异常对象,最终给JVM处理—>异常处理
b.可以使用try…catch自己处理这个异常对象
示例:
public class Demo05throws {
public static void main(String[] args) throws Exception{
checkFilePath("c:\\a.java");//java.io.FileNotFoundException: 文件路径错误
System.out.println("后续代码");//若发生异常,后续代码不执行
}
/*
* 定义一个方法,对传递的文件路径进行合法性判断
* */
//FileNotFoundException extends IOException extend Exception
public static void checkFilePath(String fileName) throws Exception{
/*
对文件路径进行判断,判断路径是否是d:\\a.txt
如果路径不是d:\\a.txt,那么就抛出异常对象,告知方法的调用者,
传递路径不正确
*/
if(!"d:\\a.txt".equals(fileName)){
throw new FileNotFoundException("文件路径错误");
}
/*
对文件名后缀进行判断,判断是否以".txt"结尾
不是".txt"结尾,那么就抛出异常对象,告知方法的调用者,
文件名后缀不正确
*/
if(!fileName.endsWith(".txt")){
throw new IOException("文件名后缀不正确");
}
System.out.println("读取文件");
}
}
try…catch关键字
异常处理的第二种方式(自己捕获处理异常)
作用:当方法内部抛出异常对象时,可以使用try…catch关键字处理这个异常
好处:程序如果有后续代码,会把异常处理完毕之后,继续执行
格式:
try{
可能会产生异常的代码(没有异常的代码也可以放进来)
}catch(异常类的数据类型 变量名){
异常的处理逻辑(随意编写)
}
...
catch(异常类的数据类型 变量名){
异常的处理逻辑(随意编写)
}
注意事项:
- catch中定义的异常变量是根据try中抛出的异常来定义,一般抛出什么异常,就定义什么异常变量来接收异常对象
- try中可能会抛出多个异常对象,就可以让他定义多个catch来分别处理这些异常对象
- try代码中如果出现了异常,那么就不会继续执行try中的代码,会执行catch中异常处理逻辑,处理完毕;继续执行try…catch之后的代码
try代码中如果没有出现异常,正常执行try中的代码,不会执行catch中的代码,执行完毕,继续执行try…catch之后的代码
示例:
public class Demo06tryCatch {
public static void main(String[] args) {
try {
checkFilePath("c:\\a.java");
System.out.println("读取文件");
} catch (FileNotFoundException e) {
//异常处理逻辑,随笔编写
System.out.println("发生了FileNotFoundException异常");//发生了FileNotFoundException异常
}
System.out.println("后续代码");
}
/*
* 定义一个方法,对传递的文件路径进行合法性判断
* */
public static void checkFilePath(String fileName) throws FileNotFoundException {
/*
对文件路径进行判断,判断路径是否是d:\\a.txt
如果路径不是d:\\a.txt,那么就抛出异常对象,告知方法的调用者,
传递路径不正确
*/
if(!"d:\\a.txt".equals(fileName)){
throw new FileNotFoundException("文件路径错误");
}
/*
对文件名后缀进行判断,判断是否以".txt"结尾
不是".txt"结尾,那么就抛出异常对象,告知方法的调用者,
文件名后缀不正确
*/
System.out.println("读取文件");
}
}
throwable三种处理异常的方法:
在java.lang.Throwable中有异常处理的方法:
String getMessage()
返回此throwable的简短描述。String toString()
返回此throwable的详细消息字符串。void printStackTrace()
打印的异常信息是最全面的,JVM默认调用此方法打印异常信息
示例:
public class Demo06tryCatch2 {
public static void main(String[] args) {
try {
checkFilePath("c:\\a.java");
System.out.println("读取文件");
} catch (FileNotFoundException e) {//接收抛出的异常对象
//异常处理逻辑,随笔编写
//System.out.println("发生了FileNotFoundException异常");//发生了FileNotFoundException异常
System.out.println(e.getMessage());//文件路径错误
System.out.println(e.toString());//System.out.println(e); //java.io.FileNotFoundException: 文件路径错误
e.printStackTrace();
/*java.io.FileNotFoundException: 文件路径错误
at cn.itcast.demo01.Exception.Demo06tryCatch2.checkFilePath(Demo06tryCatch2.java:36)
at cn.itcast.demo01.Exception.Demo06tryCatch2.main(Demo06tryCatch2.java:15)*/
}
System.out.println("后续代码");
}
/*
* 定义一个方法,对传递的文件路径进行合法性判断
* */
public static void checkFilePath(String fileName) throws FileNotFoundException {
/*
对文件路径进行判断,判断路径是否是d:\\a.txt
如果路径不是d:\\a.txt,那么就抛出异常对象,告知方法的调用者,
传递路径不正确
*/
if(!"d:\\a.txt".equals(fileName)){
throw new FileNotFoundException("文件路径错误");
}
/*
对文件名后缀进行判断,判断是否以".txt"结尾
不是".txt"结尾,那么就抛出异常对象,告知方法的调用者,
文件名后缀不正确
*/
System.out.println("读取文件");
}
}
finally关键字
作用:无论程序是否出现异常,finally中的代码都会执行
格式:
try{
可能会产生异常的代码(没有异常的代码也可以放进来)
}catch(异常类的数据类型 变量名){
异常的处理逻辑(随意编写)
}
...
catch(异常类的数据类型 变量名){
异常的处理逻辑(随意编写)
}finally{
无论是否异常都会执行的代码,一般用于资源释放(IO)
}
注意:可以没有catch,直接使用try…finally
示例:
public class Demo07 {
public static void main(String[] args) {
try {
//可能会产生异常的代码
throw new Exception("编译器异常");
} catch (Exception e) {
//异常的处理逻辑
e.printStackTrace();
}finally {
System.out.println("无论是否异常都会执行的代码");
}
}
}
异常的注意事项
异常注意事项:
多个异常使用捕获的处理方式
- 多个异常分别处理
- 多个异常一次捕获,多次处理(最常用)
- 多个异常一次捕获一次处理
1:多个异常分别处理
public class Demo01 {
public static void main(String[] args) {
//1.多个异常分别处理
try {
int[] arr = {1, 2, 3};
System.out.println(arr[3]);
}catch (ArrayIndexOutOfBoundsException e){
e.printStackTrace();
}
try {
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
System.out.println(list.get(10));
}catch (IndexOutOfBoundsException e){
e.printStackTrace();
}
System.out.println("后续代码");
}
}
2:多个异常一次捕获,多次处理
注意:
若下面的程序中两个catch调换位置,则在编译的时候就会报错!
当一个try有多个catch时,子类异常必须定义在父类异常的上面
public class Demo01 {
public static void main(String[] args) {
//2.多个异常一次捕获,多次处理
try{
int[] arr = {1, 2, 3};
System.out.println(arr[3]);
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
System.out.println(list.get(10));
}catch (ArrayIndexOutOfBoundsException e){//子类异常
e.printStackTrace();
}catch (IndexOutOfBoundsException e){//父类异常
System.out.println(e);
}
System.out.println("后续代码");
}
}
3:多个异常一次捕获一次处理
好处:简单
弊端:多个异常只能有一种处理方式
public class Demo01 {
public static void main(String[] args) {
try{
int[] arr = {1, 2, 3};
System.out.println(arr[3]);
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
System.out.println(list.get(10));
} catch (IndexOutOfBoundsException e){
System.out.println(e);
}
System.out.println("后续代码");
}
}
运行期异常的处理方式
运行期异常被抛出可以不处理,即不捕获也不声明抛出—>交给JVM处理(中断)
运行期异常没有必要处理,一般修改代码,让程序不再抛出运行期异常
public class Demo02 {
public static void main(String[] args) {
try{
System.out.println(0/0);//加trycatch没有意义
}catch (Exception e){
System.out.println(e);
}
}
}
对于0/0这样的代码,加上trycatch没有意义。
子父类异常的处理方式
如果父类方法抛出了多个异常,子类重写父类方法时
只能抛出
- 和父类相同的异常
- 或者是父类异常的子类
- 或者不抛出异常
父类方法没有抛出异常,子类重写父类该方法时也不可抛出异常。此时子类产生该异常,只能捕获处理,不能声明抛出
示例:父类
public class Fu {
public void show01() throws ClassCastException,IndexOutOfBoundsException{}
public void show02() throws IndexOutOfBoundsException{}
public void show03() throws IndexOutOfBoundsException{}
public void show04() {}
}
子类
public class Zi extends Fu {
//抛出和父类相同的异常
@Override
public void show01() throws ClassCastException, IndexOutOfBoundsException {}
//抛出父类异常的子类
@Override
public void show02() throws ArrayIndexOutOfBoundsException {}
//不抛出异常
@Override
public void show03() throws IndexOutOfBoundsException {}
//父类方法没有抛出异常,子类重写父类该方法时也不可抛出异常
@Override
public void show04() {
//此时子类产生该异常,只能捕获处理,不能声明抛出
try {
throw new Exception("编译期异常");
} catch (Exception e) {
e.printStackTrace();
}
}
}
自定义异常
java给我们提供的异常不够时,就需要自己定义一些异常类
格式:
public class 自定义异常类的名称 extends Exception /RuntimeException{
定义一个空参数构造方法
public 自定义异常类的名称(){
super();
}
定义一个带异常信息的构造方法
public 自定义异常类的名称(String message){
super(message);
}
}
注意事项:
- 自定义异常类名称一般都是以Exception结尾。
- 如果继承Exception,那么它就是一个编译异常,方法内部抛出了编译异常,就必须处理这个异常(throws/try…catch)
- 如果继承RuntimeException,那么它就是一个运行期异常,无需处理,交给JVM处理(中断)
示例:自定义一个注册异常(RegisterException)
public class RegisterException extends Exception{
//定义一个空参数构造方法
public RegisterException() {
super();//调用父类的空参构造
}
//定义一个带异常信息的构造方法
public RegisterException(String message) {
super(message);//调用父类的带异常信息的构造方法
}
}
自定义异常的使用:
需求:
模拟登陆操作,如果用户名已存在,则抛出异常并提示:亲,该用户名已经被注册。
分析:
- 定义一个集合,保存用户名已注册的用户名(数据库)
- 使用Scanner获取用户输入的注册用户名
- 定义一个方法,用于判断用户输入的用户名是否已经被占用(使用Collection集合中的方法contains判断集合中是否包含用户输入的用户名)
true:抛出一个异常对象,告知注册的人"亲,该用户名已经被注册"
false:把用户名存储到集合中
示例:使用自定义异常registerException
public class Demo01 {
public static void main(String[] args) {
//1.定义一个集合,保存用户名已注册的用户名(数据库)
ArrayList<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add("ddd");
//2.使用Scanner获取用户输入的注册用户名
System.out.println("请输入要注册的用户名:");
String regName = new Scanner(System.in).next();
//调用checkName方法
checkName(list,regName);
/*
请输入要注册的用户名:
aaa
cn.itcast.demo03.myException.RegisterException: 亲,该用户名已经被注册
at cn.itcast.demo03.myException.Demo01.checkName(Demo01.java:39)
at cn.itcast.demo03.myException.Demo01.main(Demo01.java:30)
[aaa, bbb, ccc, ddd]
*/
}
//3.定义一个方法,用于判断用户输入的用户名是否已经被占用
public static void checkName(ArrayList<String> list, String regName){
//使用Collection集合中的方法contains判断集合中是否包含用户输入的用户名
boolean b = list.contains(regName);
if(b){
//true:抛出一个异常对象,告知注册的人"亲,该用户名已经被注册"
try {
throw new RegisterException("亲,该用户名已经被注册");
} catch (RegisterException e) {
e.printStackTrace();
}
}else {
//false:把用户名存储到集合中
list.add(regName);
System.out.println("注册成功!");
}
System.out.println(list);
}
}