JAVA学习笔记——异常处理与调试
目录
- 错误与异常
- 异常分类
- 声明受查异常
- 抛出异常
- 创建异常类
- 异常捕获与处理
- 基本结构
- 再次抛出异常与异常链
- 带资源的 try 语句
- 使用异常机制的技巧
- 断言的使用
- 启用和禁用断言
- 使用断言完成参数检查
- 为文档假设使用断言
- 记录日志
- 使用方法
- 修改日志管理器配置
- 处理器、过滤器与格式化器
- 日志记录说明
错误与异常
任何程序都难免会遇到错误,可能是由于代码编写有错误,可能是输入数据中存在错误,也可能是其他的原因产生错误。错误的出现导致程序无法正常运行,对用户来说,更期望程序能够采用一些理智的行为。如果由于出现错误而使得某些操作没有完成,程序应该:
- 返回到一种安全状态,并能够让用户执行一些其他的命令
- 或者允许用户保存所有操作的结果,并以妥善的方式终止程序
在 Java 中,如果某个方法不能够采用正常的途径完整它的
任务,就可以通过另外一个路径退出方法。在这种情况下,方法并不返回任何值,而是抛出 (throw) 一个封装了错误信息的对象。需要注意的是,这个方法将会立刻退出,并不返回任何值。此外,调用这个方法的代码也将无法继续执行,取而代之的是,异常处理机制开始搜索能够处理这种异常状况的异常处理器 (exception handler)。
异常分类
在 Java 程序设计语言中,异常对象都是派生于 Throwable
类的一个实例。如果 Java 中内置的异常类不能够满足需求,用户可以创建自己的异常类。
图1 Java 中的异常层次结构
需要注意的是,所有的异常都是由 Throwable
继承而来,但在下一层立即分解为两个分支:Error
和 Exception
。
Error
类层次结构描述了 Java 运行时系统的内部错误和资源耗尽错误。应用程序不应该抛出这种类型的对象。如果出现了这样的内部错误,除了通告给用户,并尽力使程序安全地终止之外,再也无能为力了。
在设计 Java 程序时,需要关注 Exception
层次结构。这个层次结构又分解为两个分支:一个分支派生于 RuntimeException
;另一个分支包含其他异常。划分两个分支的规则是:由程序错误导致的异常属于 RuntimeException
;而程序本身没有问题,但由于像 I/O 错误这类问题导致的异常属于其他异常。
- 派生于
RuntimeException
类:- 错误地类型转换
- 数组访问越界
- 访问
null
指针
- 不是派生于
RuntimeException
类:- 试图在文件尾部后面读取数据
- 试图打开一个不存在的文件
- 试图根据给定的字符串查找
Class
对象,而这个字符串表示的类并不存在
如果出现 RuntimeException
异常,那么就一定是你的问题。应该通过检测数组下标是否越界来避免 ArraylndexOutOfBoundsException
异常;应该通过在
使用变量之前检测是否为 null
来杜绝 NullPointerException
异常的发生。
Java 语言规范将派生于 Error
类或 RuntimeException
类的所有异常称为非受查 (unchecked) 异常,所有其他的异常称为受查 (checked) 异常。编译器将核查是否为所有的受査异常提供了异常处理器。
声明受查异常
如果遇到了无法处理的情况,那么 Java 的方法可以抛出一个异常。一个方法不仅需要告诉编译器将要返回什么值,还要告诉编译器有可能发生什么错误。
在自己编写方法时,不必将所有可能抛出的异常都进行声明。至于什么时候需要在方法中用 throws
子句声明异常,什么异常必须使用 throws
子句声明,主要有以下 4 中情况:
- 调用一个抛出受査异常的方法,例如,
FilelnputStream
构造器。 - 程序运行过程中发现错误,并且利用
throw
语句抛出一个受查异常。 - 程序出现错误,例如
a[-1] = 0
会抛出一个ArraylndexOutOffloundsException
这样的非受查异常。 - Java 虚拟机和运行时库出现的内部错误。
对于那些可能被他人使用的 Java 方法,应该根据异常规范 (exception specification),在方法的首部声明这个方法可能抛出的异常。如果一个方法有可能抛出多个受查异常类型,那么就必须在方法的首部列出所有的异常类。每个异常类之间用逗号隔开。
class MyAnimation
{...public Image loadImage(String s) throws FileNotFoundException, EOFException{...}
}
不需要声明 Java 的内部错误,即从 Error
继承的错误。任何程序代码都具有抛出那些异常的潜能,而我们对其没有任何控制能力。同样,也不应该声明从 RuntimeException
继承的那些非受查异常。
总之,一个方法必须声明所有可能抛出的受查异常,而非受查异常要么不可控制 (Error),要么就应该避免发生 RuntimeException
。如果方法没有声明所有可能发生的受查异常,编译器就会发出一个错误消息。
抛出异常
一旦方法抛出了异常,这个方法就不可能返回到调用者。也就是说,不必为返回的默认值或错误代码担忧。对于一个已经存在的异常类,抛出异常过程:
- 找到一个合适的异常类
- 创建这个类的一个对象
- 将对象抛出
String readData(Scanner in) throws EOFException
{...while (...){if (in.hasNext()) // EOF encountered{if (n < len)throw new EOFException();}...}return s;
}
创建异常类
在程序中,可能会遇到任何标准异常类都没有能够充分地描述清楚的问题。在这种情况下,我们就需要创建自己的异常类。我们需要做的只是定义一个派生于 Exception
的类,或者派生于 Exception
子类的类。
习惯上,定义的类应该包含两个构造器,一个是默认的构造器;另一个是带有详细描述信息的构造器(超类 Throwable
的 toString
方法将会打印出这些详细信息,这在调试中非常有用)。
// example
class FileFormatException extends IOException
{public FileFormatException() {}public FileFormatException(String gripe){super(gripe);}
}
相关方法:
java.lang.Throwable
Throwable()
:构造一个新的Throwable
对象,这个对象没有详细的描述信息。Throwable(String message)
:构造一个新的throwable
对象,这个对象带有特定的详细描述信息。习惯上,所有派生的异常类都支持一个默认的构造器和一个带有详细描述信息的构造器。.getMessage()
:获得Throwable
对象的详细描述信息。
异常捕获与处理
基本结构
try
{//code that might throw exceptions...
}
catch(ExceptionClass1 e)
{// handlerfor this type...
}
catch(ExceptionClass2 e)
{// handlerfor this type...
}
finally
{// do something...
}
捕获异常一般基于以上结构:
try
块中为程序执行的代码块:如果在try
块中抛出异常,则停止运行剩余的所有代码,并根据抛出异常的类型进入catch
子句。catch
子句用于捕获特定的异常对象:其中变量e
引用该异常对象,可以调用e.getMessage()
方法获得该异常的详细信息。运行结束后,继续之后异常捕获块之后部分的代码。可以包含多个catch
子句,用于处理多种不同的异常类型。- 如果有
finally
子句:则无论try
块中是否产生异常(即try
全部执行完或catch
全部执行完),都要执行finally
子句。
通常,应该捕获那些知道如何处理的异常,而将那些不知道怎样处理的异常继续进行传递。如果想传递一个异常,就必须在方法的首部添加一个 throws
说明符,以便告知调用者这个方法可能会抛出异常。
如果编写一个覆盖超类的方法,而这个方法又没有抛出异常(如 JComponent
中的 paintComponent
),那么这个方法就必须捕获方法代码中出现的每一个受查异常。不允许在子类的 throws
说明符中出现超过超类方法所列出的异常类范围。
再次抛出异常与异常链
在catch
子句中可以抛出一个异常,这样做的目的是改变异常的类型。 如果开发了一个供其他程序员使用的子系统,那么,用于表示子系统故障的异常类型可能会产生多种解释。ServletException
就是这样一个异常类型的例子,执行 servlet 的代码可能不想知道发生错误的细节原因,但希望明确地知道 servlet 是否有问题。
try
{// access the database...
}
catch (SQLException e)
{Throwable se = new ServletException("database error");se.ini tCause(e);throw se;
}
当捕获到异常时,就可以使用这条语句重新得到原始异常:Throwable e = se.getCause();
。这样可以让用户抛出子系统中的高级异常,而不会丢失原始异常的细节。
带资源的 try 语句
// 方式 1
// open a resource
try
{// work with the resource
}
finally
{// close the resource
}// 方式 2
try(Resource res)
{// work with res
}
当 try
语句对资源进行操作时,之后不再使用资源,可以使用以上两种方式对资源进行关闭:
- 如方式1,使用
finally
语句,在语句块中关闭资源 - 如方式2,直接在
try
关键字后指明打开的资源,在语句结束时,系统将自动关闭资源
相关方法:
java.lang.Throwable
Throwable(Throwable cause)
/Throwable(String message, Throwable cause)
:用给定的 “原因” 构造一个Throwable
对象。Throwable initCause(Throwable cause)
:将这个对象设置为 “原因”。如果这个对象已经被设置为 “原因”,则抛出一个异常。返回 this 引用。.getCause()
:获得设置为这个对象的 “原因” 的异常对象。如果没有设置 “原因”,则返回null
。.getStackTrace()
:获得构造这个对象时调用堆栈的跟踪。.addSuppressed(Throwable t)
:为这个异常增加一个 “抑制” 异常。这出现在带资源的try
语句中,其中t
是close
方法抛出的一个异常。getSuppressed()
:得到这个异常的所有 “抑制” 异常。一般来说,这些是带资源的try
语句中close
方法拋出的异常。
java.lang.Exception(Throwable cause)
/java.lang.Exception(String message, Throwable cause)
:用给定的 “原因” 构造一个异常对象。java.lang.RuntimeException(Throwable cause)
/java.lang.RuntimeException(String message, Throwable cause)
:用给定的 “原因” 构造一个RuntimeException
对象。
使用异常机制的技巧
- 只在异常情况下使用异常机制,异常处理不能代替简单的测试
- 不要过分地细化异常
- 利用异常层次结构:① 不要只抛出
RuntimeException
异常,应该寻找更加适当的子类或创建自己的异常类;② 不要只捕获Thowable
异常,否则,会使程序代码更难读、更难维护;③ 将一种异常转换成另一种更加适合的异常时不要犹豫。 - 不要压制异常
- 在检测错误时,“苛刻” 要比放任更好
- 不要羞于传递异常
- (5) 和 (6) 可以归纳为:“早抛出,晚捕获”。
断言的使用
在一个具有自我保护能力的程序中,断言很常用。断言机制允许在测试期间向代码中插入一些检査语句,方便快速验证程序的某些假设或状态。当代码发布时,这些插人的检测语句将会被自动地移走。
Java 语言引人了关键字 assert
来表示 “断言”。这个关键字有两种形式:
assert 条件;// 后续程序
assert 条件 : 表达式;// 后续程序
这两种形式都会对条件进行检测,如果结果为 false
,则抛出一个 AssertionError
异常。在第二种形式中,表达式将被传人 AssertionError
的构造器,并转换成一个消息字符串。
启用和禁用断言
在默认情况下,断言被禁用。可以在运行程序时用 -enableassertions
或 -ea
选项启用:
java -enableassertions MyApp
需要注意的是,在启用或禁用断言时不必重新编译程序。启用或禁用断言是类加载器 (class loader) 的功能。当断言被禁用时,类加载器将跳过断言代码,因此,不会降低程序运行的速度。
也可以在某个类或整个包中使用断言,例如:
java -ea:MyClass -ea:com.mycompany.mylib... MyApp
也可以用选项 -disableassertions
或 -da
禁用某个特定类和包的断言:
java -ea:... -da:MyClass MyApp
使用断言完成参数检查
在 Java 语言中,给出了 3 种处理系统错误的机制:
- 抛出一个异常
- 日志
- 使用断言
在以下两种情况下使用断言:
- 断言失败是致命的、不可恢复的错误。
- 断言检查只用于开发和测阶段。
因此,不应该使用断言向程序的其他部分通告发生了可恢复性的错误,或者,不应该作为程序向用户通告问题的手段。断言只应该用于在测试阶段确定程序内部的错误位置。
为文档假设使用断言
很多程序员使用注释说明假设条件,看以下示例:
// 使用注释说明假设条件
if (i % 3 == 0) {...
} else if (i % 3 == 1) {...
} else { // We know (i % 3 == 2)...
}// 使用断言
if (i % 3 == 0) {...
} else if (i % 3 == 1) {...
} else {assert i % 3 == 2 : i;...
}
显然,如果 i
为正数,则 i % 3
的值不是0、1,则必然是 2。但如果 i
为负数,则余数还有可能是 -1、-2。第二种使用断言的方法,有可能帮我们发现这个潜在的错误。一般来说,这种情况我们都认为 i >= 0
,因此,如果在 if
前断言 assert i >= 0;
则会更好。这个示例说明了程序员如何使用断言来进行自我检查。
记录日志
在调试程序时,我们时长习惯于插入 System.out.println()
方法来查找问题,解决后再将其删除,又出现问题时,则又需要进行插入。
日志记录是一种在程序的整个生命周期都可以使用的策略性工具,记录日志 API 就是为了解决这个问题而设计的,它具有如下优点:
- 可以很容易地取消全部日志记录,或者仅仅取消某个级别的日志,而且打开和关闭这个操作也很容易。
- 可以很简单地禁止日志记录的输出,因此,将这些日志代码留在程序中的开销很小。
- 日志记录可以被定向到不同的处理器,用于在控制台中显示,用于存储在文件中等。
- 日志记录器和处理器都可以对记录进行过滤,过滤器可以根据过滤实现器制定的标准丢弃那些无用的记录项。
- 日志记录可以采用不同的方式格式化,例如,纯文本或 XML。
- 应用程序可以使用多个日志记录器, 它们使用类似包名的这种具有层次结构的名字,例如,
com.mycompany.myapp()
。 - 在默认情况下,日志系统的配置由配置文件控制。如果需要的话,应用程序可以替换这个配置。
使用方法
// 简单使用方法
// 生成简单日志记录 (global logger)
Logger.getGlobal().info("File->Open menu item selected");
/*May 10, 2013 10:12:15 PM LoggingImageViewer fileOpenINFO: File->Open menu item selected
*/// 取消所有日志
Logger.getGlobal().setLevel(Level.OFF);// 高级用法
// 创建或获取记录器
private static final Logger myLogger = Logger.getLogger("com.mycompany.myapp");// 设置日志级别
myLogger.setLevel(Level.FINE);
myLogger.setLevel(Level.ALL); // 开启所有级别日志// 日志记录方法
myLogger.warning(message);
myLogger.log(Level.FINE, message);// 流程跟踪方法
/*void entering(String className, String methodName);void entering(String className, String methodName, Object param);void entering(String className, String methodName, Object[] params);void exiting(String className, String methodName);void exiting(String className, String methodName, Object result);*/// 这些调用将生成 FINER 级别和以字符串 ENTRY 和 RETURN 开始的日志记录。// 记录异常try
{...
}
catch (IOException e)
{Logger.getLogger("com.mycompany.myapp").log(Level.WARNING , "Reading image", e);
}
// 调用 throwing 可以记录一条 FINER 级别的记录和一条以 THROW 开始的信息。
Java 日志记录器通常有 7 个级别,一般默认开启前三个,其他级别可以自行设置。
- SEVERE(严重)
- WARNING(警告)
- INFO(信息)
- CONFIG(配置)
- FINE(精细)
- FINER(更精细)
- FINEST(最精细)
注意:未被任何变量引用的日志记录器可能会被垃圾回收。为了防止这种情况发生,要像上面的例子中一样,用一个静态变量存储日志记录器的一个引用。
修改日志管理器配置
可以通过编辑配置文件来修改日志系统的各种属性。在默认情况下,配置文件存在于:
jre/lib/1ogging.properties
要想使用另一个配置文件,就要将 java.utiUogging.config.file
特性设置为配置文件的存储位置,并用下列命令启动应用程序:
java -Djava.util.logging.config.file=<configFile MainClass>
处理器、过滤器与格式化器
处理器:在默认情况下,日志记录器将记录发送到 ConsoleHandler
中,并由它输出到 System.err
流中。特别是,日志记录器还会将记录发送到父处理器中,而最终的处理器(命名为“”)有一个 ConsoleHandler
。
过滤器:在默认情况下,过滤器根据日志记录的级别进行过滤。每个日志记录器和处理器都可以有一个可选的过滤器来完成附加的过滤。另外,可以通过实现 Filter
接口并定义下列方法来自定义过滤器。
格式化器:ConsoleHandler 类和 FileHandler
类可以生成文本和 XML 格式的日志记录。但是,也可以自定义格式。这需要扩展 Formatter
类并覆盖方法 String formatMessage(LogRecord record)
。
该部分的详细介绍,参考《Java核心技术 卷1》第 7 章对应小节,不在这里展开。
日志记录说明
- 为一个简单的应用程序,选择一个日志记录器,并把日志记录器命名为与主应用程序包一样的名字,例如,
com.mycompany.myprog
,这是一种好的编程习惯。 - ) 默认的日志配置将级别等于或高于 INFO 级别的所有消息记录到控制台。用户可以覆盖默认的配置文件。但是改变日志配置工作过于复杂,因此最好在应用程序中安装一个更加适宜的默认配置。
- 所有级别为 INFO、WARNING 和 SEVERE 的消息都将显示到控制台上。因此,最好只将对程序用户有意义的消息设置为这几个级别。将程序员想要的日志记录,设定为 FINE 是一个很好的选择。
- 日志相关方法参考 java.util.logging
参考资料:
- 《Java核心技术 卷1 基础知识》
JAVA学习笔记——异常处理与调试相关推荐
- (JAVA学习笔记) 异常处理
文章目录 什么是异常 异常分类 异常体系结构 Error Exception Error和Exception的区别: 异常处理机制 代码演示 自定义异常 代码演示 什么是异常 异常指程序运行中出现的不 ...
- Java学习笔记 --- 异常处理
一.基本介绍 异常处理就是当异常发生时,对异常处理的方式 二.异常处理的方式 1.try - catch - finally 程序员在代码中捕获发生的异常,自行处理 处理机制示意: try {代码// ...
- Java学习笔记——异常处理
目录 一.异常 (一)异常的相关概念 (二)异常产生的原因 (三)异常的分类 二.Exception (一)检查性异常 (二)非检查性异常 (三) 异常方法 三.Java处理异常的关键字 (一)捕获异 ...
- 云计算学习笔记---异常处理---hadoop问题处理ERROR org.apache.hadoop.hdfs.server.datanode.DataNode: java.lang.NullPoin
云计算学习笔记---异常处理---hadoop问题处理ERROR org.apache.hadoop.hdfs.server.datanode.DataNode: java.lang.NullPoin ...
- 《Java学习笔记(第8版)》学习指导
<Java学习笔记(第8版)>学习指导 目录 图书简况 学习指导 第一章 Java平台概论 第二章 从JDK到IDE 第三章 基础语法 第四章 认识对象 第五章 对象封装 第六章 继承与多 ...
- Java 学习笔记:第一章 Java入门
Java 学习笔记:第一章 Java入门 1.1 计算机语言发展史以及未来方向 1.2 常见编程语言介绍 C语言 C++ 语言 Java语言 PHP 语言 Object-C和Swift 语言 Java ...
- java学习笔记13--反射机制与动态代理
本文地址:http://www.cnblogs.com/archimedes/p/java-study-note13.html,转载请注明源地址. Java的反射机制 在Java运行时环境中,对于任意 ...
- java学习笔记12--异常处理
java学习笔记系列: java学习笔记11--集合总结 java学习笔记10--泛型总结 java学习笔记9--内部类总结 java学习笔记8--接口总结 java学习笔记7--抽象类与抽象方法 j ...
- java学习笔记15--多线程编程基础2
本文地址:http://www.cnblogs.com/archimedes/p/java-study-note15.html,转载请注明源地址. 线程的生命周期 1.线程的生命周期 线程从产生到消亡 ...
最新文章
- python怎么安装第三方库-怎样安装Python的第三方库
- 随机森林算法4种实现方法对比测试:DolphinDB速度最快,XGBoost表现最差
- 2020最详细安装Ubuntu指南
- 前端学习(2379):加入git管理
- 华为郭平:很愿意使用高通芯片制造手机
- “System.AccessViolationException”类型的未经处理的异常在 System.Data.dll 中发生
- HFSS脚本建模入门
- python信号处理加汉明窗_Python学习-Scipy库信号处理signal(过滤、快速傅里叶变换、信号窗函数、卷积)...
- 电商平台后台管理系统--->系统详细设计(用户登录、商品管理模块)
- php环境安装Java_常用PHP运行环境一键安装包推荐
- EFF 测试 Privacy Badger,禁止第三方跟踪
- 小米手机、一加手机、华为手机、小米手环NFC刷门禁卡,全教程!
- Win10笔记本WIFI的标志突然变成了一个地球的解决方案(二)
- win7显示器双屏显示怎么设置
- 超全面的前端切图技巧,读这篇就够了
- 【终端快捷键】Linux terminal 终端常用快捷键
- 网站降权根服务器有关系吗,导致网站降权或被k的原因有哪些?
- 2021Java高级面试题,剖析Java开发未来的出路在哪里
- 战舰少女rcffff服务器维护,战舰少女R4月29日临时维护公告 2-5配置错误修正
- [搜索] Solr (三) 全量索引与增量索引