七. (《Java核心技术》读书笔记+重点整理系列)异常处理、断言和日志

异常分类

Throwable
Error
Exception
IOException
Runtime Exception
  • Error:Java运行时系统的内部错误以及资源耗尽的错误。程序中基本不用处理,如果出现了该类错误,告知用户并安全退出即可。

  • RuntimeException:程序编写中的错误,不需要try…catch捕获,修改程序即可避免的错误。也叫uncheckedException,所谓的unchecked是指编译过程中,编译器无法检测出来的错误。比如:数组越界、访问空指针。

  • IOException:类中抛出的、需要try…catch捕获的大多是这类异常。对应的也成为checkedException,编译器会强制检查是否处理的异常。

抛出异常

  • 声明受查异常:在方法签名后,用关键字throws接异常类名。

  • 抛出异常:在方法代码中,用关键字throw接异常对象。

  • 自定义异常类:创建一个继承自Exception或Exception子类的自定义类,规范化的包含两个构造器,一个无参无内容构造器,一个含参数调用super(String)构造器。

public class Main{
    public class myException extends Exception{
        public myException(){}
        public myException(Integer i, String s){
            super("文件名长" + String.valueOf(i) + " 文件名为" + s + "  WEONG!");
        }
    }

    public void myPrint(String filename) throws myException {
        if (filename.length() > 5){
            throw new myException(filename.length(), filename);
        }
        //...
        return;
    }

    public static void main(String[] args){
        Main m = new Main();
        try {
            m.myPrint("abcdef");
        } catch (myException e) {
            System.out.println(e.getMessage());
            //e.printStackTrace();
        }
    }
}

捕获异常

//try...catch
public static void main(String[] args) {
    File f = new File("a.txt");
    try {
        FileInputStream fin = new FileInputStream(f);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    }
}
//IDEA整体缩进快捷键:Shift + Tab

两种执行结果:
try块中没有异常,跳过catch块;
try块中遇到异常,跳过剩余try块语句,执行catch块。

应用程序中,不可避免的会用到文件操作IO流或者是数据库连接,诸如此类开销比较大的资源。这个时候就需要在使用完后,手动进行资源的及时销毁,避免内存泄露。try…catch有三种写法可以完成上述功能。

//try...catch
public static void main(String[] args) {
    File f = new File("a.txt");
    try {
        FileInputStream fin = new FileInputStream(f);
        fin.close();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e){
        e.printStackTrace();
    }
}

但如果在对象创建之后,遇到了异常跳入某个catch块中,这样close的代码就执行不到。由此引出了finally块,即无论try…catch如何执行,最后一定会执行finally块。

