Java技术进阶之异常

简介: Java技术篇

为什么需要异常机制:

Java的基本理念是“结构不佳的代码不能运行” --- Java编程思想

最理想的是在编译时期就发现错误,但一些错误要在运行时才会暴露出来。对于这些错误我们当然不能置之不理。对于错误而言的两个关键是发现和处理错误。Java提供了统一的异常机制来发现和处理错误。

不考虑异常存在来看一下这个场景:

publicvoidshowObject(Objectobj) {
if (obj==null) {
System.out.println("error obj is null");
    } else {
System.out.println(obj.toString());
    }
}

对于showObject来说obj为null是一个错误,要在输出之前做错误判断,发生错误的话把错误打印出来作为错误报告和处理。这里把错误的发现、报告处理和正常业务逻辑放在了一起。但一些错误往往比这复杂且不只一个,如果我们为每一个错误都去定义一个独特错误报告的形式且都将错误处理代码和正常业务代码紧紧的耦合在一起那我们代码会变得难以维护。

合理的使用异常不仅能使我们的代码更加健壮,还能简化开发提升开发效率。

一、抛出异常(发现错误):

1、异常也是对象:

java使用使用异常机制来报告错误。异常也是普通的类类型。Java自身已经定义好了使用java时可能会产生的异常,使用java时java会自动去检查异常的发生,当异常发生时,java会自动创建异常对象的实例并将其抛出。我们经常看到的NullPointerException便是java已经定义好的异常。

除了java自身定义的异常外我们可以自定义异常,但自定义的异常需要我们自己去检查异常情形的发生,并自己创建异常对象和抛出。当然也可以创建java自定义的异常并抛出,抛出异常使用throw关键字:

thrownewException();

我们使用的第三方库大多封装了自己的异常,并在异常情形发生时将自定义异常通过throw抛出。所有的异常类型都继承自Throwable类,所有Throwable类型的对象都可被抛出。

2、抛出异常:

异常发生,系统自动创建异常实例并抛出,或我们自己创建异常实例抛出异常时,代码的正常执行流程将会被终止,转而去执行异常处理代码。

二、异常捕获(处理错误):

1、监控区域:

当异常抛出时,自然抛出的异常应该得到处理,这就需要将抛出的捕获异常。但一个异常类型可能在很多地方被抛出,那么怎么去对特定的地方编写特定的异常处理程序那?java采用一个最方便和合理的方式,即对可能产生异常的代码区域进行监控,并在该区域后添加处理程序。

监控的代码区域放在try{}中,而异常处理代码紧跟在try后的catch中:

try {
/***/} catch (ExceptionTypee) {
/*****/}

catch类似方法的申明括号中为异常的类型和该类类型的实例。表明当前catch块处理的是什么类型的异常,而e便是该异常抛出的实例对象。

当try内的代码抛出异常时,就会停止当前流程,去匹配第一个catch中申明的异常类型与抛出类型相同的catch,如果匹配到则执行其内代码。一个try中可能会抛出多种类型的异常,可以用多个catch去匹配。

2、捕获所有异常:

注意catch中声明的异常如果为当前抛出异常的父类型也可以匹配。所以一般将基类的异常类型放在后面。

因为所有可以进行捕获的异常都继承自Exception,所有可以catch中申明Exception类型的异常来捕获所有异常,但最后将其放在最后防止将其他异常拦截了。

三、重新抛出异常

一、异常的两种类型

先看一下异常的类层次结构图:

4.png

我们可以将异常分为检查和非检查两种类型:

  • 检查异常:该类异常包含Exception及其子类异常,这些类型的异常的抛出必须有相应的catch进行捕获,否则无法通过编译。
  • 非检查异常:该类型的异常包含RuntimeException、Error和两者的子类型,这类异常可以没有对应的try-catch进行捕获也可通过编译。当异常发生时没有相应的捕获则异常会自动向上一级抛出,如此如果一直到main方法中还未被捕获则会调用该异常的printStacjTrace方法输出异常信息,并终止main的运行。其中Error为Java自身错误,这类错误发生时我们并不能在业务代码中去解决,如内存不足,所以这类异常不需要去捕获。
1、异常声明:

catch中的语句执行完成后会继续执行try-catch后的其他语句。所以当try-catch后还有语句时,一定要保证但异常发生时在catch中已经对异常进行了正确处理,后面的代码可以得到正常的运行,如果不能保证则应该终止代码向后的执行或再次抛出异常。

