异常


Java 异常处理

1.引入:了解理解为主

  1. 异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的。
  2. 比如说,你的代码少了一个分号,那么运行出来结果是提示是错误 java.lang.Error;如果你用System.out.println(11/0),那么你是因为你用0做了除数,会抛出 java.lang.ArithmeticException 的异常。
  3. 异常发生的原因有很多,通常包含以下几大类:1.用户输入了非法数据。2.要打开的文件不存在。3.网络通信时连接中断,或者JVM内存溢出。这些异常有的是因为用户错误引起,有的是程序错误引起的,还有其它一些是因为物理错误引起的
  4. 掌握以下三种类型的异常:
    (1)检查性异常:最具代表的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。
    (2)运行时异常: 运行时异常是可能被程序员避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略。
    (3)错误:** 错误不是异常,而是脱离程序员控制的问题。**错误在代码中通常被忽略。例如,当栈溢出时,一个错误就发生了,它们在编译也检查不到的。

Exception 类的层次(了解)

java.lang.Exception 类是java.lang. Throwable 类的子类。Throwable 类的父类就是java.lang.Object类,除了Exception类外,Throwable还有一个子类Error 。

异常类有两个主要的子类:IOException 类和 RuntimeException 类。
所有RuntimeException 类及其子类都是运行时异常,“运行时异常”在编写程序时可以对这种异常进行处理,也可以不处理,发生概率低
运行时异常又称非受检异常或非受控异常,

所有Exception 类的直接子类IOException 类都是编译时异常,“编译时异常”不是编译阶段发生的,是在编写程序时预先对这种异常进行处理,如果不处理,编译器报错,此异常发生概率高
编译时异常又称受检异常或受控异常,又称检查性异常

所有异常都是发生在运行阶段的(因为异常是类和对象,显示异常就是底层进行异常的new异常对象)

Error表示不可处理,Java 程序通常不捕获错误。错误一般发生在严重故障时,它们在Java程序处理的范畴之外。结果就是终止程序,退出JVM

不管是错误还是异常,都是可抛出的

所有的异常类是从 java.lang.Exception 类继承的子类。 Java 异常以类的形式存在,每一个异常类都可以创建异常对象,异常类就是一个模板,异常对象是实例化的个体,例如:发生火灾,和 小红家发生火灾,

平时控制台显示异常:实际上是JVM在执行到此处的时候,会new相应异常对象,然后JVM将new的异常对象抛出,打印输出信息到控制台了
了解UML:UML是一种统一建模语言,一种图标式语言(画图的),UML不只是在java中使用的,只要是面向对象的编程语言,都有UML,一般画UML图的都是软件架构师或者系统分析师这些人使用的
在java中使用。可以描述类和类之间的关系,程序的执行流程,对象的状态等
在开发中,软件架构师或者系统分析师设计类,java软件开发人员要能看懂
可以用软件 StarUML 来画图,像其中的空心的三角形指向的箭头就是表示继承关系

常见的运行时异常:

  1. ArithmeticException 当出现异常的运算条件时,抛出此异常。例如,一个整数"除以零"时,抛出此类的一个实例。
  2. ArrayIndexOutOfBoundsException 用非法索引访问数组时抛出的异常。如果索引为负或大于等于数组大小,则该索引为非法索引。
  3. IndexOutOfBoundsException 指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。
  4. ClassCastException 当试图将对象强制转换为不是实例的子类时,抛出该异常。
  5. NullPointerException 当应用程序试图在需要对象的地方使用 null 时,抛出该异常
  6. NumberFormatException 当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。

常见的编译时异常:

  1. ClassNotFoundException 应用程序试图加载类时,找不到相应的类,抛出该异常。
  2. NoSuchFieldException 请求的变量不存在
  3. NoSuchMethodException 请求的方法不存在

"运行时异常":

public static void main(String[] args) {
		System.out.println(100/0);
		// 此处发生"运行时异常":java.lang.ArithmeticException: / by zero
		//底层new了一个ArithmeticException异常对象,然后抛出,抛给调用者main方法,没有处理,抛给了JVM,JVM最终终止了程序的执行
		//ArithmeticException异常属于运行时异常,在编译阶段不需要对这种异常进行预先的处理
		//不再往下执行了
		System.out.println("nihao");
	}

异常对象的常用方法:printStackTrace() 和 getMassage()

  1. public String getMessage()
    返回关于发生的异常的详细信息。这个消息在Throwable 类的构造函数中初始化了
  2. public void printStackTrace()
    打印toString()结果和栈层次到System.err,即错误输出流。
