学习《Java核心技术》——第7章:异常、断言和日志

学习《Java核心技术》——第7章:异常、断言和日志

Java核心技术.卷I(第11版)

第7章:异常、断言和日志

  • 处理错误
  • 捕获异常
  • 使用异常的技巧
  • 使用断言
  • 日志
  • 调试技巧

1.处理错误

异常分类

异常对象都是派生于Throwable类的一个类实例

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QOnIE6Ni-1662469433341)(images/image-20220905201632403.png)]

Error:Java运行时系统的内部错误和资源耗尽错误

Exception分为:

  • RuntimeException包括:(一般是由编程错误导致的异常,这个取决于你代码的操作,也就是可以通过代码规避)

    • 错误的强制类型转换

    • 数组越界访问

    • 访问null指针

  • 其他异常(如IO异常,这个取决于环境,也就是通过代码也规避不了,比如检查文件存在后文件就删了,后面用到这个文件就报错)

    • 试图超越文件末尾继续读取数据

    • 试图打开一个不存在的文件

    • 试图根据指定字符串查找Class对象,但这个字符串表示的类,不存在。

非检查型(unchecked)异常:派生于Error类或RuntimeException类的所有异常。为啥这样分???

检查型(checked)异常:所有其他的异常,如IO异常

声明检查型异常

使用throws xxxException, xxxException1声明,不需要声明Error,控制不了。

一个方法必须声明所有可能抛出的检查型异常,否则编译不通过。

子类覆盖父类方法,抛出的异常不能比父类的范围大,也可以不抛出异常;如果父类没有抛出任何检查型异常,那子类也不能抛出任何检查型异常。

如何抛出异常

String readDate() throws EOFException{
    throw new EOFException();
}
  • 合适的异常类
  • 创建异常类对象
  • 将对象抛出

创建异常类

​ 继承Exception的某个子类,习惯做法是,一个默认构造器,一个包含详细描述信息的构造器

2.捕获异常

不捕获异常,发生异常后程序就会终止。

1.可以使用try/catch语句块

try{
    ...;
}catch(ExceptionType e){
    //处理异常
}

2.也可以将异常传递给调用者

public void read() throws IOException{
	code;
}

一般而言,捕获知道要怎么处理的异常,继续传播不知道具体如何处理的异常。

需要注意的是,覆盖父类方法时,子类方法抛出的异常范围不能超过父类方法。如果父类方法不抛出异常,此时只能使用try/catch语句捕获异常,不能使用throws声明异常。

捕获多个异常

try{
    code;
}catch(XXXException e){
    //处理异常1
}catch(XXXException e){
    //处理异常2
}catch(XXX3Exception | XXX4Exception e){
    //处理异常3|4
    //同一个catch同时捕获多个异常类型
}catch(XXXException e){
    //处理异常5
}

异常变量e,隐含为final变量。

再次抛出异常和异常链

try{
    
}catch(XXXException e){
    throw new XXXException();
}

更好的更推荐的办法,使用包装技术抛出异常,把原始异常设置为新异常的"原因":待具体

try{
    
}catch(XXX1Exception original){
    var e = new XXX2Exception();
    e.initCause(original);
    throw e;
}

finally子句

一般用于对资源的清理(处理)

try{}catch(XXXexception e){}finally{}

不管是否捕获异常,finally子句中的代码都会被执行。

更实用的try-with-resources语句

也可以,只使用try/finally来保证资源的正常处理

嵌套结构,内部处理资源,外部捕获异常,内外分明

//嵌套的结构
try{
    try{
        
    }finally{
        
    }
}catch(XXXException e){

}

注意,如果finally子句包含return语句时,不管是抛出异常,还是方法返回一个结果对象,都会在这之前执行finally里的语句,此时finally里的return语句会覆盖原有的结果!

因此,finally子句的体一般用于清理资源。不要把改变控制流的语句(return,throw,break,continue)放在finally子句中

try-with-Resources语句

如果资源属于一个实现了AutoCloseable接口的类,用这个语句会在try块退出时,自动关闭资源。

AutoCloseable接口有一个方法:void close() throws Exception

还有一个Closeable接口,是AutoCloseable的子接口,也只包含一个close方法,但是该方法声明为抛出一个IOException。

语法

try(Resource res = ...){
    work with res
}//try块退出时(正常执行完或者遇到异常),会自动调用res.close()

后面也可以跟catch、finally子句

分析堆栈轨迹(stack trace)元素

Throwable类的printStackTrace方法

var t = new Throwable();
var out = new StringWriter();
t.printStackTrace(new PrintWriter(out));
string description = out.tostring();

更灵活的方式,使用StackWalker类

它会生成一个和StackWalker.StackFrame实例流,其中每个实例分别描述一个栈帧(stack frame)

StackWalker walker = StackWalker.getInstance();
walker.forEach(frame -> analyze frame);
//以懒方式处理Stream<StackWalker.StackFrame>,可以调用:
walker.walker(frame -> process stream);

利用这个类的一些方法,可以得到执行代码行的文件名和行号,类对象和方法名。toString方法包含这些信息。

3.使用异常的技巧

  • 异常处理不能代替简单的测试,只在异常情况下使用异常

    捕获异常花费时间巨大

  • 不要过分细化异常,将正确处理和错误处理分开就行

    比如保险起见,每条语句都用try/catch。这样代码量激增,且意义不大。

  • 充分利用异常层次结构

    指使用更加具体意义更加明确的异常

  • 不要压制异常

    有异常就要处理,即使可能性特别小,后果就是一旦异常不处理,就会终止程序

  • 在检测错误时,要严格一些

    在出错的地方,就抛出异常提示,比如pop一个空栈,应该抛出EmptyStackException,而不是返回null。

  • 不要害怕传递异常

    有利于向更高层次通知错误,向用户报告错误