一些异常在当前的方法中不需要或无法进行处理时,可以将其抛出到上一层。要在方法中将异常抛出需要在方法中对要抛出的异常进行声明,这样方法的调用者才能知道哪些异常可能会抛出,从而在调用方法时添加异常处理代码。

非检查异常抛出到上一级时可以不用进行声明,合理的使用非检查异常可以简化代码。

(1) 异常声明

在方法声明的参数列表之后使用throws进行异常声明,多个异常类型使用逗号隔开:

voidt () thrwosExcptionTyep1, ExceptionType2 {
}

在方法中声明了的异常在方法中可以不进行捕获,直接被抛出到上一级。异常声明父类异常类型可以匹配子类异常类型,这样当有多个子类异常抛出时,只用声明一个父类异常即可,子类异常将被自动转换为父类型。

四、创建自定义异常:

要创建自己的异常必须得继承自其它的异常,一般继承Exception创建检测异常,继承RumtimeException创建非检查异常。

一般情况下异常提供了默认构造器和一个接受String参数的构造器。对于一般自定义的异常来说,只需要实现这两个构造方法就足够了,因为定义异常来说最有意义的是异常的类型,即异常类的名字,但当异常发生时只需看到这个异常的类型就知道发生了什么,而其他一些操作在Throwable中已经有定义。所以除非有一些特殊操作,不然在自定义异常时只需只需简单的实现构造方法即可。

五、异常信息:

所以异常的根类Throwable定义了我们需要的大多数方法:

// 获取创建异常时传入的字符串StringgetMessage()
// 使用System.err输出异常发生的调用栈轨迹voidprintStackTrace()
// 使用传入的PrintStream打印异常调用栈voidprintStackTrace(PrintStreams)
// 使用PrintStreamOrWriter打印异常调用栈voidprintStackTrace(PrintStreamOrWriters)
获取调用栈实例StackTraceElement[] getStackTrace()
该方法放回StackTraceElement数组,StackTraceElement为调用方法栈的实例,改类型有以下常用方法:// 返回栈代码所在的文件名StringgetFileName()
// 返回异常抛出地的行号intgetLineNumber()
// 返回栈的类名StringgetClassName()
// 放回栈的方法名StringgetMethodName()

六、异常链:

1、重新获取异常

当我们捕获到一个异常时可能想将他在次抛出,但这样直接抛出的话异常的栈信息是该异常原来的栈信息,不会是最新的再次抛出的异常的栈信息。如下:

classSimpleExceptionextendsException {
publicSimpleException() {
    }
publicSimpleException(Stringmsg) {
super(msg);
    }
}
publicclassTest {
publicvoids() throwsSimpleException {
thrownewSimpleException();
    }
publicvoids2() throwsSimpleException {
try {
s();
        } catch(SimpleExceptione) {
throwe;
        }
    }
publicstaticvoidmain(String[] args) {
Testt=newTest();
try {
t.s2();
        } catch (SimpleExceptione) {
e.printStackTrace();
        } 
    }
}

上面代码输出为:

com.ly.test.javatest.exceptiontest.SimpleExceptionatcom.ly.test.javatest.exceptiontest.Test.s(Test.java:19)
atcom.ly.test.javatest.exceptiontest.Test.s2(Test.java:24)
atcom.ly.test.javatest.exceptiontest.Test.main(Test.java:33)

可以看到异常抛出最终地为 com.ly.test.javatest.exceptiontest.Test.s(Test.java:19),但如果我们想让异常抛出地变为s2那?毕竟我们在这里自己抛出了异常。Thrwoable类的fillInStackTrac创建一个新的Throwable对象,并将当前栈信息做新创建的Throwable异常的异常栈信息,然后返回。

2、异常链:

上面的做法又有另外一个问题,如果我们使用fillInStackTrace获得新的异常,那原来的异常信息也就丢失了,如果我们想抛出新的异常当又得包含原来的异常那?

Error、Exception和RuntimeException都含有一个接受Throwable对象的构造方法,在创建新的异常时时传入原来异常,即可保存原来异常。需要时使用getCause来获取到。除了使用构造方法传入异常,还可使用initCase方法传入异常。这其中的潜台词是“改异常是由什么异常造成的”。如下:

