在Java中的基本理念是结构不佳的代码不能运行,发现错误的理想时期是在编译期间,因为你不用运行程序,只是凭借着对Java基本理念的理解就能发现问题。但是编译期并不能找出所有的问题,有一些NullPointerException和ClassNotFoundException在编译期找不到,这些异常是RuntimeException运行时异常,这些异常往往在运行时才能被发现。 我们写Java程序经常会出现两种问题,一种是java。lang。Exception,一种是java。lang。Error,都用来表示出现了异常情况,下面就针对这两种概念进行理解。 认识Exception Exception位于java。lang包下,它是一种顶级接口,继承于Throwable类,Exception类及其子类都是Throwable的组成条件,是程序出现的合理情况。 在认识Exception之前,有必要先了解一下什么是Throwable。 什么是Throwable Throwable类是Java语言中所有错误(errors)和异常(exceptions)的父类。只有继承于Throwable的类或者其子类才能够被抛出,还有一种方式是带有Java中的throw注解的类也可以抛出。 在Java规范中,对非受查异常和受查异常的定义是这样的: Theuncheckedexceptionclassesaretheruntimeexceptionclassesandtheerrorclasses。 Thecheckedexceptionclassesareallexceptionclassesotherthantheuncheckedexceptionclasses。Thatis,thecheckedexceptionclassesareThrowableandallitssubclassesotherthanRuntimeExceptionanditssubclassesandErroranditssubclasses。 也就是说,除了RuntimeException和其子类,以及error和其子类,其它的所有异常都是checkedException。 那么,按照这种逻辑关系,我们可以对Throwable及其子类进行归类分析 可以看到,Throwable位于异常和错误的最顶层,我们查看Throwable类中发现它的方法和属性有很多,我们只讨论其中几个比较常用的 返回抛出异常的详细信息publicstringgetMessage();publicstringgetLocalizedMessage();返回异常发生时的简要描述publicpublicStringtoString();打印异常信息到标准输出流上publicvoidprintStackTrace();publicvoidprintStackTrace(PrintStreams);publicvoidprintStackTrace(PrintWriters)记录栈帧的的当前状态publicsynchronizedThrowablefillInStackTrace(); 此外,因为Throwable的父类也是Object,所以常用的方法还有继承其父类的getClass()和getName()方法。 常见的Exception 下面我们回到Exception的探讨上来,现在你知道了Exception的父类是Throwable,并且Exception有两种异常,一种是RuntimeE一种是CheckedException,这两种异常都应该去捕获。 下面列出了一些Java中常见的异常及其分类,这块面试官也可能让你举出几个常见的异常情况并将其分类 RuntimeException UncheckedException 与Exception有关的Java关键字 那么Java中是如何处理这些异常的呢?在Java中有这几个关键字throws、throw、try、finally、catch下面我们分别来探讨一下 throws和throw 在Java中,异常也就是一个对象,它能够被程序员自定义抛出或者应用程序抛出,必须借助于throws和throw语句来定义抛出异常。 throws和throw通常是成对出现的,例如 staticvoidcacheException()throwsException{thrownewException();} throw语句用在方法体内,表示抛出异常,由方法体内的语句处理。throws语句用在方法声明后面,表示再抛出异常,由该方法的调用者来处理。 throws主要是声明这个方法会抛出这种类型的异常,使它的调用者知道要捕获这个异常。throw是具体向外抛异常的动作,所以它是抛出一个异常实例。 try、finally、catch 这三个关键字主要有下面几种组合方式try。。。catch、try。。。finally、try。。。catch。。。finally。 try。。。catch表示对某一段代码可能抛出异常进行的捕获,如下 staticvoidcacheException()throwsException{try{System。out。println(1);}catch(Exceptione){e。printStackTrace();}} try。。。finally表示对一段代码不管执行情况如何,都会走finally中的代码 staticvoidcacheException()throwsException{for(inti0;i5;i){System。out。println(enter:ii);try{System。out。println(execute:ii);}finally{System。out。println(leave:ii);}}} try。。。catch。。。finally也是一样的,表示对异常捕获后,再走finally中的代码逻辑。 JDK1。7使用try。。。with。。。resources优雅关闭资源 Java类库中有许多资源需要通过close方法进行关闭。比如InputStream、OutputStream,数据库连接对象Connection,MyBatis中的SqlSession会话等。作为开发人员经常会忽略掉资源的关闭方法,导致内存泄漏。 根据经验,tryfinally语句是确保资源会被关闭的最佳方法,就算异常或者返回也一样。trycatchfinally一般是这样来用的 staticStringfirstLineOfFile(Stringpath)throwsIOException{BufferedReaderbrnewBufferedReader(newFileReader(path));try{returnbr。readLine();}finally{br。close();}} 这样看起来代码还是比较整洁,但是当我们添加第二个需要关闭的资源的时候,就像下面这样 staticvoidcopy(Stringsrc,Stringdst)throwsException{InputStreamisnewFileInputStream(src);try{OutputStreamosnewFileOutputStream(dst);try{byte〔〕bufnewbyte〔100〕;while((nis。read())0){os。write(buf,n,0);}}finally{os。close();}}finally{is。close();}} 这样感觉这个方法已经变得臃肿起来了。 而且这种写法也存在诸多问题,即使tryfinally能够正确关闭资源,但是它不能阻止异常的抛出,因为try和finally块中都可能有异常的发生。 比如说你正在读取的时候硬盘损坏,这个时候你就无法读取文件和关闭资源了,此时会抛出两个异常。但是在这种情况下,第二个异常会抹掉第一个异常。在异常堆栈中也无法找到第一个异常的记录,怎么办,难道像这样来捕捉异常么? staticvoidtryThrowException(Stringpath)throwsException{BufferedReaderbrnewBufferedReader(newFileReader(path));try{Stringsbr。readLine();System。out。println(ss);}catch(Exceptione){e。printStackTrace();}finally{try{br。close();}catch(Exceptione){e。printStackTrace();}finally{br。close();}}} 这种写法,虽然能解决异常抛出的问题,但是各种trycathfinally的嵌套会让代码变得非常臃肿。 Java7中引入了trywithresources语句时,所有这些问题都能得到解决。要使用trywithresources语句,首先要实现AutoCloseable接口,此接口包含了单个返回的close方法。Java类库与三方类库中的许多类和接口,现在都实现或者扩展了AutoCloseable接口。如果编写了一个类,它代表的是必须关闭的资源,那么这个类应该实现AutoCloseable接口。 java引入了trywithresources声明,将trycatchfinally简化为trycatch,这其实是一种语法糖,在编译时会进行转化为trycatchfinally语句。 下面是使用trywithresources的第一个范例 使用trywithresources改写示例一parampathreturnthrowsIOExceptionstaticStringfirstLineOfFileAutoClose(Stringpath)throwsIOException{try(BufferedReaderbrnewBufferedReader(newFileReader(path))){returnbr。readLine();}} 使用trywithresources改写程序的第二个示例 staticvoidcopyAutoClose(Stringsrc,Stringdst)throwsIOException{try(InputStreaminnewFileInputStream(src);OutputStreamosnewFileOutputStream(dst)){byte〔〕bufnewbyte〔1000〕;while((nin。read(buf))0){os。write(buf,0,n);}}} 使用trywithresources不仅使代码变得通俗易懂,也更容易诊断。以firstLineOfFileAutoClose方法为例,如果调用readLine()和close()方法都抛出异常,后一个异常就会被禁止,以保留第一个异常。 异常处理的原则 我们在日常处理异常的代码中,应该遵循三个原则 不要捕获类似Exception之类的异常,而应该捕获类似特定的异常,比如InterruptedException,方便排查问题,而且也能够让其他人接手你的代码时,会减少骂你的次数。不要生吞异常。这是异常处理中要特别注重的事情,因为很可能会非常难以正常结束情况。如果我们不把异常抛出来,或者也没有输出到Logger日志中,程序可能会在后面以不可控的方式结束。不要在函数式编程中使用checkedException。什么是Error Error是程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时JVM(Java虚拟机)出现的问题。这些错误是不可检查的,因为它们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况,比如OutOfMemoryError和StackOverflowError异常的出现会有几种情况,这里需要先介绍一下Java内存模型JDK1。7。 其中包括两部分,由所有线程共享的数据区和线程隔离的数据区组成,在上面的Java内存模型中,只有程序计数器是不会发生OutOfMemoryError情况的区域,程序计数器控制着计算机指令的分支、循环、跳转、异常处理和线程恢复,并且程序计数器是每个线程私有的。 什么是线程私有:表示的就是各条线程之间互不影响,独立存储的内存区域。 如果应用程序执行的是Java方法,那么这个计数器记录的就是虚拟机字节码指令的地址;如果正在执行的是Native方法,这个计数器值则为空(Undefined)。 除了程序计数器外,其他区域:方法区(MethodArea)、虚拟机栈(VMStack)、本地方法栈(NativeMethodStack)和堆(Heap)都是可能发生OutOfMemoryError的区域。 虚拟机栈:如果线程请求的栈深度大于虚拟机栈所允许的深度,将会出现StackOverflowError异常;如果虚拟机动态扩展无法申请到足够的内存,将出现OutOfMemoryError。本地方法栈和虚拟机栈一样堆:Java堆可以处于物理上不连续,逻辑上连续,就像我们的磁盘空间一样,如果堆中没有内存完成实例分配,并且堆无法扩展时,将会抛出OutOfMemoryError。方法区:方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。一道经典的面试题 一道非常经典的面试题,NoClassDefFoundError和ClassNotFoundException有什么区别? 在类的加载过程中,JVM或者ClassLoader无法找到对应的类时,都可能会引起这两种异常错误,由于不同的ClassLoader会从不同的地方加载类,有时是错误的CLASSPATH类路径导致的这类错误,有时是某个库的jar包缺失引发这类错误。NoClassDefFoundError表示这个类在编译时期存在,但是在运行时却找不到此类,有时静态初始化块也会导致NoClassDefFoundError错误。 ClassLoader是类路径装载器,在Java中,类路径装载器一共有三种两类 一种是虚拟机自带的ClassLoader,分为三种 启动类加载器(Bootstrap),负责加载JAVAHOMEjrelibrt。jar扩展类加载器(Extension),负责加载JAVAHOMEjrelibext。jar应用程序类加载器(AppClassLoader),加载当前应用的classpath的所有类 第二种是用户自定义类加载器 Java。lang。ClassLoader的子类,用户可以定制类的加载方式。 另一方面,ClassNotFoundException与编译时期无关,当你尝试在运行时使用反射加载类时,ClassNotFoundException就会出现。 简而言之,ClassNotFoundException和NoClassDefFoundError都是由CLASSPATH中缺少类引起的,通常是由于缺少JAR文件而引起的,但是如果JVM认为应用运行时找不到相应的引用,就会抛出NoClassDefFoundError错误;当你在代码中显示的加载类比如Class。forName()调用时却没有找到相应的类,就会抛出java。lang。ClassNotFoundException。 NoClassDefFoundError是JVM引起的错误,是unchecked,未经检查的。因此不会使用trycatch或者finally语句块;另外,ClassNotFoundException是受检异常,因此需要trycatch语句块或者tryfinally语句块包围,否则会导致编译错误。调用Class。forName()、ClassLoader。findClass()和ClassLoader。loadClass()等方法时可能会引起java。lang。ClassNotFoundException,如图所示NoClassDefFoundError是链接错误,发生在链接阶段,当解析引用找不到对应的类,就会触发;而ClassNotFoundException是发生在运行时的异常。 文章参考: https:www。java67。com201212noclassdeffounderrorvsclassnotfoundexceptionjava。html 《极客时间Java核心技术36讲》 《深入理解Java虚拟机》第二版 《EffectiveJava第三版》 https:www。cnblogs。comxiohaop3547443。html https:blog。csdn。netqq29229567articledetails80773970 https:blog。csdn。netriemannarticledetails87522352 《Java编程思想》 https:www。cnblogs。comxz816111p8466048。html https:docs。oracle。comjavasespecsjlsse9htmljls11。htmljls11。1。1 jdk1。8源码注释