//try...catch...finally
public static void main(String[] args) {
    File f = new File("a.txt");
    FileInputStream fin = null;
    try {
        fin = new FileInputStream(f);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } finally {
        try {
            fin.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

试想如果变量在创建的时候就报异常,导致变量没有创建仍是空值,就会在finally块中报空值引用错误。而且这种写法,在finally块中遇到异常时,还需要再使用一次try…catch,略显臃肿。

//try-with-resources
public static void main(String[] args) {
    File f = new File("a.txt");
    try(FileInputStream fin =new FileInputStream(f)) {
        ...
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e){
        e.printStackTrace();
    }
}

try-with-resources写法的原理,会在try块退出的时候执行一次资源关闭。实测不会出现空指针情况,而且写法简洁,最为推荐。

  • 异常链:在实际的开发背景中,还会在catch块中再次抛出异常。用来改变异常类型,或者使用自定的详述的异常类型。也可以用Java提供的initCause(e)和getCause(),来构建一个因果的异常链。

  • 堆栈轨迹元素:常用的e.printStackTrace()方法打印方法调用过程,也可以使用e.getStackTrace()返回一个StackTraceElement[]数组,用来逐个分析异常文件名和代码行数。

断言

断言是在开发测试阶段使用,用来确定每一部分的前置条件是否满足,并以此来确定程序错误出现的区间,以便快速定位BUG。相较于异常的优势在于,可以在运行的时候选择是否执行断言,并且不用重新编译。

public static void main(String[] args) {
    File f = null;
    assert f != null:f;
    System.out.println(f.getName());
}
//定位到错误在File对象没有初始化

assert有两种形式:
assert 条件;
assert 条件 : 表达式;

//含有的表达式会在输出中显示值,核心就是报一个AssertionError
Exception in thread "main" java.lang.AssertionError: null
	at Main.main(Main.java:23)
  • 启用断言:在IDEA中修改运行配置,加入-ea的参数即可。我开始还以为在程序参数中加,但是发现程序参数是主函数的args形参。应该调出VM参数,加入-ea。同时可以用-ea:或者-da:启用或抑制某个包中的断言。还有就是在程序中使用ClassLoader类加载器的时候,手动启用或是移除断言。

记录日志

在JDK1.3以前,日志都是直接print到STDOUT流中。后来出现了log4j,以及性能更优的slf4j框架。

  • 基本日志:日志记录器有7个级别,分别为:SEVERE>WARNING>INFO>CONFIG>FINE>FINER>FINEST。
//全局日志记录器
public static void main(String[] args) {
    int a = 1;
    //Logger.getGlobal().setLevel(Level.WARNING);
    //设置最低显示级别
    Logger.getGlobal().info("赋值a为1。");
}
  • 高级日志:也就是使用自定义的日志记录器。还可以有不同类型的日志,比如entering函数步入以及exiting函数退出。
public class Main{
    private static final Logger mylog = Logger.getLogger("com.mylog");
    //用静态变量存储日志记录器,防止没有使用而被垃圾回收器回收。
    public static void main(String[] args) {
        int a = 1;
        mylog.warning("a赋值为1");
    }
}
public class Main{
    private static final Logger mylog = Logger.getLogger("com.mylog");
    public static void main(String[] args) {
        mylog.setLevel(Level.FINER);
        mylog.setUseParentHandlers(false);
        Handler handler = new ConsoleHandler();
        handler.setLevel(Level.FINER);
        mylog.addHandler(handler);
        mylog.entering("com", "main");
        int a = 1;
        mylog.exiting("com", "read", a);
    }
}
//这里涉及到了处理器的内容,之后会详述。
  • 处理器:日志记录器会将记录发送到日志处理器中,由处理器决定如何输出日志。默认情况下,会发送到ConsoleHandler中,输出到System.err中。(关于System.err与System.out相似,为标准错误输出,体现在编译器中就是输出内容为红色)值得注意的是,自定义处理器时,会默认将日志再传一份给命名为“”的父处理器,这也是为什么要设置setUseParentHandlers为false,目的是只有一遍输出即可。记录器有记录级别,同时处理器也有处理级别,所以能显示的日志为两者的最小阈值。类似的,还可以将日志输出到文件或者流数据中,分别对应了FileHandler和StreamHandler。
public class Main{
    private static final Logger mylog = Logger.getLogger("com.mylog");
    public static void main(String[] args) {
        try {
            Handler handler = new FileHandler("src/1.log");
            //默认路径在C盘用户里
            mylog.addHandler(handler);
            mylog.setLevel(Level.WARNING);
            mylog.warning("BUG!!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
文件处理器配置参数(java.util.logging.Handler)作用
append追加文件末尾或是每个程序打开一个文件
limit文件最大允许大小,超过则新建
pattern文件路径以及文件名模式(如%g循环数值)
count文件名循环数(1不循环)
  • 过滤器:每个记录器和处理器都可以有一个过滤器,默认情况下是根据日志的级别过滤。如果想要自定义的话,可以实现Filter接口,重写isLoggable方法,使用setFilter绑定。

  • 格式化器:同样的,自定义时需要实现Formatter接口,用setFormatter绑定。

  • 修改配置文件:文件路径在jdk/conf/logging.properties中,修改相应字段,程序中的修改优先级更高。可以指定自定义部件属性值,也可以直接修改全局值。

//补充配置文件相关,实质上Properties抽象出String类型的键值对
try {
    Properties properties = new Properties();
    FileInputStream fileInputStream = new FileInputStream("1.txt");
    properties.load(fileInputStream);
    //还可以通过store存储
    String name = properties.getProperty("name");
    System.out.println(name);
} catch (Exception e) {
    e.printStackTrace();
}
  • 本地化:使用logmessages资源包,显示信息必须用包中定义的,可以完成不同语言的映射,适用于全球化编程。

本文中的日志均使用的是java.util.logging自带的框架,可以使用功能更为强大的log4j或者是commons-logging。

调试技巧

  • 日志代理:new对象的时候,使用匿名类重写要使用的方法,调用super方法,加入日志信息。

  • -verbose启动VM,可以看到类的加载路径。-Xlint对常见错误的检查。

  • Windows下打开bin下的jconsole监控内存、线程、类加载等情况。

  • jmap工具查看堆中变量值。(使用jps命令查看运行的所有java程序及其虚拟标识符)

PS

  • print中如果出现多个变量的字符串拼接,最好使用printf格式化输出,否则会创建很多的String变量。

  • 打开文件的路径框中输入cmd,可以直接在目标路径下打开命令行。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值