System.out.println(1/0);
这行代码如果运行会出现什么,答案是抛出除数不能为0的异常,本片文章的重点不是说异常,而是异常的样子
不知道各位在运行代码时有没有发现一点
为什么编译器会给我们抛出异常(答:为了能找到错误的位置)
Exception in thread "main" java.lang.ArithmeticException: / by zero
at net.shock.codes.Main.main(Main.java:12)
看到这行错误,我们很容易就会发现错误在第12行,但是我们的重点不是解决这个错误,而是为什么能把错误显示出来,还能点击这个蓝色的文字并且跳转,这是编译器自己就是这样设计的?肯定不是,因为我们在用命令行运行代码时也能看到这个异常,说明这是java里的设计。
那么我们这篇博文要讲什么。
我们要做一个东西。不知道各位在做项目时有没有这样的一个问题,我们一般写代码为了方便,会习惯性的用输出语句把输出一些信息,方便调试,如果代码量比较小还好,但是代码量很大了,比如有几十上百的类,这时想找出这个输出语句就成了很麻烦的事的,所以我们要做一个能把输出转化为除了能输出内容外还能输出位置的功能。
开始吧。
首先要明确接下来我们要做什么,既然我们是要得到输出位置,正常的方法我们是没法获取的,因为我们不可能把每行输出都写上自己的位置,所以我们先这样做
try {
System.out.println(1/0);
} catch (Exception e) {
e.printStackTrace();
}
然后我们用debug到异常里,这时,我们可以看到这个地方
synchronized (s.lock()) {
// Print our stack trace
s.println(this);
StackTraceElement[] trace = getOurStackTrace();
for (StackTraceElement traceElement : trace)
s.println("\tat " + traceElement);
看到这里我想大家应该知道是怎么回事了吧,当然,没明白的我们来试试
代码输出 \tat 的时候输出了traceElement,所以我们找到这个变量的位置,跟踪getOurStackTrace可以看到一个字段stackTrace,但是这个字段是私有的,我们没法得到,但大家不要忘了一件事,这个类是所有异常的父类,所以肯定这个字段肯定不是完全的私有
我们现在把这个类实例化,可以看到
没错,就是这个东西,现在我们把这个方法取出来,然后输出来看看
看到这个我想大家应该知道了吧,这就是我们想得到的,但是光是这样还不够,因为我们还不知道这里面到底是什么,我们到这个类的toString里看看
有点乱,但是问题不大,总体来说就是类名.方法名.(文件名:行号)
这是输出格式,其中(文件名:行号)要特别注意,因为这个格式是固定的,我测试了几次,只有这样的格式才能跳转,也许这是编译器自己这样设计的,也许其他编译器会有其他样式,暂时不管了,现在我们来完成下一个目标,捕获输出
在刚才我们到Throwable类里的时候应该看到了这一行代码
s.println("\tat " + traceElement);
这个s是什么呢,他的类型java.lang.Throwable.PrintStreamOrWriter,但我们一般的输出类型是PrintStream,从名字我们可以猜想到,这个类是输出类的子类,当然,我的猜想错了,因为这个类不是子类,而是利用外界的PrintStream把结果输出来了,但是不影响。
知道了这些,我们就开始吧
public class Main{
public static void main( String[] args ){
System.setOut(new MyPrintStream(System.out));
System.out.println("123");
}
}
class MyPrintStream extends PrintStream{
public MyPrintStream(PrintStream ps) {
super(ps);
}
@Override
public void print(String s) {
super.print(format()+s);
}
private String format(){
StackTraceElement[] ss = new Throwable().getStackTrace();
StackTraceElement s = ss[3];
return String.format("%s\t", s);
}
}
之所以选择第三个是因为我测出来是就是3,其他也是可以选的,比如方法之上或者方法之下什么都可以,而格式化也可以写其他的,比如加上时间什么的,看起来就行日志一样,我只选择了文字,其他的想改什么就重写相应的方法就行,大概就这些了,作用不大,不过写代码时可以利用这方法抓取那些输出语句,方便维护什么的