publicclassTest {
publicvoids() throwsException {
thrownewException();
    }
publicvoids2() throwsException {
try {
s();
        } catch(Exceptione) {
Exceptionne= (Exception)e.fillInStackTrace();
ne.initCause(e);
throwne;
        }
    }
publicstaticvoidmain(String[] args) {
Testt=newTest();
try {
t.s2();
        } catch (Exceptione) {
e.printStackTrace();
        } 
    }
}

六、总是执行的finally

看一下面的代码:

publicclassTest {
publicstaticvoids() throwsIOException {
thrownewIOException();
    }
publicstaticvoidmain(String[] args) {
StringfileName="C:\\temp\\test.txt";
Filefile=newFile(fileName);
InputStreamin=null;
try {
in=newFileInputStream(file);
s();
inttempbyte=in.read();
in.close();
        } catch (IOExceptione) {
if (in!=null) {
System.out.println("in");
            }
e.printStackTrace();
        }
    }
}

可以看到要对in进行close但正常的流程中发生了异常,导致正常流程中的in.close无法执行,便跳到cattch中去执行,上面于是又在catch中写了一个关闭。着只是一个简单清理操作,但如果需要执行的清理操作不止一行而是非常多那?也是在正常流程和catch中写两遍吗,这样是非常不友好的,所以java提供了finally,如下

publicclassTest {
publicstaticvoids() throwsIOException {
thrownewIOException();
    }
publicstaticvoidmain(String[] args) {
StringfileName="C:\\temp\\test.txt";
Filefile=newFile(fileName);
InputStreamin=null;
try {
in=newFileInputStream(file);
s();
inttempbyte=in.read();
        } catch (IOExceptione) {
e.printStackTrace();
        } finally {
if (in!=null) {
System.out.println("in");
            }
        }
    }
}

finally中的代码无论异常是否发生都会被执行,即使try中包含return语句,也会在放回之前执行finally语句。

对于清理操作和一些异常发生也必得到执行的代码都应该放到finally中。

清理未创建的资源:

上面介绍使用finally来释放资源,但看下面这个情形:

publicvoidtest() {
try {
in=newBufferedReader(newFileReader());
Strings=in.readLine();
    } catch (FileNotFoundExceptione) {
    } catch (Exceptione) {
try {
in.close();
        } catch (IOExceptione2) {
System.out.println("in class false");
        }
    } finally {
//in.close();    }
}

这个例子可以看到如果new FileReader抛出了FileNotFoundException,那么in是不会被创建的,如果此时还在finally中执行in.close()那么自然是行不同的。但如果抛出了IOExceptin异常,那么说明in成功创建但在readLine时发生错,所以在catch中进行close时in肯定已经被创建。这种情形资源的释放应该放到catch中。

七、异常丢失:
1、在fianlly中return
publicclassTest {
publicstaticvoidmain(String[] args) {
try {
inti=throwException();
System.out.println(i);
        } catch (Exceptione) {
e.printStackTrace();
        }
    }
publicstaticintthrowException () throwsException {
try {
thrownewException();
        } catch (Exceptione) {
throwe;
        } finally {
return1;
        }
    }
}

上面代码输出:1

2、在finally中抛出异常
publicclassTest {
publicstaticvoidmain(String[] args) {
try {
inti=throwException();
System.out.println(i);
        } catch (Exceptione) {
e.printStackTrace();
        }
    }
publicstaticintthrowException () throwsException {
try {
thrownewException();
        } catch (Exceptione) {
throwe;
        } finally {
thrownewNullPointerException();
        }
    }
}

上面代码输出为:

java.lang.NullPointerExceptionatcom.ly.test.javatest.Test.throwException(Test.java:20)
atcom.ly.test.javatest.Test.main(Test.java:7)

可以看到main中捕获到的是NullPointerException,首先抛出的Exception异常丢失了。

在开发中非特殊情形应避免以上两种情况的出现。

八、异常限制:

父类构造器中声明的异常在基类的构造器中必须也声明,因为父类的构造器总是会显示会隐式(默认构造器)的被调用,而在子类构造器中是无法捕获父类异常的。但子类可以添加父类中没有声明的异常。

重载方法时子类只可抛出父类中声明的异常,因为我们会将子类对象去替换基类,这时如果重载的方法添加类新的异常声明,那么原来的异常处理代码将无法再正常工作。但子类方法可以减少或不抛出父类方法声明的异常。

相关文章
|
1月前
|
存储 监控 安全
单位网络监控软件:Java 技术驱动的高效网络监管体系构建
在数字化办公时代,构建基于Java技术的单位网络监控软件至关重要。该软件能精准监管单位网络活动,保障信息安全,提升工作效率。通过网络流量监测、访问控制及连接状态监控等模块,实现高效网络监管,确保网络稳定、安全、高效运行。
67 11
|
2月前
|
Java
在 Java 中捕获和处理自定义异常的代码示例
本文提供了一个 Java 代码示例,展示了如何捕获和处理自定义异常。通过创建自定义异常类并使用 try-catch 语句,可以更灵活地处理程序中的错误情况。
92 1
|
1月前
|
XML Java 编译器
Java注解的底层源码剖析与技术认识
Java注解(Annotation)是Java 5引入的一种新特性,它提供了一种在代码中添加元数据(Metadata)的方式。注解本身并不是代码的一部分,它们不会直接影响代码的执行,但可以在编译、类加载和运行时被读取和处理。注解为开发者提供了一种以非侵入性的方式为代码提供额外信息的手段,这些信息可以用于生成文档、编译时检查、运行时处理等。
71 7
|
2月前
|
Java API 调度
如何避免 Java 中的 TimeoutException 异常
在Java中,`TimeoutException`通常发生在执行操作超过预设时间时。要避免此异常,可以优化代码逻辑,减少不必要的等待;合理设置超时时间,确保其足够完成正常操作;使用异步处理或线程池管理任务,提高程序响应性。
129 12
|
2月前
|
Java
在 Java 中,如何自定义`NumberFormatException`异常
在Java中,自定义`NumberFormatException`异常可以通过继承`IllegalArgumentException`类并重写其构造方法来实现。自定义异常类可以添加额外的错误信息或行为,以便更精确地处理特定的数字格式转换错误。
49 1
|
27天前
|
移动开发 前端开发 Java
Java最新图形化界面开发技术——JavaFx教程(含UI控件用法介绍、属性绑定、事件监听、FXML)
JavaFX是Java的下一代图形用户界面工具包。JavaFX是一组图形和媒体API,我们可以用它们来创建和部署富客户端应用程序。 JavaFX允许开发人员快速构建丰富的跨平台应用程序,允许开发人员在单个编程接口中组合图形,动画和UI控件。本文详细介绍了JavaFx的常见用法,相信读完本教程你一定有所收获!
Java最新图形化界面开发技术——JavaFx教程(含UI控件用法介绍、属性绑定、事件监听、FXML)
|
13天前
|
监控 JavaScript 数据可视化
建筑施工一体化信息管理平台源码,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
智慧工地云平台是专为建筑施工领域打造的一体化信息管理平台,利用大数据、云计算、物联网等技术,实现施工区域各系统数据汇总与可视化管理。平台涵盖人员、设备、物料、环境等关键因素的实时监控与数据分析,提供远程指挥、决策支持等功能,提升工作效率,促进产业信息化发展。系统由PC端、APP移动端及项目、监管、数据屏三大平台组成,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
|
1月前
|
JavaScript 安全 Java
java版药品不良反应智能监测系统源码,采用SpringBoot、Vue、MySQL技术开发
基于B/S架构,采用Java、SpringBoot、Vue、MySQL等技术自主研发的ADR智能监测系统,适用于三甲医院,支持二次开发。该系统能自动监测全院患者药物不良反应,通过移动端和PC端实时反馈,提升用药安全。系统涵盖规则管理、监测报告、系统管理三大模块,确保精准、高效地处理ADR事件。
|
2月前
|
IDE 前端开发 Java
怎样避免 Java 中的 NoSuchFieldError 异常
在Java中避免NoSuchFieldError异常的关键在于确保类路径下没有不同版本的类文件冲突,避免反射时使用不存在的字段,以及确保所有依赖库版本兼容。编译和运行时使用的类版本应保持一致。
101 7
|
2月前
|
监控 前端开发 Java
【技术开发】接口管理平台要用什么技术栈?推荐:Java+Vue3+Docker+MySQL
该文档介绍了基于Java后端和Vue3前端构建的管理系统的技术栈及功能模块,涵盖管理后台的访问、登录、首页概览、API接口管理、接口权限设置、接口监控、计费管理、账号管理、应用管理、数据库配置、站点配置及管理员个人设置等内容,并提供了访问地址及操作指南。