4.使用断言

断言机制允许在测试期间向代码中插入一些检查,而在生产代码中会自动删除这些检查

assert condition;
assert condition : expression;

计算条件,如果false,则抛出一个AssertionError异常。

expression传入AssertionError对象的构造器,转换成一个消息字符串。

但是,AssertionError对象并不保存表达式的值。否则很容易让程序员尝试从断言中回复程序的运行,违背了断言的初衷。

启用和禁用断言:(属于类加载器的功能,不需要重新编译)

启用:在运行程序时用-enablearrertions-ea选项启动断言,即

java -enableassertions MyApp

禁用:使用disableassertions-da在某个特定类和包中禁用断言

由于,有的类不是由类加载器加载,而是直接由虚拟机加载,比如一些“系统类”,需要使用-enablesystemassertions/-esa来开关启动断言。

也可以通过编程来控制类加载器的断言状态。

在idea中怎么使用断言

  • edit configuration
  • Modify options
  • add VM options

使用断言完成参数检查

在java中,有3种处理系统错误的机制:

  • 抛出一个异常
  • 日志
  • 使用断言

使用断言的情况:(用于开发、测试阶段检查错误

  • 断言失败是致命的、不可恢复的错误
  • 断言检查只是在开发和测试阶段打开,用于确定程序内部错误的位置

比如:规定方法的参数不能为空,则在方法开头,可以使用断言检查参数是否为空,这也叫前置条件(Precondition),不满足前置条件,就会触发断言。

使用断言提供假设文档

。。。。

就一些if/else if/else,不能穷举,所以会在最后的else后跟上行注释。

这样的情况可以的使用assert断言来检查,也相当于是注释了。

5.日志

类似sout输出信息调试,但是sout需要后面再注释掉。

建议使用日志API用于调试,很方便。

优点(原文):

  • 可以很容易地取消全部日志记录,或者仅仅取消某个级别以下的日志,而且可以很容易地再次打开日志开关。

  • 可以很简单地禁止日志记录,因此,将这些日志代码留在程序中的开销很小。

  • 日志记录可以被定向到不同的处理器,如在控制台显示、写至文件,等等。

  • 日志记录器和处理器都可以对记录进行过滤。过滤器可以根据过滤器实现器指定的标准丢弃那些无用的记录项。

  • 日志记录可以采用不同的方式格式化,例如,纯文本或XML

  • 应用程序可以使用多个日志记录器,它们使用与包名类似的有层次结构的名字,例如,com.mycompany.myapp。

  • 日志系统的配置由配置文件控制。

比如日志框架Log4J

基本日志,Logger:

全局日志记录器Logger.getGlobal()

Logger.getGlobal().info("log info"); //打印记录
Logger.getGlobal().setLevel(Level.OFF);//禁用

高级日志

就是拆分全局记录器,建立自己的日志记录器。

private static final Logger myLogger = logger.getLogger("com.xxx.xxx");

使用静态变量存储日志记录器的引用,防止没有引用没有被使用而被垃圾回收。

日志的7个级别

  • SEVERE
  • WARNING
  • INFO
  • CONFIG
  • FINE
  • FINER
  • FINEST

也可以自己设置新的级别logger.setLevel(Level.Fine)

logger.warining(msg);//设置warning级别日志
logger.fine(msg);
logger.log(Level.FINE, msg);//设置日志

具体看API

修改日志管理器配置

配置文件默认在:conf/logging.propertiesjre/lib/logging.properties(Java9之前)

日志记录器并不会把消息发送到控制台,那是处理器的任务,可以这样设置将消息打印到控制台:

java.util.logging.console Handler.level=FINE

日志管理器在虚拟机启动时初始化,也就是在main方法执行前。

本地化

资源包(resource bundle),包括一组映射,分别对应各个本地化环境。

可以有多个资源包,不同包用于不同部分。

ResourceBundle类管理资源包

处理器

  • ConsoleHandler

  • FileHandler

    将记录收集到文件中

  • SocketHandler

    将记录发送到指定的主机和端口

处理器也有日志级别。因此,在配置文件里,不光要修改日志记录器的级别也要修改处理器级别。

过滤器

每个日志记录器和处理器都有一个可选的过滤器来完成附加的过滤。

实现Filter接口并定义boolean isLoggable(LogRecord record)

格式化器

将日志记录以一定格式输出

ConsoleHandler类和FileHandler类可以生成文本XML格式的日志记录。

扩展Formatter类并覆盖String format(LogRecord record)方法

日志技巧

  • 简单的应用,使用一个日志记录器就行
  • 默认的日志配置会把等级等于或高于INFO的所有消息记录到控制台
  • 所有级别为INFO、WARNING和SERVER的消息都将显示到控制台

。。。

6.调试技巧

  • sout
  • 在类里,psvm,测试这个类
  • JUnit(http://junit.org),单元测试框架
  • 日子代理(logging proxy),是一个子类的对象,可以解惑方法调用,记录日志然后调用父类的方法。
  • 利用Throwable类的printStackTrace方法,可以从任意的异常对象获得堆栈轨迹。
  • 将程序错误记录到文件中,
    • 捕获错误流:java myApp 2> err.txt
    • 同时捕获System.err和system.out:java myApp 1> errors.txt 2>&1
  • 用静态方法Thread.setDefaultUncaughtExceptionHandler改变未捕获异常的处理器==?==
  • 观察类的加载过程,启动Java虚拟机时可以使用-verbose标志。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值