欢迎大家来观看本博课------Java------认识异常。1.异常的概念和体系结构
1.异常的概念和体系结构
1.1 异常的概念
在Java中,在程序执行过程中发生的不正常行为称为异常。如在之前我们经常遇到的算数异常(ArithmeticException)、数组越界异常(ArrayIndexOutOfBoundsException)和空指针异常(NullPointerException )。
1.2 异常的体系结构
在Java中,异常是一种类。由于异常的种类太多了,为了对异常和错误进行很好的分类管理,Java内部维护了一个异常的体系结构。
如下图
从上图可以看出异常的种类众多
1. Throwable位于最高层,是所有异常和错误的父类,派生出了Exception和Error两个子类。
2. Error是指Java虚拟机无法解决的严重问题。
3. Exception是指程序在发生不正常行为后,程序员可以通过调整代码,是程序正常运行。
1.3 异常的分类
异常分为两种,分别为编译时异常和运行时异常。
1.编译时异常
编译时异常也被称为受检查异常。即程序没运行时,编译器就会划红线报异常。
以我们之前讲的克隆为例子
如上图,程序都还没运行,编译器就会划红线报异常了,这就是编译时异常。
2.运行时异常
运行时异常也被称为不受查异常,即在程序运行之后才会出现的异常。
如以上代码,程序运行前没有出现任何异常,但是运行代码后,就会报出下图的异常。这就是所谓的运行时异常。
2. 异常的处理
在Java中,关于异常的处理我们要用带5个关键字,分别为throw,try,catch,finally和throws。
1. 异常的抛出----throw
public class Test {
private static void func(int a){
if(a==0){
throw new ArithmeticException();
}
}
public static void main(String[] args) {
func(0);
}
}
运行以上代码
public class Test {
private static void func(int a){
if(a==0){
throw new ArithmeticException("a==0");
}
}
public static void main(String[] args) {
func(0);
}
}
以上个代码的区别在哪呢?就是一个抛出的异常传了参数,一个没有传参数。
这个传递的字符串通常是产生异常的原因。如果传来参数,运行的时候,字符串也会显示出来。
【注意事项】
1. throw必须写在方法的内部。
2.throw抛出的异常需要是Exception类或者是Exception的子类。
3.如果throw抛出的异常是运行时异常,可以不写,最后交给JVM来处理。
4.如果throw抛出的是编译时异常,则程序员必须处理,否则程序无法运行。
5.throw一但抛出,后续代码将不会运行。
6.throw抛出的一般是自定义异常。(软性要求)
2.异常的捕获
异常的捕获,也就是异常的处理方式。通常是异常声明---throws关键字和异常捕获:try-catch两种。
1.异常声明---throws关键字
异常申明一般处于方法的参数列表的后面,如果程序不想亲自解决该方法声明的异常,我们可以将该异常抛给方法的调用者来处理。即当前的方法不处理异常,让方法的调用来处理异常。
以之前实现对象的克隆为例子:
之前我们实现克隆的时候为什么到这一步还会保错呢?
因为我们在调用clone这个方法的时候,这个clone方法声明了一个异常,需要该方法的调用者来处理这个异常。明显是在main方法中调用了该clone方法,所以main方法要来处理这个异常。处理方式就是也在方法的调用者的方法参数列表后面在声明一次该异常就行了。
如下图
【注意事项】
1.throws的异常声明只能写在函数参数列表的后面。
2.throws声明的异常必须是Exception类或者Exception的子类。
3.如果方法内部抛出了多个异常,throws后面就得写多个异常,中间用逗号隔开。如果异常的类型之间存在父子类的关系,则只声明也可以。
2.异常的捕获并处理----try{...}catch(...){...}
throws声明的异常并没有对异常进行真正的处理,如果想真正得对异常进行处理,需要用到try和catch关键字。
语法格式
try{
...
}catch(){
...
}
代码演示
public static void func(){
int[] arr={1,2,3};
System.out.println(arr[4]);
}
public static void main(String[] args) {
try{
func();
}catch (ArrayIndexOutOfBoundsException e){
System.out.println("捕获数组越界异常");
e.printStackTrace();
}
}
从上面代码,我们知道func()方法里面出现了数组越界的异常,我们将出现异常的代码放在try的代码块里面,接着在catch的括号里面写上对应出现的异常。
【注意事项】
1.try...catch中可以根多种异常,知道与catch后的异常类型匹配为止。
public static void func(){
int[] arr={1,2,3};
System.out.println(arr[4]);
}
public static void main(String[] args) {
try{
func();
}catch (NullPointerException e){
System.out.println("捕获了空指针异常");
e.printStackTrace();
}catch (ArithmeticException e){
System.out.println("捕获了算术异常");
e.printStackTrace();
}catch (ArrayIndexOutOfBoundsException e){
System.out.println("捕获数组越界异常");
e.printStackTrace();
}
}
如上面的代码,虽然也写了catch很多异常,但是抛出的异常是数组越界异常,所以只会执行与数组越界异常匹配的catch语句。
2.如果try里面的代码块里的代码出现了异常,那么try代码块就不会执行出现异常代码之后的代码。但如果try代码块里面不出现异常,则try代码块里面的所有代码都会执行。
public static void func(){
int[] arr={1,2,3};
System.out.println(arr[4]);
}
public static void main(String[] args) {
try{
func();
System.out.println("func()后面的代码");
}catch (ArrayIndexOutOfBoundsException e){
System.out.println("捕获数组越界异常");
e.printStackTrace();
}
}
由于func()抛出了异常,所以就不会执行try代码块里面func()后面的代码。
3. 只有捕捉到了异常,catch代码块里面的的代码才会运行。
public static void main(String[] args) {
try{
int n=10;
}catch (ArrayIndexOutOfBoundsException e){
System.out.println("捕获数组越界异常");
e.printStackTrace();
}
}
由于try代码快里面的代码没有出现异常,则catch就没有捕获异常,则catch代码快里面的代码就不会运行。
4.如果try代码块里面抛出了多个异常,只会捕捉一个异常,因为当捕捉了一个代码之后,代码就会终止了。
3.finally关键字
在程序运行的时候,会有一些特定的代码,不管程序是否抛出异常,都需要执行。比如程序资源的打开:网络连接、数据库连接和IO流。在程序正常运行或者出现异常时,必须实现对资源的关闭,否则会造成内存泄漏。另外,可能因为程序抛出异常,会导致一些代码执行不了。这时候就要用到finally关键字。因为finally代码块里面不管前面的代码是否抛出异常,finally代码块里面的代码都会执行。
1. 程序抛出异常
public static void main(String[] args) {
int[] arr={1,2,3};
try{
arr[4]=0;
}catch (ArrayIndexOutOfBoundsException e){
System.out.println("捕获到数组越界异常");
}finally {
System.out.println("finally代码块运行");
}
}
运行代码
2. 程序不抛出异常
public static void main(String[] args) {
int[] arr={1,2,3};
try{
arr[2]=0;
}catch (ArrayIndexOutOfBoundsException e){
System.out.println("捕获到数组越界异常");
}finally {
System.out.println("finally代码块运行");
}
}
3. 易错点
public static int func(){
try{
return 20;
}catch (ArrayIndexOutOfBoundsException e){
System.out.println("捕获到数组越界异常");
}finally {
return 100;
}
}
public static void main(String[] args) {
int data= func();
System.out.println(data);//打印100
}
之所以最后的结果是100,我们可以理解为因为finally代码块终究会执行,所以程序就会只执行finally代码块里面的return语句。
4. 异常处理总结
首先会执行try代码块里面的代码,看其是否抛出异常。如果抛出异常,就会看catch代码块有没有匹配的异常,如果catch边没有找到对应的异常,就会交给上层调用者去处理。如果上层调用者也没有处理异常,则会交给main方法处理,如果main方法也没有处理,则最后就会交给JVM去处理。
3.自定义异常类
虽然在Java中,异常的种类也很丰富了,但终会有一些异常在Java中是没有定义的,所以我们要学会自定义异常类。
1. 自定义异常一般都会继承Exception类或者RunTimeException类。
2. 继承Exception类是受检查类。
3. 继承RunTimeException类是不受检查类。
实现登录异常
自定义NameExcetion类继承Exception
public class NameException extends Exception{
public NameException() {
super();
}
public NameException(String message) {
super(message);
}
}
自定义Password类继承RuntimeException
public class PasswordException extends RuntimeException{
public PasswordException() {
super();
}
public PasswordException(String message) {
super(message);
}
}
主函数
class Login {
public String password;
public String name;
public Login(String password, String name) {
this.password = password;
this.name = name;
}
public void login(String password,String name)throws PasswordException,NameException{
if(!this.password.equals(password)){
throw new PasswordException("密码错误");
}if(!this.name.equals(name)){
throw new NameException("用户名错误");
}
System.out.println("登录成功");
}
}
public class Test {
public static void main(String[] args) {
Login login=new Login("123","man");
try{
login.login("123","man");
}catch (PasswordException e){
e.printStackTrace();
}catch (NameException e){
e.printStackTrace();
}
}
}
我们也可以在login函数处理继承Exception类抛出的异常。
class Login {
public String password;
public String name;
public Login(String password, String name) {
this.password = password;
this.name = name;
}
public void login(String password,String name){
if(!this.password.equals(password)){
throw new PasswordException("密码错误");
}try {
if(!this.name.equals(name)){
throw new NameException("用户名错误");
}
}catch (NameException e){
e.printStackTrace();
}
System.out.println("登录成功");
}
}
public class Test {
public static void main(String[] args) {
Login login=new Login("123","man");
try{
login.login("123","man");
}catch (PasswordException e){
e.printStackTrace();
}
}
}