Java核心技术·卷一·第七章笔记

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

错误无处不在,我们希望程序能在遇到错误时,通过错误处理器,返回一种安全状态,或者妥善的保存用户数据然后退出,由此保证程序的健壮性。

7.1.1 异常分类

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

大致继承层次如上。但是并非不能直接继承自Throwable。

Error层次结构描述系统级别的内部错误或者资源耗尽错误,程序员无法抛出此类错误,而且也无法处理。

RuntimeException:由编程错误导致的异常

IOException:由I/O错误导致的异常

非检查型异常:派生自Error类(在我们的控制之外)或者RuntimeException类(一开始编写时就应该避免的,例如数组越界)

非检查型异常:其他所有

7.1.2 声明检查型异常

抛出异常的情况:

  • 调用了一个抛出检查型异常的方法
  • 检测到一个错误,并利用throw语句抛出一个检查型异常
  • 程序出现错误(例如a[-1]会抛出一个非检查型异常)
  • Java虚拟机或者运行时库出现内部错误

在方法头部的多个异常用逗号隔开。注意,只能声明抛出检查型异常,而不能声明抛出非检查型异常

class Test{
    public void f() throws ArrayIndexOutOfBoundsException {//错误,这是一个RuntimeException
        ...do something
    }
}

注意:

class A{
    public void f() throws IOException {
        //...
    }
}
class B extends A {
    public void f() throws Exception{
        //报错,子类覆写方法的Exception不能是超类的异常的超类
        //类比于访问控制符
        //如果超类没有抛出任何异常,子类就不能抛出异常
    }
}
7.1.3 如何抛出异常

有手就行。除此之外可以调用含字符串的构造器更好的描述异常。

7.1.4 创建异常类

派生自Exception,或者它的子类即可

class MyException extends Exception{
    public MyException() {
    }
    public MyException(String message){
        super(message);
    }
}

7.2 捕获异常

7.2.1 捕获异常

try catch语句。有手就行。

7.2.2 捕获多个异常

一try多catch,catch中用 " | " 分开。有手就行

7.2.3 再次抛出异常与异常链

可以在catch语句中再次抛出异常

出来新建,还可以调用initCause方法把原始异常设置为新异常的"原因"