public static void main(String[] args) {
//		 1. getMassage()方法  获取异常的简单描述信息
		
		//此处仅仅new了异常对象,但没有将此异常抛出,JVM会认为这仅仅是一个普通的java对象
		NullPointerException a = new NullPointerException("此处是空指针异常");
		System.out.println(a.getMessage());
		//输出:此处是空指针异常
		
		
//		 2.printStackTrace()方法 打印异常追踪的堆栈信息
		NullPointerException b = new NullPointerException("此处是空指针异常");
		b.printStackTrace();
		//输出:java.lang.NullPointerException: 此处是空指针异常

2. Java 的“编译时异常”处理两种方式,是在编译阶段预先进行处理;

public static void main(String[] args) {
		//当在main方法中调用doSome()方法时,因为doSome()方法声明的位置上有编译时异常,
		//在调用这个方法时必须对这种编译时异常进行预先处理
		// doSome();
		/*如果不处理,直接调用,编译器报错Exception in thread "main" java.lang.Error: Unresolved compilation problem: 
		Unhandled exception type ClassNotFoundException*/
		 
	}

         //表示这个doSome()方法在执行的过程中,有可能会出现“类没找到异常”ClassNotFoundException
		//“类没找到异常”ClassNotFoundException直接父类是Exception,属于编译时异常
		 public static void doSome() throws ClassNotFoundException{
			 System.out.println("hei");
		 }

第一种:在方法声明的位置上,使用throws关键字,进行异常的上抛,上抛给调用者

throws 关键字放在方法签名的尾部。
一个方法可以声明抛出多个异常,多个异常之间用逗号隔开。

public class Yichang {

	public static void main(String[] args)throws ClassNotFoundException {
		doSome();
	}
		 public static void doSome() throws ClassNotFoundException{
			 System.out.println("hei");
		 }

}

一般不建议在main方法上使用throws,因为Java中异常发生后,如果一直上抛,最终抛给了调用者main方法,若继续上抛,抛给了JVM,JVM知道此异常,会终止java程序的执行
异常处理机制的作用就是要增强程序的健壮性

第二种:使用try...catch 语句进行异常的捕捉

使用 try 和 catch 关键字可以捕获异常。try/catch 代码块放在异常可能发生的地方。

try
{
   // 程序代码
}catch(ExceptionName e)
{
   //Catch 块
}

Catch 语句包含要捕获异常类型的声明。当保护代码块中发生一个异常时,try 后面的 catch 块就会被检查。

如果发生的异常包含在 catch 块中,异常会被传递到该 catch 块,这和传递一个参数到方法是一样。java运行时系统会自动将catch括号中的Exception e 初始化,也就是实例化Exception类型的对象。e是此对象引用名称。

catch后面的小括号中的类型可以是具体的异常类型,也可以是该类型的父类型

public class Yichang {

	public static void main(String[] args) {
		try {
			doSome();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
/*当try语句中出现异常是时,会执行catch中的语句,java运行时系统会自动将catch括号中的Exception e 初始化,也就是实例化Exception类型的对象。e是此对象引用名称。然后e(引用)会自动调用Exception类中指定的方法,也就出现了e.printStackTrace() ;。
printStackTrace()方法的意思是:在命令行打印异常信息在程序中出错的位置及原因。(这是白话解释,比较容易理解)*/
	}
		 public static void doSome() throws ClassNotFoundException{
			 System.out.println("hei");
		 }

}

也可以使用 throw 关键字抛出一个异常,无论它是新实例化的还是刚捕获到的。

public static void main(String[] args) {
		doSome();
		System.out.println("抛出“运行时异常”后,程序仍然正常执行吗?");
	}
	public static void doSome(){
		throw new NullPointerException();
//		System.out.println(); 此处写入语句,提示Remove,就是不允许写入语句
}
}
/*输出结果:Exception in thread "main" java.lang.NullPointerException*/
/*分析:按住ctrl键,查看源代码,查找到空指针异常NullPointerException,其父类是RuntimeException
 * 说明它是“运行时异常” , 在编译阶段,虽然方法中已经抛出异常,但不会报错
*/

多重捕获块:

一个 try 代码块后面跟随多个 catch 代码块的情况就叫多重捕获。
要求自上而下的异常类型若父类已经捕捉过了,往下不要再次捕捉了(异常类型是从小到大的原则)

try{
   // 程序代码
}catch(异常类型1 异常的变量名1){
  // 程序代码
}catch(异常类型2 异常的变量名2){
  // 程序代码
}catch(异常类型3 异常的变量名3){
  // 程序代码
}

如果保护代码中发生异常,异常被抛给第一个 catch 块。
如果抛出异常的数据类型与 ExceptionType1 匹配,它在这里就会被捕获。
如果不匹配,它会被传递给第二个 catch 块。
如此,直到异常被捕获或者通过所有的 catch 块。

3. finally关键字

  1. finally 关键字用来创建在 try 代码块后面执行的代码块。
  2. 无论是否发生异常,finally 代码块中的代码总会被执行。
  3. 在 finally 代码块中,可以运行清理类型等收尾善后性质的语句。
  4. finally 代码块出现在 catch 代码块最后,语法如下:
try{
  // 程序代码
}catch(异常类型1 异常的变量名1){
  // 程序代码
}catch(异常类型2 异常的变量名2){
  // 程序代码
}finally{
  // 程序代码
}

catch 不能独立于 try 存在。
在 try/catch 后面添加 finally 块并非强制性要求的。
try 代码后不能既没 catch 块也没 finally 块。
try, catch, finally 块之间不能添加任何代码。

public class Yichang {

	public static void main(String[] args) {
		int a[] = new int[2];
	    try{
	       System.out.println("Access element three :" + a[3]);
	    }catch(ArrayIndexOutOfBoundsException e){
	       System.out.println("Exception thrown  :" + e);
	    }
	    finally{
	       a[0] = 6;
	       System.out.println("First element value: " +a[0]);
	       System.out.println("The finally statement is executed");
	    }
/*运行结果:
Exception thrown :java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 2
First element value: 6
The finally statement is executed
*/

finally面试题:

public class Yichang {

	public static void main(String[] args) {
		int s = m();
		System.out.println(s);
		//输出结果:100
	}

	public static int m() {
		int i = 100;
		try {
			return i;
		} finally {
			i++;
		}
	}
}
/*因为java有规则:(不可以违背)
 * 1. 方法体中的代码必须自上而下,依次逐行执行
 * 2. 一旦return 语句执行,整个方法必须结束,
 * 反编译的执行:
 * public static int m() {
		int i = 100;
		int j = i;
		i++;
		return j;
		}
		此处的finally是执行了,return 也最后执行了*/

final 和 finally 和 finalize 之间的区别:

  1. final 是一个关键字,表示最终的,不可变的
  2. finally是一个关键字,和try联合使用,使用在异常机制中,在finally语句块中的代码一定会执行
  3. finalize()是Object类中的一个方法,作为方法名出现,是一个标识符

4.声明自定义异常:

编写自己的异常类时需要记住下面的几点。

  1. 所有异常都必须是 Throwable 的子类。

  2. 如果希望写一个编译时异常类,则需要继承 Exception 类。(一般用于异常发生概率比较高时)

  3. 如果你想写一个运行时异常类,那么需要继承 RuntimeException 类。

  4. 编写需要两步:1. 编写一个类继承Exception 类或继承 RuntimeException 类。2. 提供有参和无参的构造方法

  5. 以下实例是一个银行账户的模拟,通过银行卡的号码完成识别,可以进行存钱和取钱的操作。
    InsufficientFundsException.java 文件代码:
    自定义异常类,继承Exception类

package InsufficientFundsException;

public class InsufficientFundsException extends Exception{
	//此处的amount用来储存当出现异常(取出的钱多于现有余额时)所缺乏的钱
	  private double amount;
	  
	  //有参构造
	  public InsufficientFundsException(double amount)
	  {
	    this.amount = amount;
	  } 
	  
	  
	  public double getAmount()
	  {
	    return amount;
	  } 
}

CheckingAccount 类中包含一个 withdraw() 方法抛出一个 InsufficientFundsException 异常。
CheckingAccount.java 文件代码:

package InsufficientFundsException;
public class CheckingAccount {
	//balance为余额,number为卡号
	   private double balance;
	   private int number;
	   
	  //有参构造
	   public CheckingAccount(int number)
	   {
	      this.number = number;
	   }
	   
	  //方法:存钱
	   public void deposit(double amount)
	   {
	      balance += amount;
	   }
	   
	   
	  //方法:取钱  要取的钱数:amount
	   public void withdraw(double amount) throws InsufficientFundsException
	   {//此处要加 throws InsufficientFundsException  
		//没有会提示 Unhandled exception type InsufficientFundsException 未处理的异常类型不足FundsException
	      if(amount <= balance)
	      {
	         balance -= amount;
	      }
	       else
	      {
	         double needs = amount - balance;
	         throw new InsufficientFundsException(needs);
	         /*相当于以下语句合并了:
	          *InsufficientFundsException e =  new InsufficientFundsException(needs);
	          *throw e;*/
	      }
	    /*在此处用throw抛出一个异常,因为是编译时异常,在主方法调用他之前必须对他在编译阶段进行处理,因为或者throws上抛 或者try catch进行捕捉异常,
	    但此处若采用try catch进行捕捉异常,就是自己抛出,自己抓,没有意义,
	    所以采取throws上抛*/
	   }
	   }
	   
	   
	  //方法:返回余额
	   public double getBalance()
	   {
	      return balance;
	   }
	   
	   
	  //方法:返回卡号
	   public int getNumber()
	   {
	      return number;
	   }
}

BankDemo 程序示范了如何调用 CheckingAccount 类的 deposit() 和 withdraw() 方法。

package InsufficientFundsException;

public class BankDemo {
	 public static void main(String [] args)
	   {
	      CheckingAccount c = new CheckingAccount(20211127);//调用CheckingAccount类的有参构造,传过去卡号
	      System.out.println("存入 $500...");
	      c.deposit(500.00);//调用存钱方法
	      try
	      {
	         System.out.println("取出 $100...");
	         c.withdraw(100.00);//调用取钱方法
	         System.out.println("取出 $600...");
	         c.withdraw(600.00);//调用取钱方法
	      }catch(InsufficientFundsException e)
	      {
	         System.out.println("Sorry, but you are short $" + e.getAmount());
	         e.printStackTrace();//打印异常的信息
	      }
	    }
}
/*运行结果:
存入 $500...
取出 $100...
取出 $600...
Sorry, but you are short $200.0
InsufficientFundsException.InsufficientFundsException
	at InsufficientFundsException.CheckingAccount.withdraw(CheckingAccount.java:30)
	at InsufficientFundsException.BankDemo.main(BankDemo.java:14)
*/
在上述异常处理中有非常常用的:

在某种不合理的条件下可能出现异常时,自己手动抛出异常,并采取throws上抛的处理方式,而不是,自己抛出,自己抓的try catch的处理方式
在测试程序的main方法中,不建议再往上抛,所以采取try catch的捕捉的处理方式
IllegalNameException.java(自定义“编译时异常”)

package 异常;
//自定义“编译时异常”
public class IllegalNameException extends Exception {
	//无参构造
	public IllegalNameException() {
		
	}
	//有参构造
	public IllegalNameException(String s) {
		super(s);
	}
}
/*解释有参构造中的使用super(参数):
 * 源代码:
 * public Exception(String message) {
        super(message);
    }
    
    public Throwable(String message) {
        fillInStackTrace();
        detailMessage = message;
    }
    
     private String detailMessage;
     
     public String getMassage(){
     
              return detailMessage;
     }
     
     执行以上代码就是构造异常对象时,将new的异常对象的信息传过去
     后来之所以我们可以调用异常类Exception 中的getMassage()方法  获取异常的简单描述信息
    */

Use.java(在主类中出现异常)

package 异常;
/*用户类:*/
public class User {
	
	//注册时(判断用户名是否合法)方法
	public void register(String username,String password) throws IllegalNameException {
		if(username==null||username.length()<6||username.length()>14) {
			throw new IllegalNameException("用户名不合法,长度必须在【6到14】之间");
		}
		else {
			System.out.println("用户名合法,注册成功,欢迎["+username+"]");
		}
	}
}
/*知识点:
 * 1.username==null 条件写在最前面,有助于防止出现空指针异常
 * 2. 此处用到“||” 可以有短路运算逻辑,一旦第一个出现空指针异常,见假为假,直接输出null不再继续往下判断了
 * 3.用到String类中的方法 length() 来获取字符串的长度*/

Test.java(测试运行阶段)

package 异常;

public class Test {

	public static void main(String[] args) {
		 //创建用户对象
		User u = new User();
		try {
			u.register("xiao", "2021");
		} catch (IllegalNameException e) {
			System.out.println(e.getMessage());
//			e.printStackTrace();
		}
		
		
		User s = new User();
		try {
			s.register("xiaotong", "2021");
		} catch (IllegalNameException e) {
			System.out.println(e.getMessage());
		}
	}
/*输出结果:
用户名不合法,长度必须在【6到14】之间
用户名合法,注册成功,欢迎[xiaotong]
 */
}
子类在方法的重写时,要求不能抛出比父类更多的异常,可以更少;

在实际开发中直接将父类的方法头复制过来用就行,一般不会去分析

class Animal {
	public void doSome() {

	}
}

class cat extends Animal {
//	public void doSome() throws Exception {
//	} 因为父类没有抛出异常,但子类却抛出,编译错误
}

class Animal01 {
	public void doSome()throws Exception  {

	}
}

class cat01 extends Animal01 {
	public void doSome() throws NullPointerException {
//子类抛出了父类异常的子类异常也是可以的
	}
}

相关