java异常机制全介绍

java异常机制

异常的基本概念

什么是异常

  • 在程序运行中出现的错误,称为异常

  • 异常模拟的是现实世界中不正常的事件

  • java中采用的方式实现异常(和其他的类是一样的,按照类和对象理解就可以啦)

    • 类是可以创建对象的

      NullPointerException e = 0x1234;
      
    • e 是引用类型,e 中保存的内存地址指向堆中的异常对象

    • 这个对象一定是NullPointerException类型的异常

    • e 表示真实存在的NullPointerException类型的异常“对象”

      异常都是一类一类的

  • 异常举例

  ArithmeticException 算术异常(divide by zero)
  ArrayIndexOutOfBoundsException 数组越界;
  ArrayStoreException 向数组对象中存储错误类型的数据
  ClassCastException 类型转换异常 是一类异常
  ClassNotFoundException 找不到类异常
  CloneNotSupportedException 克隆不支持异常
      表示调用了Object类中的clone方法来创建一个对象,但是对象的类中没有实现Cloneable接口
  IndexOutOfBoundsException 越界异常
  InstantiationException 实例化异常
      尝试创建一个不能实例化的类的异常,例如实例化了一个接口,抽象方法,数组类,原始数据类型;
  NegativeArraySizeException 负的数组大小异常
      尝试创建负数长度的数组的异常;
  NoSuchFieldException 访问类中并不存在的成员抛出的异常;
  NoSuchMethodException 访问类中并不存在的方法抛出的异常;
  NullPointerException 空指针异常;
  NumberFormatException 在将字符串类型转为数字类型时,字符串格式不正确的异常;
  SecurityException 操作不安全的异常;
  StringIndexOutOfBoundsException 由String类中的方法抛出的“字符串越界异常”,一般是`charAt`方法引起的;
  UnsupportedOperationException 请求的操作不支持的异常
  RuntimeException 继承自 Exception 类,它 和 它的子类都是 unchecked 异常;
  Exception 的直接子类都是 checked 异常, 即编译时异常,需要提前处理(throws 或者 try...catch...
  • 异常举例
  public class Test01 {
  	public static void main(String[] args) {
  		int a = 10;
  		int b = 0;
  		int c = a/b;// ArithmeticException
  		
         	// 上面的代码出现了异常,“没有处理”,下面的代码不会执行,直接退出了 JVM
  		System.out.println(c);
  	}
  }
  /*控制台输出
  Exception in thread "main" java.lang.ArithmeticException: / by zero
  	at com.test.exception.Test01.main(Test01.java:7)
  */
  • 以上程序编译通过了,但是运行时出现了异常,表示发生了某一个异常事件。
  • 控制台输出信息的本质:
    • 程序在执行过程中发生了ArithmeticException(算术异常)这个事件,JVM为我们创建了一个ArithmeticException 类型的对象,并且这个对象中包含了详细的异常信息,并且JVM将这个对象中的信息输出到控制台。

异常机制的作用

  • java 语言为我们提供了一种完善的异常处理机制,作用是:

    程序发生异常事件之后,为我们输出详细的信息,通过这个信息,可以对程序进行一些处理,使得程序更加健壮。

异常的分类&异常继承结构

异常继承结构

异常的继承结构

异常的捕获和处理

使用throws关键字声明抛出异常

  • 在方法声明的位置上使用 throws 关键字向上抛出异常
    抛出异常的方法

  • 例如

    import java.io.*;
    
    public class Test02 {
    	public static void main(String[] args)  {
    		FileInputStream fs = new FileInputStream("c:\\ab.txt");
    	}
    }
    

在这里插入图片描述

  • 以上程序编译不通过
.\Test02.java:7: 错误: 未报告的异常错误FileNotFoundException; 必须对其进行捕获或声明以便抛出
                FileInputStream fs = new FileInputStream("c:\\ab.txt");
// 为啥编译器会检测出来这个个编译时异常咧?
因为FileInputStream 这个构造方法在声明的位置上使用了 throws FileNotFoundException;
  • 使用 throws 处理异常不是真正的处理异常,而是推卸责任 ;只是一直往上抛并没有处理

    import java.io.*;
    
    public class Test03 {
    	public static void main(String[] args) throws FileNotFoundException {
    		m1();// m1()方法的声明位置上使用throws(向上抛)
    		// 上面的m1方法如果出现了异常,因为采用的是throws上抛给了JVM,JVM遇到这个异常就会退出JVM
    		// 下面这行代码就不会执行
    		System.out.println("hello world");
    	}
    	static void m1() throws FileNotFoundException{
    		m2();// m2 方法声明的位置上使用throws(向上抛)
    	}
    	static void m2() throws FileNotFoundException {
    		m3();// m3() 方法的声明位置上使用 throws(向上抛)
    	}
    	static void m3() throws FileNotFoundException {
    		m4(); // m4方法的声明位置上使用 throws(向上抛)
    	}
    	static void m4() throws FileNotFoundException{
    		new FileInputStream("c://daf"); // FileInputStream 构造方法声明位置上使用throws(向上抛)
    	}
    }
    /*
     * 在程序执行过程中出现了 FileNotFoundException类型的异常
     * JVM为我们创建了一个FileNotFoundException类型的对象
     * 该对象中携带了以下信息,JVM负责将这些信息打印到控制台。
     * 并且JVM停掉了程序的运行。
     
    Exception in thread "main" java.io.FileNotFoundException: c:\daf (系统找不到指定的文件。)
    at java.io.FileInputStream.open0(Native Method)
    at java.io.FileInputStream.open(Unknown Source)
    at java.io.FileInputStream.<init>(Unknown Source)
    at java.io.FileInputStream.<init>(Unknown Source)
    at com.test.exception.Test03.m4(Test03.java:20)
    at com.test.exception.Test03.m3(Test03.java:17)
    at com.test.exception.Test03.m2(Test03.java:14)
    at com.test.exception.Test03.m1(Test03.java:11)
    at com.test.exception.Test03.main(Test03.java:8)
    */
    

使用try-catch捕获处理异常

  • 语法结构

    try{
        // 可能出现异常的代码
    }catch(异常类型1 变量){
        // 处理异常的代码
    }catch(异常类型2 变量){
        // 处理异常的代码
    }...
    
    • catch 语句块可以写多个

    • 注意:catch语句块中从上到下的异常类型必须是从小到大的

      • 例如:

      在这里插入图片描述

      try {//可以编译通过
          FileInputStream fis = new FileInputStream("adnx.txt");//捕获对应的FileNotFoundException异常
      			fis.read();//捕获对应的IOException类型的异常
      }catch(FileNotFoundException e) {  //子异常
      
      }catch(IOException e) { // 父异常
      
      }
      try {//编译报错
          FileInputStream fis = new FileInputStream("adnx.txt");//捕获对应的FileNotFoundException异常
      			fis.read();//捕获对应的IOException类型的异常
      }catch(IOException e) {//父异常
      
      }catch(FileNotFoundException e) {//子异常
      
      }
      
    • try … catch …中最多执行一个 catch 语句块。执行结束之后 try…catch… 就结束了

  • 例子:

    import java.io.*;
    
    public class Test05 {
    	public static void main(String[] args) {
    		try {
    			// 程序执行到此处发生了FileNotFoundException类型的异常
    			// JVM 会自动创建一个FileNotFoundException 类型的对象,将该对象的内存地址赋值给catch出语句块中的e变量
    			FileInputStream fis = new FileInputStream("abc");
    			// 上面代码出现了异常,try语句块的代码不再继续执行,直接进入catch语句块中执行
    			fis.read();
    		}catch(FileNotFoundException e) {
    			System.out.println("文件不存在异常");
    			// FileNotFoundException 类将Object类中的toString()方法重写了。
    			System.out.println(e);//java.io.FileNotFoundException: abc (系统找不到指定的文件。)
    		}catch(IOException e){
    			System.out.println("其它IO异常");
    		}
    	}
    }
    
    

getMessage() && printStackTrace()

如何取得异常对象的具体信息,常用的方法有两种:

  • 取得异常描述信息

    getMessage()

  • 取得异常的堆栈信息(适用于程序调式阶段)

    printStackTrace()

举例:

import java.io.*;

public class Test06 {
	public static void main(String[] args) {
		
		try {
			FileInputStream fis = new FileInputStream("c:/adf.txt");
		} catch (FileNotFoundException e) {
			// 打印堆栈异常信息
			//一般情况下都会使用该方法去调试程序
//			e.printStackTrace();
//			/*输出如下:
//			 * java.io.FileNotFoundException: c:\adf.txt (系统找不到指定的文件。)
//				at java.io.FileInputStream.open0(Native Method)
//				at java.io.FileInputStream.open(Unknown Source)
//				at java.io.FileInputStream.<init>(Unknown Source)
//				at java.io.FileInputStream.<init>(Unknown Source)
//				at com.test.exception.Test06.main(Test06.java:9)
//			 */
//			System.out.println("abc");
			
			
			// 在发生FileNotFoundException异常的时候,JVM为我们执行了下面这行代码,
			//FileNotFoundException e = new FileNotFoundException("c:\\adf.txt (系统找不到指定的文件。)");
			// Throwable 构造方法:
//			public Throwable(String message) {
//		        fillInStackTrace();
//		        detailMessage = message;
//		    }
			// 然后我们在调用e.getMessage()的时候会得到下面的输出信息
			String msg = e.getMessage();
			System.out.println(msg);
			//输出 c:\adf.txt (系统找不到指定的文件。)
		}
		
	}
}

finally语句块详解

  • finally语句块可以直接和 try 语句块连用

    try ... finally...

  • try...catch...finally也可以

  • finally语句块中的代码一定会执行的。

    • 测试:

      public class Test07 {
      	public static void main(String[] args) {
      		try {
      			System.out.println("Abc");
      			return;
      		} finally {
      			System.out.println("hhhh");
      		}
      	}
      }
      // 最终输出
      Abc
      hhhh
      

      可以这么理解程序的执行顺序:

      ​ 先执行第 4 行, 再执行第 7 行 最后再执行第 5 行

    • 再测试

      import java.io.*;
      
      public class Test08 {
      	public static void main(String[] args) throws FileNotFoundException{
      		try {
      			FileInputStream fis = new FileInputStream("c:/ab.txt");
      			// 下面这句不会执行
      			System.out.println("aaaaa");
      		} finally {
      			// 下面这句会执行
      			System.out.println("hhhhh");
      		}
      	}
      }
      /* 输出结构
      hhhhh
      Exception in thread "main" java.io.FileNotFoundException: c:\ab.txt (系统找不到指定的文件。)
      	at java.io.FileInputStream.open0(Native Method)
      	at java.io.FileInputStream.open(Unknown Source)
      	at java.io.FileInputStream.<init>(Unknown Source)
      	at java.io.FileInputStream.<init>(Unknown Source)
      	at com.test.exception.Test08.main(Test08.java:8)
      */
      

      可以这么理解程序的执行顺序:

      ​ 执行到第 6 行出现异常,先执行第 11 行, 再向上抛出异常,JVM打印出异常信息

    • 再测试(注意

      • 只有这一种情况下,finally 语句块中的代码不会被执行
        • 在执行 finally 语句块之前退出 JVM,则 finally语句块不会被执行
      public class Test09 {
      	public static void main(String[] args) {
      		try {
      			// 退出jvm
      			System.exit(0);
      		} finally {
      			// 什么都不会输出
      			System.out.println("hhhh");
      		}
      	}
      }
      
      

深入 finally 语句块(面试常考)

  • 举例01
public class Test10 {
   public static void main(String[] args) {
   	int i = m1();
   	System.out.println(i); //10
   }

   private static int m1() {
   	int i = 10;
   	try {
   		return i;
   	} finally {
   		i++;
   		System.out.println("m()->i: " + i);//11
   	}
   }
}
  • 以上代码的输出结果为:

    m()->i: 11
    10
    

    以上代码的执行原理:

    public class Test10 {
    	public static void main(String[] args) {
    		int i = m1();
    		System.out.println(i);//10
    	}
    
    	private static int m1() {
    		int i = 10;
    		try {
    			int temp = i; //jvm定义一个临时变量temp 存储 i, 然后return的是临时变量的值
    			return temp;// 所以返回的10
    		} finally {
    			i++;// 针对i操作
    			System.out.println("m()->i: " + i);//11
    		}
    	}
    }
    
  • 02

    finally语句块是一般来说是一定会执行的,所以通常在程序中为了保证某资源一定会被释放,一般写在finally语句块中释放资源

    import java.io.*;
    
    public class Test11 {
    	public static void main(String[] args) {
    		// 必须在外边声明,否则finally语句块中找不到fis
    		FileInputStream fis = null;
    		try {
    			fis = new FileInputStream("Test11.java");
    		} catch (FileNotFoundException e) {
    			e.printStackTrace();
    		}finally {
    			// 为了保证资源一定会释放
    			if(fis != null) {
    				try {
    					fis.close();// 释放资源
    				}catch(IOException e) {// 捕获异常
    					e.printStackTrace();
    				}
    			}
    		}
    	}
    }
    
    

final && finalize && finally区别

三者没有任何联系

  • final
    • 修饰的类不能被继承
    • 修饰的方法不能被重写Override
    • 修饰的实例变量要手动赋值-且不能再修改
    • 修饰的静态变量是最终的
  • finalize
    • Object类中的方法名
      • 垃圾回收器在回收垃圾之前会调用该方法
        • 一般用于在回收前释放资源
  • finally
    • 是异常机制中用于声明最终执行的代码块的关键字
    • finally语句块中的代码总是执行,无论是否发生异常
      • 但是在try中如果执行了System.exit(0),那么 jvm 就会退出,这个时候finally语句块中的代码就不会被执行了。

自定义异常 &手动抛出异常

  1. 假设有如下业务:

    • 用户在注册的时候要求用户名要超过6位,否则就抛出 IllegalNameException 异常
  2. 自定义异常

    自定义异常既可以定义编译时异常又可以定义运行时异常

    • 定义编译时异常直接继承Exception
    • 定义运行时异常直接继承RuntimeException
  3. 实现

    public class Test12 {
    	public static void main(String[] args) {
    		CustomeService cs = new CustomeService();
    		try {
    			cs.register("jack");// 需要处理异常(要么抛出,要么捕获)
                // jack 小于 6个字符,所以
                /*
                com.test.exception.IllegalNameException: 用户名长度不能低于6个字符
                at com.test.exception.CustomeService.register(Test12.java:27)
                at com.test.exception.Test12.main(Test12.java:7)
    			*/
    		} catch (IllegalNameException e) {
    			e.printStackTrace();
    		}
    	}
    }
    
    class IllegalNameException extends Exception{//定义编译时异常
    	//定义异常一般提供两个构造方法
    	public IllegalNameException() {
    		
    	}
    	
    	public IllegalNameException(String msg) {
    		super(msg);
    	}
    }
    class CustomeService{
    	public void register(String name) throws IllegalNameException{
    		if(name.length() < 6) {
    			IllegalNameException e = new IllegalNameException("用户名长度不能低于6个字符");
    			throw e; //手动抛出异常//这里不使用 try...catch... 处理,因为是要让用户知道该异常,所以需要向上抛出
    		}else {
    			//程序能执行到这里,证明用户名是合法的
    			System.out.println("注册成功");
    		}
    	}
    }
    
    • 自定义异常

      • 可以定义编译时异常或者运行时异常

        • 根据异常出现的概率来决定
        • 然后就可以确定继承那个类了
          • 继承Exception是定义编译时异常
          • 继承RuntimeException是运行时异常
      • 定义异常的时候一般只要写两个构造方法

        public IllegalNameException() {}
        public IllegalNameException(String msg) {
        	super(msg);// 可以用于打印e.message
        }
        
      • 自定义的异常一般在服务端都是向上抛出

        • throws

方法的覆盖与异常

定律:重写的方法不能比被重写的方法抛出更宽泛的异常

  1. 子类无法抛出比父类更多的异常
class A{
    public static void m1(){
        
    }
}
class B extends A{
    @Override
    public static void m1() throws Exception{// 编译无法通过
        // 子类不能抛出比父类更多的异常
    }
}
  1. 子类无法抛出比父类更高层次的异常

    import java.io.*;
    class A{
        public static void m1() throws FileNotFoundException{
            
        }
    }
    class B extends A{
        @Override
        public static void m1() throws IOException{// 编译无法通过
            // 子类不能抛出比父类更高层次的异常
        }
    }
    
    import java.io.*;
    class A{
        public static void m1() throws IoException{
            
        }
    }
    class B extends A{
        @Override
        public static void m1() throws FileNotFoundException{// 编译可以通过
            // 子类可以抛出比父类更低层次的异常
        }
    }
    

以上

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章