try{
    ExceptionTest.f(1);
}catch (Exception original){
    var e = new RuntimeException("error in:");
    e.initCause(original);
    throw  e;
//捕获之后,使用下面这条语句来获取原始异常
    Throwable original = caughtException.getCause();

强烈建议使用这种异常链式,这样在传递的时候不会丢失底层异常的具体信息。

7.2.4 finally语句

无论是否有异常,无论catch语句怎么操作(例如抛出新的异常)finally块的语句都会执行。这多用于关闭资源,连接等最终处理操作。

final语句的功能应该限定于清理资源,而不应该写任何逻辑。

package com.package1;


import java.io.IOException;

public class Test {
    public static void main(String[] args) {
        System.out.println(FinalTest.f());

    }
}
class FinalTest{
    public static int f(){
        try{
            return Integer.parseInt("这里是要报错的");
            //按道理来讲,会报一个转换错误,但是final语句直接return了,这里没用了
        }
        finally {
            return 0;
        }
    }
}
7.2.5 try-with-Resources语句

是一种扩展的写法

open a resource
try{
    work with the resource
}
finally{
    close the source
}
//假如这个资源实现了AutoCloseable接口(有close方法,抛出Exception,它还有一个Closeable接口,抛出的是IOException)可以这样写:
try (Resource res = ...){
    work with res;
}
//在退出try代码块时会自动执行close方法.
try(var in = new Scanner(new FileInputStream("/user/share/dict/word"),StandardCharset.UTF_8),
   var out = new PrintWriter("out.txt",StandardCharset.UTF_8)){
    while(in.hasNext()){
        out.println(in.next().toUpperCase());
    }
}
// 无论这个块如何退出,in和out都会被关闭

try-with-Resource语句也可以有自己的catch和final语句,这些语句会在close之后执行

7.2.6 分析堆栈轨迹元素

堆栈轨迹(stack trace),程序执行过程中某个特定点上的所有挂起方法调用的一个列表(那一层层的嵌套报错)

package com.package1;


import java.io.PrintWriter;
import java.io.StringWriter;

public class Test {
    public static void main(String[] args) {
        var t = new Throwable();
        var out = new StringWriter();
        t.printStackTrace(new PrintWriter(out));
        //可以调用Throwable类的pringtStackTrace方法访问堆栈轨迹的文本描述信息
        String description = out.toString();
        System.out.println(description);
        System.out.println("finished");
    }
}
//"C:\Program Files\Java\jdk-15.0.2\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2020.3.2\lib\idea_rt.jar=49790:C:\Program Files\JetBrains\IntelliJ IDEA 2020.3.2\bin" -Dfile.encoding=UTF-8 -classpath C:\Users\NieGuevara\Desktop\JavaRelearn\out\production\JavaRelearn com.package1.Test
//java.lang.Throwable
//	at com.package1.Test.main(Test.java:9)
//finished
//还可以使用StackWalker类
//它会生成一个StackWalker.StackFrame实例流,每一个实例描述一个栈帧(stack frame)
StackWalker walker = StackWalker.getInstance();
walker.foeEach(frame -> analyze frame);
//如果想使用懒方式处理
walker.walk(stream -> process stream);
package com.package1;


import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Scanner;

public class Test {
    public static void main(String[] args) {
        try(var in = new Scanner(System.in)){
            System.out.println("Enter n");
            int n = in.nextInt();
            factorial(n);
        }
    }
    public static int factorial(int n){
        System.out.println("factorial("+ n + ")");
        var walker = StackWalker.getInstance();
        walker.forEach(System.out::println);
        int r;
        if(n <= 1){
            r = 1;
        }
        else{
            r = n * factorial(n-1);
        }
        System.out.println("return " + r);
        return r;
    }
}
//result:
Enter n
5
factorial(5)
com.package1.Test.factorial(Test.java:19)
com.package1.Test.main(Test.java:13)
factorial(4)
com.package1.Test.factorial(Test.java:19)
com.package1.Test.factorial(Test.java:25)
com.package1.Test.main(Test.java:13)
factorial(3)
com.package1.Test.factorial(Test.java:19)
com.package1.Test.factorial(Test.java:25)
com.package1.Test.factorial(Test.java:25)
com.package1.Test.main(Test.java:13)
factorial(2)
com.package1.Test.factorial(Test.java:19)
com.package1.Test.factorial(Test.java:25)
com.package1.Test.factorial(Test.java:25)
com.package1.Test.factorial(Test.java:25)
com.package1.Test.main(Test.java:13)
factorial(1)
com.package1.Test.factorial(Test.java:19)
com.package1.Test.factorial(Test.java:25)
com.package1.Test.factorial(Test.java:25)
com.package1.Test.factorial(Test.java:25)
com.package1.Test.factorial(Test.java:25)
com.package1.Test.main(Test.java:13)
return 1
return 2
return 6
return 24
return 120

Process finished with exit code 0

7.3 使用异常的技巧

1.异常处理不能代替简单的测试
if(!s.empty()){
    s.pop();
}
//运行时间明显少于
try{
    s.pop();
}catch(EmptyStackException){
    ...
}

只在异常情况下使用异常

2.不要过分细化异常

不要写多个并列或者嵌套的异常

3.充分利用异常层次结构

不要只抛出超类的那些异常,应该细化

4. 不要压制异常

意即不要catch了之后什么也不干

5.在检测错误时,苛刻比放任更好

能抛出异常,就直接抛出异常,而不是返回一个null,这有很大可能在之后抛出一个NullPointerException异常

6.不要羞于传递异常

自己不能处理就继续上抛

7.4 使用断言

7.4.1 断言的概念

在测试期间向代码插入一些检查,而在生产代码中自动删除这些检查。

assert condition;
//或者
assert condition : expression;//表达式将会传入AssertError的构造器,并转换成一个消息字符串.
//如果结果为false,则抛出一个AssertionError异常

Java中assert失败不会自动打印断言的相关信息,而需要程序员自己写expression

public class Test {
    public static void main(String[] args) {
        int x = 1;
        assert x < 0 : "x应该小于0";
        }
}

但是是不报错的,因为默认assert是被ban了

7.4.2 启用和禁用断言

可以在运行程序时使用 -enableassertions 或者 -ea 启用断言

而且,不用重新编译程序来启用或者禁用断言。启用和禁用断言是类加载器的功能。

也可以在某个类或整个包中启用断言

java -ea:MyClass -ea:com.werun.njq MyApp;

可以使用-disableassertion 或者 -da 在某个特定类或者包中禁用断言

对于系统类,可以使用-esa , -enablesystemassertions 开启断言

7.4.3 使用断言完成参数检查

  • 断言是致命的
  • 断言检查只是在开发和测试阶段打开

7.5 日志

这里仅介绍java标准日志,log4j2,Logback什么的先不做介绍

7.5.1 基本日志
package com.package1;

import java.util.logging.Logger;

public class Test {
    public static void main(String[] args) {
        Logger.getGlobal().info("打印一条日志");
    }
}
//6月 16, 2021 7:55:02 下午 com.package1.Test main
//信息: 打印一条日志
调用 Logger.getGlobal().setLevel(level.OFF);会关闭日志
private static final Logger myLogger = Logger.getLogger("com.package1.Test");
//获得自己的日志记录器而非使用全局的(或造成混乱)
//声明为static防止被垃圾回收(如果未被任何变量引用,是有可能的)

包于包之间是没有任何语义关系的,但是对于日志记录器,在子包里面的日志记录器会继承母包的日志级别:

  • SEVERE(最高级别)
  • WARNING
  • INFO
  • CONFIG
  • FINE
  • FINER
  • FINSET

默认只用前三个。也可以设置不同的级别。可以用Level.OFF关闭所有,或者Level.ALL开启所有

logger.setLevel(Level.FINE);
logger.warning(message);
logger.log(Level.FINE,message);

默认的日志处理器会抑制低于INFO级别的日志

package com.package1;

import java.util.logging.Level;
import java.util.logging.Logger;

public class Test {
    public static void main(String[] args) {
        myLogger.setLevel(Level.FINE);
        //即便如此,INFO以下的信息仍然不会输出,需要配置Handler。
        f(1);
    }
    private static int f(int n){
        myLogger.entering("com.package1.Test","f",n);
        //生成的日志是FINER级别的,
        myLogger.logp(Level.FINER,"com.package1.Test","f","logp会获得调用类和方法的准确位置");
        n++;
        myLogger.exiting("com.package1.Test","f",n);
        return n;
    }
    private static final Logger myLogger = Logger.getLogger("com.package1.Test");
}

记录异常也是一种通常的用法

if(...){
    var e = new IOException("...");
    logger.throwing("com.werun.njq","f",e);
    //级别为FINER
    throw e;
}
//or:
try{
    ...
}catch(IOException e){
    Logger.getLogger("com.werun.njq").log(Level.Warning,"Reading image",e)}
7.5.3 修改日志管理器配置

可以通过编辑配置文件来修改日志系统的各个属性,默认情况下配置文件位于

conf/logging.properties

7.5.4 本地化

采用资源包实现本地化(亦或者说国际化)

在请求一个日志记录器时,可以指定一个资源包

具体见书309

7.5.5 处理器

默认情况下日志记录器将记录发送到ConsoleHandler,并由它输出到system.err流

7.5.6 过滤器

实现Filter接口并重新定义isLoggable方法

7.5.7 格式化器

扩展Formatter类并覆盖format方法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值