异常的概念
异常是程序在“编译”或者“执行”的过程中可能出现的问题
异常应该尽量提前避免,但是无法做到绝对避免,异常的可能情况太多,开发中只能提前干预。
异常一旦出现,如果没有提前避免,程序就会退出JVM虚拟机而终止,开发中异常是需要提前处理的。研究异常,并且避免异常,然后提前处理异常,体现的是程序的安全性,健壮性
Java会为常见的代码异常都设计一个类来代表
Error
错误的意思,严重错误Error,无法通过处理的错误,一旦出现,程序员难以进行修改,一般只能重启系统,优化项目。例如:内存崩溃,JVM本身崩溃
Exception
异常类,是开发中代码在编译或者执行过程中可能出现的错误,它是需要提前处理的,以便程序更加健壮
- 编译时异常:继承自Exception的异常或者其子类,编译阶段就会报错,必须程序员进行处理,否则代码编译无法通过
- 运行时异常,继承自RuntimeException的异常或其子类,编译阶段不报错,运行阶段出现,运行时异常可处理也可不处理
运行时异常
常见的运行时异常
- 数组索引越界异常:ArrayIndexOutOfBoundsException
- 空指针异常:NullPointerException(直接输出没有问题,但是调用空指针变量的功能就会报错)
- 类型转换异常:ClassCastException
- 迭代器遍历没有此元素异常:NoSuchElementException
- 数学操作异常:ArithmeticException
- 数字转换异常:NumberFormatException
public class test1 {
public static void main(String[] args) {
//数组索引越界异常:ArrayIndexOutOfBoundsException
int[] array={10,20,30};
System.out.println(array[3]);
//空指针异常:NullPointerException
String str1=null;
System.out.println(str1); //直接输出没有问题
System.out.println(str1.length()); //但是调用空指针变量的功能就会报错
//类型转换异常:ClassCastException
Object name="Leslie";
Integer s=(Integer) name;
//迭代器遍历没有此元素异常:NoSuchElementException
//数学操作异常:ArithmeticException
int c=10/0;
//数字转换异常:NumberFormatException
String num="23a";
Integer n=Integer.valueOf(num);
}
}
编译时异常
代码编译阶段就会报错
代码语言:javascript复制package ExceptionTest;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class ExceptionDemo {
//这里如果不设置throws ParseException,就会抛出编译异常
//原因就在于parse操作很容易不规范
public static void main(String[] args) throws ParseException {
String date="2015-01-12 10:23:21";
SimpleDateFormat sdf =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date d =sdf.parse(date);
System.out.println(d);
}
}
异常的默认处理机制
异常产生后的默认处理过程是自动处理过程
- 默认会在出现异常的代码处自动创建一个异常对象:ArithmeticException等
- 异常会从方法中出现的点这里先抛出给调用者,各层调用者不断抛出最终抛出给JVM虚拟机
- 虚拟机接收到异常对象后,现在控制台输出/打印异常栈信息数据
- 直接从当前执行的异常点终止当前程序
这种默认异常处理机制并不好(对于项目开发),一旦出现真的异常,会立即导致程序的死亡
编译时异常处理方式
方式一:直接抛出错误
代码语言:javascript复制方法 throws 异常1,异常2,...{
...
}
//上述方式,需要对异常逐个抛出,当异常很多时并不方便
//一般建议采用throws Exception这种方式,直接抛出根类异常
方法 throws Exception{
...
}
代码语言:javascript复制package ExceptionTest;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class ExceptionDemo1 {
public static void main(String[] args) throws ParseException {
System.out.println("----程序开始----");
parseDate("2020-05-15 08:54:05"); //如果程序正确可以输出结果
parseDate("2020/05/15/08/54/05"); //如果程序错误依旧会报错并直接终止程序
System.out.println("----程序结束----");
}
public static void parseDate(String time) throws ParseException {
SimpleDateFormat sdf =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date d=sdf.parse(time);
System.out.println(d);
}
}
这种异常处理方式并不好,整个过程中只是逐层把异常向上层抛出,本质与默认处理方式相同,只是避免了编译阶段的报错,让程序能够正常编译。但一旦出现错误,仍然会导致JVM虚拟机终止程序。这种方法适用于程序较小,报错原因较易排查的项目
方式二:在出现异常的地方自己处理,谁出现谁处理
采用监视捕获异常的方式,即try catch
代码语言:javascript复制package ExceptionTest;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class ExceptionDemo1 {
public static void main(String[] args) {
System.out.println("----程序开始----");
parseDate("2020-05-15 08:54:05"); //程序正确
parseDate("2020/05/15/08/54/05"); //程序错误
System.out.println("----程序结束----");
}
public static void parseDate(String time) {
try{
SimpleDateFormat sdf =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date d=sdf.parse(time);
System.out.println(d);
}
catch (ParseException e){ //可以并列多个异常,或是直接用Exception监视所有可能异常
e.printStackTrace(); //打印异常栈信息,不会引起程序死亡
}
}
}
(在企业开发中,一般直接采用监视Exception根类的方式,这样可以监视并打印所有可能的异常)
第二种处理异常的方式可以处理异常,并且出现异常后代码也不会死亡而是正常执行,但这种方式也存在不足之处,在没有返回值的情况下,下层独自监视处理异常,导致上层不了解下层的处理结果而是盲目的运行程序
方式三:在出现异常的地方把异常逐层抛出给最外层调用者,最外层调用者几种捕获处理(规范做法)
代码语言:javascript复制package ExceptionTest;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class ExceptionDemo1 {
public static void main(String[] args) {
System.out.println("----程序开始----");
try {
parseDate("2020-05-15 08:54:05"); //程序正确
parseDate("2020/05/15/08/54/05"); //程序错误
} catch (ParseException e) {
e.printStackTrace();
}
System.out.println("----程序结束----");
}
public static void parseDate(String time) throws ParseException {
SimpleDateFormat sdf =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date d=sdf.parse(time);
System.out.println(d);
}
}
这种方案最外层调用者可以知道底层执行的情况,同时程序在出现异常后也不会立即死亡,这是理论上最好的方案
运行时异常的处理机制
运行时异常的处理规范:直接在最外层捕获处理即可,底层会自动抛出
代码语言:javascript复制package ExceptionTest;
public class ExceptionDemo2 {
public static void MyDivision(int a,int b){
System.out.println(a/b);
}
public static void main(String[] args) {
System.out.println("----程序开始----");
try{
MyDivision(10,0);
System.err.println("运行成功!");
}catch (Exception e){
e.printStackTrace();
System.err.println("运行失败!");
}
}
}
异常捕获的本质就是直接将上层传输的异常捕获,然后输出它的异常栈信息,避免异常传输到JVM虚拟机,终止程序。从而保证了程序能够顺利执行完毕
finally关键字
用在捕获处理的异常格式中的,放在捕获异常的最后面
代码语言:javascript复制try{
//可能出现异常的代码
}catch(Exception e){
e.printStackTrace();
}finally{
//无论代码是否出现异常还是正常执行
//最终一定要执行这里的代码
}
在捕获异常中,try固定出现一次,catch出现0~N次(当存在finally时,可以省略catch,其他情况下不可以),finally可以出现至多一次
finally作用
可以在代码执行完毕之后进行资源(资源都实现了Close able接口,自带close()方法)的释放操作
代码语言:javascript复制package ExceptionTest;
public class ExceptionDemo2 {
public static double MyDivision(double a,double b){
try{
return a/b;
}
catch (Exception e){
return 0.0;
}
finally {
System.out.println("====finally被执行====");
}
}
public static void main(String[] args) {
System.out.println(MyDivision(10,2));
/* 这里就可以看出finally的强制执行效果
程序正常执行,原本应该进入try,在try中会执行return语句
return语句理应终止程序运行,但程序还是被强制执行了finally中的内容
也就是在这里实际执行流程是在执行到return语句后被强制跳转到finally语句,先执行其中内容
因此,在finally语句中加入return语句是十分危险的
因为无论任何情况,只要finally中包含return语句,最后都只会执行finally中的return语句
*/
//return 233;
}
}
自定义异常
自定义编译时异常
代码语言:javascript复制package ExceptionTest;
/*
* 自定义编译时异常:
* 1. 继承Exception
* 2. 重写构造器
* */
public class MyException extends Exception{
public MyException() {
}
public MyException(String message) {
super(message); //调用父类构造器
}
public MyException(String message, Throwable cause) {
super(message, cause);
}
public MyException(Throwable cause) {
super(cause);
}
public MyException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
调用自定义异常
代码语言:javascript复制package ExceptionTest;
public class ExceptionDemo3 {
public static void main(String[] args) {
try {
ageControl(12);
}catch (Exception e){
e.printStackTrace();
}
}
public static void ageControl(int age) throws MyException {
if(age>60 || age<18){
throw new MyException("/ age is illegal");
}
else{
System.out.println("age is " age);
}
}
}
自定义的编译时异常,在调用时会直接报错,所以直接用throws抛出给上层即可
注意:
throws用在方法上,用于抛出方法中的异常给调用方
throw用在出现异常的地方,用于创建异常对象且立即从此处抛出
自定义运行时异常的方式跟自定义编译时异常基本一致,只是运行时异常需要继承的是RuntimeException,并且在编译阶段并不会报错