文章目录
第一章 异常
1.1 异常的概念
**异常:**就是非正常的意思,在java
等面向对象的编程语言中,异常本身就是一个类,产生异常就是创建一个异常对象,并把这个异常对象抛出来的过程,java
的异常处理机制是中断处理。
1.2 异常体系
异常机制就是帮助我们找到程序中的问题,异常的根类是java.lang.throwable
,其下有两个子类,分别是java.lang.Error
和java.lang.Exception
。平常所说的异常就是java.lang.Exception
。
**异常和错误:**异常是小感冒,在运行过程中出现了异常也能用方法去自动处理,例如try......catch...
改不改源码无所谓。但是错误是致命的,像申请一个非常大的数组,导致内存溢出,那就必须改动源码才能跑,所以这个只能在敲代码过程中避免。
1.3 异常分类
-
java
中的异常可以分为编译期异常(Exception)
和运行期异常(RuntimeException)
- 通常编译期异常,我们在编辑器中刚敲完代码它就会在对应的代码处出现红色的波浪线,提示你这里的代码有异常,我们就可以进行适当修改。
- 运行期异常,比如数组越界,在敲完代码时,编译器不会用红色波浪线说这里代码有问题,而是在运行代码时,才打印出红色字体说
IndexOutofBoundException
。
1.4 异常的产生过程解析
- 下面这个数组越界的例子属于运行期异常
第二章 Java的异常处理
java
中异常处理的关键字有5个:try、 catch、 finally、 throw、 throws
2.1 抛出异常throw
throw 关键字
作用:可以使用throw关键字在指定的方法中抛出指定的异常
使用格式:
throw new xxxException
(“产生异常的原因”);注意:
1. throw关键字必须写在方法的内部
2. throw关键字后边new的对象必须是Exception或Exception的子类对象
throw关键字后边创建的是
RuntimeException
或者是RuntimeException
的子类对象,我们 可以不处理,默认交给JVM
处理(打印异常对象,终止程序) throw关键字后边创建的是编译异常(写代码的时候报错),我们就必须处理这个异常,要么
throws
,要么try.....catch
以后,我们首先必须对方法传递过来的参数进行合法性校验,如果参数不合法,那么我们就必须使用抛出异常的方式,告知方法的调用者,传递的参数有问题。
-
下面演示当出现
IndexOutOfBoundsException
和NullPoiterException
的时候,如何处理public class Demo03Throw { public static int getElement(int[] arr, int index) { /* * 对传进来的数组进行非空校验,如果是null,就抛出异常,并显示:输入的数组为空 * 对传进来的index进行越界校验,如果越界了,就抛出异常,并显示:数组越界 * 这两个都是运行期异常即RuntimeException,调用者main收到异常后可以throw给JVM处理 */ if(arr == null) { throw new NullPointerException("输入的数组为空"); } if(index < 0 || index > arr.length) { throw new IndexOutOfBoundsException("数组越界"); } int ele = arr[index]; return ele; } public static void main(String[] args) { // TODO Auto-generated method stub int arr[] = null; int e = getElement(arr, -1); System.out.println(e); } }
2.2 Objects非空判断
类Objects是由一些静态使用方法组成,这些方法是null-save (空指针安全)或null-tolerant (容忍空指针的),那么在它的源码中,对对象的null值肯定进行了抛出异常的操作
-
public static <T> T requireNonNull(T obj)
: 查看指定的应用对象是不是null源码中的确对为
null
的进行了抛出异常的操作:public static <T> T requireNonNull(T obj){ if(obj == null){ throw new NullPointerException(); } return obj; }
-
用代码实验一下
public static <T> T requireNonNull(T obj)
方法import java.util.Objects; public class Demo04Objects { /* * 实验public static <T> T requireNonNull(T obj)方法 * 其实这个方法的效果和自己写的:if(obj == null) throw new NullPointerException("对象为空");差不多 * 直接用Objects.requireNonNull(obj);与上面的效果一样 * 还有一个重载的方法,Objects.requireNonNull(obj, messageSupplier)可以打印信息 */ public static void method(Object obj) { /* if(obj == null) { throw new NullPointerException("对象为空"); } */ // Objects.requireNonNull(obj); Objects.requireNonNull(obj, "对象为空"); } public static void main(String[] args) { method(null); } }
2.3 声明异常throws
throws关键字:异常处理的第一种方式,交给别人处理
作用:
可以使用throws关键字处理异常对象,会把异常对象抛出给方法的调用者处理(自己不处理,让别人去处理),最终交给
JVM
处理–>中断处理使用格式:在方法声明时使用
1. 修饰符 返回值类型 方法名 (参数列表) throws AAAException, BBBException....{ throw new AAAException(“产生原因”); throw new BBBException(“产生原因”); ........ }
注意:
- throws关键字必须写在方法声明处
- throws关键字后边说明的异常必须是Exception或者是Exception的子类
- 方法内部如果抛出了多个异常对象有子父类关系,那么直接声明父类异常即可
- 调用了一个声明抛出异常的方法,我们就必须处理声明的异常,要么继续使用throws声明抛出,交给方法的调用者处理,最终交给
JVM
,要么try…catch自己处理
-
- 下面的代码演示编译期异常
- 下面的代码演示编译期异常
-
- 对于编译期异常,补救措施: ① throws声明 ② try…catch自己处理,但是下面还是出错了
- 对于编译期异常,补救措施: ① throws声明 ② try…catch自己处理,但是下面还是出错了
-
- 给main也加了一个throws声明,成功了
- 给main也加了一个throws声明,成功了
-
- 增添一个后缀校验,throws声明直接填父类Exception就可以
- 增添一个后缀校验,throws声明直接填父类Exception就可以
2.4 捕获异常try…catch
try…catch: 异常处理的第二种方式,自己处理异常
格式:
try{ 可能产生异常的代码 }catch(定义一个异常变量,用来接收try中抛出的异常对象){ 异常的处理逻辑,捕获异常对象字后,怎么处理异常对象 一般在工作中,会把异常信息记录到一个日志中 } 可以接着更多的catch或其它代码 catch(异常类型名 变量名){ } ......
注意:
- try中可能会抛出多个异常对象,那么就可以使用对个catch来处理这些异常对象。
- 如果try中产生了异常,那么就会执行catch中的异常处理逻辑,执行完毕catch中的异常处理逻辑之后,继续执行try…catch之后的代码
- 如果try中没有产生异常,那么执行完try里面的代码之后,继续执行try…catch之后的代码
-
1. try…catch…的代码演示
package exception; import java.io.IOException; public class Demo06tryCatch { public static void readFile(String fileName) throws IOException{ if(!fileName.endsWith(".txt")) { //如果后缀名不是 .txt 就向调用该方法的main抛出一个异常对象 throw new IOException("传递的文件名后缀不对"); } } public static void main(String[] args){ try { //可能出现异常的代码 readFile("c:\\a.tx"); }catch(IOException e) { //try中抛出什么异常对象,catch就定义什么异常变量,用来接收这个异常对象 //异常的处理逻辑,捕获异常对象之后,怎么处理异常对象 System.out.println("catch: 传递的文件名后缀不对"); } System.out.println("后续代码"); } }
-
try…catch和throws的一些不同
- try…catch是自己处理,比如上面代码中当main收到来自
readFile
的异常之后,它自己用catch里面的内容去解决了。而相比之下,throws声明只是让异常一级一级的往上抛,自己从不处理。 - try…catch代码执行完之后,还会执行余下的代码;throws中由于最终交给了
JVM
处理,JVM
采用的是中断处理,所以会让整个程序到此结束,余下的代码不会再执行下去。
- try…catch是自己处理,比如上面代码中当main收到来自
2.5 throwable
类中定义了3个打印异常的方法
-
同样是用2.4中的代码作为示例:
import java.io.IOException; public class Demo06tryCatch { public static void readFile(String fileName) throws IOException{ if(!fileName.endsWith(".txt")) { //如果后缀名不是 .txt 就向调用该方法的main抛出一个异常对象 throw new IOException("传递的文件路径不是.txt结尾"); } } public static void main(String[] args) { try { readFile("c:\\a.tx"); }catch(IOException e) { //System.out.println("catch: 传递的文件名后缀不对"); System.out.println(".................(1) getMessage方法......................"); //返回throwable的简短描述 System.out.println(e.getMessage()); System.out.println(".................(2) toString方法........................"); //返回throwable的详细信息字符串 System.out.println(e.toString()); System.out.println(".................(3) 直接打印e和toString方法一样..........."); //和(2)一样 System.out.println(e); System.out.println(".................(4)printStackTrace方法.................."); //返回最详细的信息,平常默认JVM是打印的这个,包括异常的类型、原因、位置 e.printStackTrace(); } System.out.println("后续代码"); } }
-
代码结果
........................(1) getMessage方法................................ catch: 传递的文件名后缀不对 ........................(2) toString方法.................................. java.io.IOException: catch: 传递的文件名后缀不对 ........................(3) 直接打印e和toString方法一样...................... java.io.IOException: catch: 传递的文件名后缀不对 ........................(4)printStackTrace方法............................ java.io.IOException: catch: 传递的文件名后缀不对 后续代码 at exception.Demo06tryCatch.readFile(Demo06tryCatch.java:7) at exception.Demo06tryCatch.main(Demo06tryCatch.java:12)
2.6 finally 代码块
下面的这种情况中,当readFile
发生异常之后,就会直接转到catch那边的代码,try里面的System.oyt.println(“try的后续代码”)
将不会被执行
无论发生异常与否,写在finally里面的代码都会被执行
import java.io.IOException;
public class Demo07tryCatchFinally {
public static void readFile(String fileName) throws IOException{
if(!fileName.endsWith(".txt")) {
//如果后缀名不是 .txt 就向调用该方法的main抛出一个异常对象
throw new IOException("传递的文件路径不是c:\\a.txt");
}
}
public static void main(String[] args) {
try {
//可能会产生异常的代码
readFile("c:\\a.tx");
System.out.println("try的后续代码");
}catch(IOException e) {
//异常的处理逻辑
e.printStackTrace();
}finally {
//无论发生异常与否,这里的代码都将会被执行
System.out.println("资源释放");
}
System.out.println("后续代码");
}
}
2.7 异常注意事项多次
多次异常情况下的处理方式:
-
1. 多个异常,分别处理
public static void main(String[] args) { //一个异常一个try...catch try { int[] arr = {1, 2, 3}; System.out.println(arr[3]); }catch(ArrayIndexOutOfBoundsException e) { e.printStackTrace(); } try { ArrayList<Integer> list = new ArrayList<Integer>(); list.add(1); list.add(2); list.add(3); System.out.println(list.get(3)); }catch(IndexOutOfBoundsException e) { e.printStackTrace(); } System.out.println("后续代码"); } /*结果: java.lang.ArrayIndexOutOfBoundsException: 3 at exception.Demo08Attention.main(Demo08Attention.java:10) java.lang.IndexOutOfBoundsException: Index: 3, Size: 3 at java.util.ArrayList.rangeCheck(Unknown Source) at java.util.ArrayList.get(Unknown Source) at exception.Demo08Attention.main(Demo08Attention.java:20) 后续代码 */
-
2. 多个异常,一次捕获,多次处理
public static void main(String[] args) {
/*一个try多个catch的注意事项:
* catch里边定义的异常变量,如果有子父类关系,那么子类的异常变量必须写在上边,否则就会
* 报错,因为ArrayIndexOutOfBoundsException extends IndexOutOfBoundsException
* 所以一旦IndexOutOfBoundsException写在第一个catch中,那么无论两种异常谁发生,都会
* 在第一个catch起作用,那ArrayIn...这个就失去了作用,它就是个多余的,所以就报错。
*/
try {
int[] arr = {1, 2, 3};
System.out.println(arr[3]);
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(3);
System.out.println(list.get(3));
}catch(ArrayIndexOutOfBoundsException e) { //子类写在前
e.printStackTrace();
}
catch(IndexOutOfBoundsException e) { //父类写在后
e.printStackTrace();
}
System.out.println("后续代码");
}
- 3. 多个异常,一次捕获,一次处理
public static void main(String[] args) {
/*其实就是2中,把子类异常去掉,直接写一个父类,因为父类也能处理子类的异常*/
try {
int[] arr = {1, 2, 3};
System.out.println(arr[3]);
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(3);
System.out.println(list.get(3));
}catch(IndexOutOfBoundsException e) {
e.printStackTrace();
System.out.println("后续代码");
}
}
-
4. 其实上面的都是运行时异常,可以不处理,直接交给
JVM
处理,JVM
遇到时会打印异常并终止程序 -
5. 如果finally中有return语句,那么返回的一定是finally中的return语句,应该避免
public static int getA() { int a = 10; try { return a; }catch(Exception e) { e.printStackTrace(); }finally { //一定会执行的代码 return a = 100; } } public static void main(String[] args) { System.out.println(getA()); //结果输出是100 }
-
6. 子类重写父类方法时,父类方法的throws声明长什么样,子类的throws声明就应该长什么样
public class Fu { public void show01() throws NullPointerException, ClassCastException{} public void show02() throws IndexOutOfBoundsException{} public void show03() {} } class Zi extends Fu{ public void show01() throws NullPointerException, ClassCastException{} //子类重写父类的方法时,throws的异常声明可以和父类的一样 public void show02() throws ArrayIndexOutOfBoundsException{} //子类重写父类的方法时,throws的异常声明可以是父类声明的异常的子类 public void show03() {} //子类重写父类方法时,父类没有throws声明,子类也不应该throws声明,但是可以用try..catch }
第三章:自定义异常
3.1 概述
为什么需要自定义异常类?
因为有些异常java
中并没有定义,比如年龄负数问题,注册名重复问题等等。
什么是自定义异常类
在开发中根据自己的业务的异常情况来定义异常类
自定义一个业务逻辑异常:RegisterException
异常类如何定义
- 自定义一个编译期异常:自定义类 并继承于
java.lang.Exception
- 自定义一个运行期异常类:自定义类 并继承于
java.lang.RuntimeException
自定义异常类格式:
public class XXXException extends Exception | RuntimeException{
添加一个空参数的构造方法
添加一个带异常信息的构造方法
}
注意:
-
自定义异常类一般都是以Exception结尾,说明该类是一个异常类,比如我的
RegisterException
-
自定义异常类,必须继承
Exception
或者RuntimeException
- 继承
Exception
:那么定义的异常类就是一个编译期异常,如果方法内部抛出了编译期异常,就必须处理这个异常,要么throws,要么try…catch - 继承
RuntimeException
:那么自定义的异常类就是一个运行期异常,无需处理,交给虚拟机处理(中断处理)
- 继承
3.2 自定义异常练习
-
自定义一个注册异常类,然后用它模拟注册操作
public class RegisterException extends RuntimeException { private static final long serialVersionUID = 7115469508465834552L; //构造一个空参数的构造方法 public RegisterException() { //让父类来处理 super(); } //添加一个带异常信息的构造方法 public RegisterException(String Message) { //还是让父类来处理,其实是让它帮忙打印Message super(Message); } }
-
1. try…catch,CheckName方法抛异常给main,main自己处理
import java.util.Scanner;
/*
* 要求:我们模拟注册操作,如果用户名已存在,则抛出异常并提示:亲,该用户名已经被注册。
* 分析:
* 1.使用数组保存已经注册过的用户名(数据库)
* 2.使用scanner获取用户输入的注册的用户名(前端,页面)
* 3.定义一个方法,对用户输入的注册的用户名进行判断
* 遍历存储已经注册过用户名的额数组,获取每一个用户名
* 使用获取到的用户和用户输入的用户名比较
* true:
* 用户名已经存在,抛出RegisterException异常,并告知用户“亲,该用户名已经被注册”
* false:
* 继续遍历比较
* 如果循环结束了,还没有找到重复的用户名,提示用户“恭喜您,注册成功了!”;
*/
public class PracticeRegisterException {
static String[] UserName = {"张三", "李四", "王五"};
public static void main(String[] args) {
System.out.println("请输入您想要注册的用户名:");
Scanner in = new Scanner(System.in);
String name = in.next();
try {
CheckName(name);
} catch (RegisterException e) {
System.out.println("亲,该用户名已经被注册");
return;
//try...catch方法里要有个return,不然catch执行完之后,下面的恭喜您也会被打印出来
}
System.out.println("恭喜您, 注册成功了!");
}
public static void CheckName(String name) throws RegisterException {
for(String usr : UserName) {
if(name.equals(usr)) {
throw new RegisterException();
}
}
}
}
- 2. throws,CheckName方法抛异常给main,main把异常抛给JVM进行中断处理。
import java.util.Scanner;
/*
* 要求:我们模拟注册操作,如果用户名已存在,则抛出异常并提示:亲,该用户名已经被注册。
* 分析:
* 1.使用数组保存已经注册过的用户名(数据库)
* 2.使用scanner获取用户输入的注册的用户名(前端,页面)
* 3.定义一个方法,对用户输入的注册的用户名进行判断
* 遍历存储已经注册过用户名的额数组,获取每一个用户名
* 使用获取到的用户和用户输入的用户名比较
* true:
* 用户名已经存在,抛出RegisterException异常,并告知用户“亲,该用户名已经被注册”
* false:
* 继续遍历比较
* 如果循环结束了,还没有找到重复的用户名,提示用户“恭喜您,注册成功了!”;
*/
public class PracticeRegisterException {
static String[] UserName = {"张三", "李四", "王五"};
public static void main(String[] args) throws RegisterException {
System.out.println("请输入您想要注册的用户名:");
Scanner in = new Scanner(System.in);
String name = in.next();
CheckName(name);
System.out.println("恭喜您, 注册成功了!");
}
public static void CheckName(String name) throws RegisterException{
for(String usr : UserName) {
if(name.equals(usr)) {
throw new RegisterException("亲,该用户已被注册");
}
}
}
}
- 3. 什么都不用做,CheckName方法直接抛出异常就行,默认交给JVM进行中断处理,因为我们的注册异常继承于RuntimeException
import java.util.Scanner;
/*
* 要求:我们模拟注册操作,如果用户名已存在,则抛出异常并提示:亲,该用户名已经被注册。
* 分析:
* 1.使用数组保存已经注册过的用户名(数据库)
* 2.使用scanner获取用户输入的注册的用户名(前端,页面)
* 3.定义一个方法,对用户输入的注册的用户名进行判断
* 遍历存储已经注册过用户名的额数组,获取每一个用户名
* 使用获取到的用户和用户输入的用户名比较
* true:
* 用户名已经存在,抛出RegisterException异常,并告知用户“亲,该用户名已经被注册”
* false:
* 继续遍历比较
* 如果循环结束了,还没有找到重复的用户名,提示用户“恭喜您,注册成功了!”;
*/
public class PracticeRegisterException {
static String[] UserName = {"张三", "李四", "王五"};
public static void main(String[] args) {
System.out.println("请输入您想要注册的用户名:");
Scanner in = new Scanner(System.in);
String name = in.next();
CheckName(name);
System.out.println("恭喜您, 注册成功了!");
}
public static void CheckName(String name){
for(String usr : UserName) {
if(name.equals(usr)) {
throw new RegisterException("亲,该用户已被注册");
}
}
}
}