java异常使用

Posted 信行合一

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java异常使用相关的知识,希望对你有一定的参考价值。

1.异常的输出流程

  • 第一行:输出 错误类型 + 错误内容
  • 2-N行:输出 错误堆栈 信息

1.常见异常例子:

package com.ztd.question.err;

/**
 * java 异常打印流程
 * @author ztd
 */
public class Demo01SimpleError 
    public static void main(String[] args) 
        new Demo01SimpleError().m1();
    
    public void m1() 
        m2();
    
    public void m2() 
        try 
            throw new Exception("出问题了");
         catch (Exception e) 
            e.printStackTrace();
        
    

java打印的结果如下:

java.lang.Exception: 出问题了
	at com.ztd.question.err.Demo01SimpleError.m2(Demo01SimpleError.java:16)
	at com.ztd.question.err.Demo01SimpleError.m1(Demo01SimpleError.java:12)
	at com.ztd.question.err.Demo01SimpleError.main(Demo01SimpleError.java:9)

2.异常为什么会这么输出?

这就需要查看一下 e.printStackTrace() 这个方法的实现了,这个方法是Throwable类里面的一个方法,这个方法在控制台里面输出的内容之所以是红色,是因为它使用的是System.err 这个 PrintStream 输出的,我们通常输出内容使用的是 System.out 对象输出的,编辑器可以根据 输出对象 的不同来在控制台展示不同的颜色

真正执行的方法如下:

private void printStackTrace(PrintStreamOrWriter s) 
        Set<Throwable> dejaVu =
            Collections.newSetFromMap(new IdentityHashMap<Throwable, Boolean>());
        dejaVu.add(this);

        synchronized (s.lock()) 
            // 1.打印的第一条错误信息
            s.println(this);
            StackTraceElement[] trace = getOurStackTrace();
            // 2.打印堆栈信息
            for (StackTraceElement traceElement : trace)
                s.println("\\tat " + traceElement);
					
            // 3.打印被压制的错误
            for (Throwable se : getSuppressed())
                se.printEnclosedStackTrace(s, trace, SUPPRESSED_CAPTION, "\\t", dejaVu);
            // 4.打印导致错误的原因
            Throwable ourCause = getCause();
            if (ourCause != null)
                ourCause.printEnclosedStackTrace(s, trace, CAUSE_CAPTION, "", dejaVu);
        
    

1.输出错误类型 + 消息内容

s.println(this) 方法输出的就是 toString()方法的信息,这个方法也是用的 Throwable 类本身自己的 toString() 方法,可以看到这个方法内容如下:就是输出当前类的 名称 + : + 错误信息

 public String toString() 
        String s = getClass().getName();
        String message = getLocalizedMessage();
        return (message != null) ? (s + ": " + message) : s;
    

对比一下上面的错误信息完全一致

java.lang.Exception: 出问题了

2.输出错误堆栈信息

之后打印的是堆栈的调用链信息,堆栈的调用关系是从下往上调用的,因为堆栈调用方式本身就是后进先出,所以报错的位置一定在堆栈的最上方,堆栈的初始调用位置一定在最下方。这个堆栈的形成是在 new Exception 的时候就初始化好的。

在执行 new Exception() 的时候,可以看到方法的执行如下:

public Exception(String message) 
		super(message);

super 方法执行的是 Throwable 类里面的方法

public Throwable(String message) 
    fillInStackTrace();
    detailMessage = message;

public synchronized Throwable fillInStackTrace() 
  if (stackTrace != null ||
      backtrace != null /* Out of protocol state */ ) 
    fillInStackTrace(0);
    stackTrace = UNASSIGNED_STACK;
  
  return this;

// 最终执行了 本地方法 来生成堆栈信息
private native Throwable fillInStackTrace(int dummy);

其中 fillInStackTrace() 方法最终调用本地方法生成堆栈信息数组 StackTraceElement[]。这个 StackTraceElement类里面包含了当前方法执行所在的 类名方法名文件名代码行号

public final class StackTraceElement implements java.io.Serializable 
    // Normally initialized by VM (public constructor added in 1.5)
    private String declaringClass;
    private String methodName;
    private String fileName;
    private int    lineNumber;

这样在输出错误堆栈的时候,就可以拼接出来每一条堆栈信息了

打印堆栈的代码如下:at…

