1.处理错误
假设在一个Java程序运行期间出现了一个错误,这个错误可能是由于文件包含错误信息,或者网络连接出现问题造成的,也可能是因为使用了无效的数组下标,也可能是试图使用一个没有被赋值的对象引用而造成的。用户期望在出现错误时,程序能够采取合理的行为。如果由于出现错误而使得某些操作没有完成,程序应该:
·返回到一种安全的状态,并能够让用户执行其他的命令
·允许用户保存所有工作的结果,并以妥善的方式终止程序
为了能够处理程序中的异常情况,必须考虑到程序中可能会出现的错误和问题,那么需要考虑以下几个问题:
1.用户输入错误:除了那些不可避免的键盘输入错误外,有些用户偏偏不喜欢按照规定的输入进行输入,而是随心所欲喜欢自行其是。
2.设备错误:硬件不可能无时无刻都保持着正确性,硬件不可能宗师让它做什么它就做什么。举一个简单的例子,比如说打印机没有纸了。
3.物理限制:比如说磁盘已经满了,你可能没有已经用尽了所有可用的存储空间。
4.代码错误:程序方法有可能没有正确的完成工作。比如方法返回了一个错误的答案,或者错误的调用了其他方法。也可能是计算一个无效的数组索引,散列表中查找一个不存在的记录等等。
对于方法中的错误,传统的做法是返回一个特殊的错误码,由调用方法分析。但是并不是所有的情况都可能返回错误码的,异常有自己的语法和特定的继承层次结构。
2.异常分类
上图是Java异常层次结构的一个简化示意图。
所有的异常都是由Throwable继承而来,但在下一层立即分解为两个分支:Error和Exception
Error类层次结构描述了Java运行时系统的内部错误和资源耗尽错误。程序不应该抛出这种类型的对象。如果出现了这样的内部错误,除了通知用户,并尽力妥善地终止程序之外,无能为力,但是这种情况比较少见。
Exception层次结构要格外注意,这个层次机构又分解为两个分支:一个分支派生于RuntimeExcepion;另一个分支包含其他异常。一般规则是:由编程错误导致的异常属于RuntimelyException;如果程序本身没有问题,但由于像I/O错误这类问题导致的异常属于其他异常。
派生于RuntimeException的异常包括以下问题:错误的强制类型转换、数组访问越界、访问Null指针。
不是派生于RuntimeException的异常包括:试图超越文件末尾继续读取数据、试图打开一个不存在的文件、试图根据给定的字符串查找Class对象,而这个字符串表示的类并不存在。
Java规定:
-
必须捕获的异常,包括
Exception
及其子类,但不包括RuntimeException
及其子类,这种类型的异常称为Checked Exception。 -
不需要捕获的异常,包括
Error
及其子类,RuntimeException
及其子类。
捕获异常
捕获异常使用try...catch
语句,把可能发生异常的代码放到try {...}
中,然后使用catch
捕获对应的Exception
及其子类:
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
byte[] bs = toGBK("中文");
System.out.println(Arrays.toString(bs));
}
static byte[] toGBK(String s) {
try {
// 用指定编码转换String为byte[]:
return s.getBytes("GBK");
} catch (UnsupportedEncodingException e) {
// 如果系统不支持GBK编码,会捕获到UnsupportedEncodingException:
System.out.println(e); // 打印异常信息
return s.getBytes(); // 尝试使用用默认编码
}
}
}
如果我们不捕获UnsupportedEncodingException
,会出现编译失败的问题:
编译器会报错,错误信息类似:unreported exception UnsupportedEncodingException; must be caught or declared to be thrown,并且准确地指出需要捕获的语句是return s.getBytes("GBK");
。意思是说,像UnsupportedEncodingException
这样的Checked Exception,必须被捕获。
这是因为String.getBytes(String)
方法定义是:
public byte[] getBytes(String charsetName) throws UnsupportedEncodingException {
...
}
在方法定义的时候,使用throws Xxx
表示该方法可能抛出的异常类型。调用方在调用的时候,必须强制捕获这些异常,否则编译器会报错。
在toGBK()
方法中,因为调用了String.getBytes(String)
方法,就必须捕获UnsupportedEncodingException
。我们也可以不捕获它,而是在方法定义处用throws表示toGBK()
方法可能会抛出UnsupportedEncodingException
,就可以让toGBK()
方法通过编译器检查:
// try...catch
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
byte[] bs = toGBK("中文");
System.out.println(Arrays.toString(bs));
}
static byte[] toGBK(String s) throws UnsupportedEncodingException {
return s.getBytes("GBK");
}
}
上述代码仍然会得到编译错误,但这一次,编译器提示的不是调用return s.getBytes("GBK");
的问题,而是byte[] bs = toGBK("中文");
。因为在main()
方法中,调用toGBK()
,没有捕获它声明的可能抛出的UnsupportedEncodingException
。
修复方法是在main()
方法中捕获异常并处理
捕获异常
在Java中,凡是可能抛出异常的语句,都可以用try ... catch
捕获。把可能发生异常的语句放在try { ... }
中,然后使用catch
捕获对应的Exception
及其子类。
多catch语句
可以使用多个catch
语句,每个catch
分别捕获对应的Exception
及其子类。JVM在捕获到异常后,会从上到下匹配catch
语句,匹配到某个catch
后,执行catch
代码块,然后不再继续匹配。
简单地说就是:多个catch
语句只有一个能被执行.
抛出异常
异常的传播
当某个方法抛出了异常时,如果当前方法没有捕获异常,异常就会被抛到上层调用方法,直到遇到某个try ... catch
被捕获为止
Java断言
断言(assert)是Java中的一条语句。它可以用来测试您对该程序的假设。
在执行断言时,它被认为是正确的。如果失败,JVM将抛出一个名为AssertionError的异常。它主要用于测试目的。
Java断言提供了一种有效的方法来检测和纠正编程错误。
有两种使用断言的方法。
第一种方法是:
assert 表达式;
第二种方法是:
assert 表达式1:表达式2;
前置条件和后置条件
前置条件: 是在执行某些代码之前必须求值为 true 的条件
后置条件: 是在执行某些代码后必须求值为 true 的条件
assert关键字用法简单,但是使用assert往往会让你陷入越来越深的陷阱中。应避免使用。笔者经过研究,总结了以下原因:
1、 assert关键字需要在运行时候显式开启才能生效,否则你的断言就没有任何意义。而现在主流的Java IDE工具默认都没有开启-ea断言检查功能。这就意味着你如果使用IDE工具编码,调试运行时候会有一定的麻烦。并且,对于Java Web应用,程序代码都是部署在容器里面,你没法直接去控制程序的运行,如果一定要开启-ea的开关,则需要更改Web容器的运行配置参数。这对程序的移 植和部署都带来很大的不便。
2、用assert代替if是陷阱之二。assert的判断和if语句差不多,但两者的作用有着本质的区别:assert关键字本意上是为测试 调试程序时使用的,但如果不小心用assert来控制了程序的业务流程,那在测试调试结束后去掉assert关键字就意味着修改了程序的正常的逻辑。
3、assert断言失败将面临程序的退出。这在一个生产环境下的应用是绝不能容忍的。一般都是通过异常处理来解决程序中潜在的错误。但是使用断言就很危险,一旦失败系统就挂了。
日志作用
日志记录了系统行为的时间、地点、状态等相关信息,能够帮助我们了解并监控系统状态,在发生错误或者接近某种危险状态时能够及时提醒我们处理,同时在系统产生问题时,能够帮助我们快速的定位、诊断并解决问题。
Java中常用日志框架
在Java程序中常用日志框架可以分为两类:
-
无具体实现的抽象门面框架,如:Commons Logging、SLF4J
-
具体实现的框架,如:Log4j,Log4j 2,Logback,Jul
java常用日志框架关系
-
Log4j 2与Log4j 1都是Apache旗下的日志框架,Log4j 2与Log4j 1发生了很大的变化,Log4j 2不兼容Log4j 1。
-
Commons Logging和Slf4j是日志门面(门面模式是软件工程中常用的一种软件设计模式,也被称为正面模式、外观模式。它为子系统中的一组接口提供一个统一的高层接口,使得子系统更容易使用)。Log4j和Logback则是具体的日志实现方案。可以简单的理解为接口与接口的实现,调用者只需要关注接口而无需关注具体的实现,做到解耦。
-
比较常用的组合使用方式是Slf4j与Logback组合使用,Commons Logging与Log4j组合使用。
-
Logback必须配合Slf4j使用。由于Logback和Slf4j是同一个作者,其兼容性不言而喻。
JDK自带了logging
,log4j1
、log4j2
、logback
,这些框架都自己定制了日志 API ,并且有相应的实现;目前用于实现日志统一的框架 Apache commons-logging
、slf4j
,遵循面向接口编程的原则,这两大框架可以让用户在程序运行期间去选择具体的日志实现系统(log4j1\log4j2\logback
等)来记录日志,是统一抽象出来的一些接口。
通常有以下七种日志级别:
SEVERE WARNING INFO CONFIG FINE FINER FINEST
日志技巧:
1.对于一个简单的应用,选择一个日志记录器,可以把日志记录器命名为与主应用包一样的名字,为了方便起见,可以为有大量日志记录活动的类增加静态字段。
2.默认的日志配置会把级别等于或高于INFO的所有消息记录到后台,用户可以覆盖这个默认配置,但是改变配置的过程有些复杂。因此,最好在你的应用中安装一个更合适的默认配置。
3.需要牢记:所有级别为INFO WARNING SEVERE的消息都将显示到控制台上。因此,最好只将对程序用户有意义的消息设置为这几个急别,将程序员想要的日志消息设定为FINE急别是一个很好的选择。