Java中的异常、断言和日志

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自带了logginglog4j1log4j2logback ,这些框架都自己定制了日志 API ,并且有相应的实现;目前用于实现日志统一的框架 Apache commons-loggingslf4j ,遵循面向接口编程的原则,这两大框架可以让用户在程序运行期间去选择具体的日志实现系统(log4j1\log4j2\logback等)来记录日志,是统一抽象出来的一些接口。

通常有以下七种日志级别:

SEVERE WARNING INFO CONFIG FINE FINER FINEST

日志技巧:

1.对于一个简单的应用,选择一个日志记录器,可以把日志记录器命名为与主应用包一样的名字,为了方便起见,可以为有大量日志记录活动的类增加静态字段。

2.默认的日志配置会把级别等于或高于INFO的所有消息记录到后台,用户可以覆盖这个默认配置,但是改变配置的过程有些复杂。因此,最好在你的应用中安装一个更合适的默认配置。

3.需要牢记:所有级别为INFO WARNING SEVERE的消息都将显示到控制台上。因此,最好只将对程序用户有意义的消息设置为这几个急别,将程序员想要的日志消息设定为FINE急别是一个很好的选择。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值