for (StackTraceElement traceElement : trace)
  // 堆栈打印代码 at ...
  s.println("\\tat " + traceElement);

这个时候,我们看到堆栈打印的直接是对象,所以看一下StackTraceElement的toString方法:

    public String toString() 
        return getClassName() + "." + methodName +
            (isNativeMethod() ? "(Native Method)" :
             (fileName != null && lineNumber >= 0 ?
              "(" + fileName + ":" + lineNumber + ")" :
              (fileName != null ?  "("+fileName+")" : "(Unknown Source)")));
    

可以看到,这就是我们看到的错误消息:用到了 类名称、方法名称、文件名称、代码行号

at com.ztd.question.err.SimpleError.m2(SimpleError.java:16)

如果这个方法是本地方法,还会带上 (Native Method) 标识,如果是本地方法就不会显示类文件名称和行号了,因为不存在

at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

3.被压制的异常:suppressed exceptions

1.异常什么情况下会被隐藏

看下面的这种情况,本来在catch之后需要抛出异常的,但是因为finally里面也抛出了异常,那么这个时候 catch 抛出的异常就被隐藏了

package com.ztd.question.err;

/**
 * 被压制的异常
 * @author ztd
 */
public class Demo02SuppressException 
    public static void main(String[] args) 
        try
            process("");
        catch(Exception e)
            System.out.println("catched:");
            throw new RuntimeException(e);
        finally
            System.out.println("finally");
            throw new NullPointerException();
        
    
    static void process(String s)
        throw new IllegalArgumentException();
    

打印结果如下:

catched:
finally
Exception in thread "main" java.lang.NullPointerException
	at com.ztd.question.err.Demo02SuppressException.main(Demo02SuppressException.java:16)

2.如何打印隐藏的异常

这个时候如果想要把2个异常都打印出来,该怎么办?Throwable里面提供了一个 addSuppressed() 方法,用于把隐藏的异常也打印出来

package com.ztd.question.err;

/**
 * 被隐藏的异常
 * @author ztd
 */
public class Demo03PrintSuppressException 
    public static void main(String[] args) throws Exception 
        Exception origin = null;
        try
            process("");
        catch(Exception e)
            origin =e;
            throw new RuntimeException(e);
        finally
            try
                throw new NullPointerException();
            catch(Exception e)
                if (origin!=null)
                    origin.addSuppressed(e);
                else
                    origin=e;
                
            
            if (origin!=null)
                throw origin;
            
        
    
    static void process(String s)
        throw new IllegalArgumentException();
    

在打印异常的代码中也可以看到对这个地方进行了处理,而且会加上Suppress标识

// Print suppressed exceptions, if any
for (Throwable se : getSuppressed())
  se.printEnclosedStackTrace(s, trace, SUPPRESSED_CAPTION, "\\t", dejaVu);

打印结果如下:

Exception in thread "main" java.lang.IllegalArgumentException
	at com.ztd.question.err.Demo03PrintSuppressException.process(Demo03PrintSuppressException.java:31)
	at com.ztd.question.err.Demo03PrintSuppressException.main(Demo03PrintSuppressException.java:11)
	Suppressed: java.lang.NullPointerException
		at com.ztd.question.err.Demo03PrintSuppressException.main(Demo03PrintSuppressException.java:17)

4.如果异常没有被主动输出会怎么处理?

看下面的例子,虽然报错,但是没有主动调用 e.printStackTrace()

package com.ztd.question.err;

/**
 * 没有主动输出异常
 * @author ztd
 */
public class Demo04ErrorNotPrint 
    public static void main(String[] args) throws Exception 
        throw new Exception("没有主动print的错误");
    

可是结果依然打印了错误日志和调用的堆栈信息

Exception in thread "main" java.lang.Exception: 没有主动print的错误
	at com.ztd.question.err.Demo04ErrorNotPrint.main(Demo04ErrorNotPrint.java:9)

实际上,当一个错误没有地方进行catch 进行处理的时候,jvm会自动调用 Thread 类里面的dispatchUncaughtException如下方法输出异常信息,这就是为什么我们没有手动的输出错误信息,但是信息依然会被打印的原因

   private void dispatchUncaughtException(Throwable e) 
       getUncaughtExceptionHandler().uncaughtException(this, e);
   

这个方法会获取一个错误处理器,如果不存在就会使用ThreadGroup作为处理器,然后调用如下方法

 public void uncaughtException(Thread t, Throwable e) 
        if (parent != null) 
            parent.uncaughtException(t, e);
         else 
            Thread.UncaughtExceptionHandler ueh =
                Thread.getDefaultUncaughtExceptionHandler();
            if (ueh != null) 
                ueh.uncaughtException(t, e);
             else if (!(e instanceof ThreadDeath)) 
                System.err.print("Exception in thread \\""
                                 + t.getName() + "\\" ");
                e.printStackTrace(System.err);
            
        
    

输出结果如下:最终还是会像调用 e.printStackTrace() 的效果一样打印错误日志

Exception in thread "main" java.lang.RuntimeException: 运行异常
	at com.ztd.question.proxy.ExceptionTest.main(ExceptionTest.java:17)

5.异常转换

如果一个方法捕获了某个异常后,又在catch语句中抛出新的异常,就相当于把抛出的异常【转换】了。但是这样原有的异常信息不就丢失了吗?我们可以在新的异常的构造方法将原来的异常作为参数,就可以使新的异常保持原有异常的信息了。

package com.ztd.question.err;

/**
 * 异常转换例子
 * @author ztd
 */
public class Demo05ExceptionChange 
    public static void main(String[] args) throws Exception 
        new Demo05ExceptionChange().m2();
    

    public void m1() throws Exception 
        throw new Exception("m1() 异常");
    

    public void m2() throws Exception 
        try 
            m1();
         catch (Exception e) 
            throw new Exception("m2() 异常", e);
        
    

打印结果如下:

Exception in thread "main" java.lang.Exception: m2() 异常
	at com.ztd.question.err.Demo05ExceptionChange.m2(Demo05ExceptionChange.java:20)
	at com.ztd.question.err.Demo05ExceptionChange.main(Demo05ExceptionChange.java:9)
Caused by: java.lang.Exception: m1() 异常
	at com.ztd.question.err.Demo05ExceptionChange.m1(Demo05ExceptionChange.java:13)
	at com.ztd.question.err.Demo05ExceptionChange.m2(Demo05ExceptionChange.java:18)
	... 1 more

6.动态代理导致的 UndeclaredThrowableException 异常

在RPC接口调用场景或者使用动态代理的场景中,偶尔会出现UndeclaredThrowableException,又或者在使用反射的场景中,出现InvocationTargetException,这都与我们所期望的异常不一致,且将真实的异常信息隐藏在更深一层的堆栈中。本文将重点分析下UndeclaredThrowableException

先给结论

使用jdk动态代理接口时,若方法执行过程中抛出了受检异常但方法签名又没有声明该异常时则会被代理类包装成UndeclaredThrowableException抛出。

问题还原:

// 接口定义
public interface IService 
 void foo() throws SQLException;

public class ServiceImpl implements IService
 @Override
 public void foo() throws SQLException 
  throw new SQLException("I test throw an checked Exception");
 

// 动态代理
public class IServiceProxy implements InvocationHandler 
 private Object target;
 
 IServiceProxy(Object target)
  this.target = target;
 
 
 @Override
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable 
  return method.invoke(target, args);
 

 
public class MainTest 
 public static void main(String[] args) 
  IService service = new ServiceImpl();
  IService serviceProxy = (IService) Proxy.newProxyInstance(service.getClass().getClassLoader(),
    service.getClass().getInterfaces(), new IServiceProxy(service));
  try 
   serviceProxy.foo();
   catch (Exception e)
   e.printStackTrace();
  
 

上面运行得到的异常是:

java.lang.reflect.UndeclaredThrowableException
 at com.sun.proxy.$Proxy0.foo(Unknown Source)
 at com.learn.reflect.MainTest.main(MainTest.java:16)
Caused by: java.lang.reflect.InvocationTargetException
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 at java.lang.reflect.Method.invoke(Method.java:498)
 at com.learn.reflect.IServiceProxy.invoke(IServiceProxy.java

以上是关于java异常使用的主要内容,如果未能解决你的问题,请参考以下文章

java中啥情况下会发生io异常

使用 Netbeans 时 java 中的初始化程序错误异常

Java异常(Exception)

Java异常(Exception)

媒体播放器文件未找到异常

Java程序员必备:异常的十个关键知识点