在多线程和反射中java异常处理

1、概述

java异常可以理解为因为程序运行环境或者代码逻辑等原因,致使程序不能继续正常工作的一种情况。而在java中定义了很多种不同类型的异常,使得jvm在抛出异常的时候,我们能根据这个异常的种类,快速的定位出程序出问题的原因。

1.1 异常的种类

在java中所有的异常都继承于Throwable类。而Throwable又分为两个子类,Error类和Exception类。
Error:代表着程序无法处理的错误。比如我们的虚拟机出现了内存溢出或者内存泄漏、系统奔溃等。这些异常通常通过程序无法解决。
Exception:Exception又可以分为检查性异常和运行性异常。其中检查性异常是指在在编译期间就需要捕获的异常,需要我们提前在代码中对这些可能发生的异常进行处理。比如,我们的文件IO异常,SQL异常等等。
而运行时异常,是指由于我们程序员在代码编写过程中由于失误或者疏忽导致程序在运行时出现错误。这些异常会由jvm自动抛出,不需要我们在代码中进行预先的处理。常有的RuntimeException包括:空指针异常NullPointerException,数组越界异常IndexOutOfBoundsException,除数为0异常ArithmeticException等等。这些异常需要我们程序员在开发过程中要细致,避免出现这些异常。

1.2 异常的抛出和处理原则

1.2.1 异常的抛出

异常的抛出可以分为jvm抛出和自己手动抛出。jvm会抛出RuntimeException和checked Exception。 不过有时候,为了需要,我们也可以自己手动抛出异常,并且这经常和自定义异常一起使用。在系统中,有时候java中没有定义我们所需要的异常,那么我们可以自定义一个异常,然后针对这个自定义异常,进行相关的逻辑处理。

/**
 * @author sks
 *自定义异常需要扩展Exception基类。
 */
public class MyException extends Exception{

	private static final long serialVersionUID = 1L;
    private String Message;

    public MyException(String message) {
        this.setMessage(message);
    }

    public String getMessage() {
        return Message;
    }

    public void setMessage(String message) {
        Message = message;
    }

}

然后,我们就可以在程序中手动抛出这个。可以自己在程序中处理,或者抛给上级调用的方法(一般都这么操作)。

    public static void test(){
	    
	    	try {
				
	    		throw new MyException("手动抛出错误");
			} catch (Exception e) {
				// TODO: handle exception
			    System.out.println(e.getMessage());
			}
	    }

1.2.2 异常的处理

异常的处理可以分为自己内部处理或者抛给上级调用方法处理。其中

1.2.2.1 自己处理异常 try-catch-finally

try{
    //code that might generate exceptions    
}catch(Exception e){
    //the code of handling exception1
}catch(Exception e){
    //the code of handling exception2
}finally{
    do something
}

上述try-catch所描述的即是监控区域,关键词try后的一对大括号将一块可能发生异常的代码包起来,即为监控区域。Java方法在运行过程中发生了异常,则创建异常对象。将异常抛出监控区域之外,由Java运行时系统负责寻找匹配的catch子句来捕获异常。若有一个catch语句匹配到了,则执行该catch块中的异常处理代码,就不再尝试匹配别的catch块了。
在多个catch时,依照顺序进行,因此,子类的exception一般放在前面,以便于我们进行更准确的定位。

一般笔试时候,会经常问到,try-catch-finally的返回值问题。比如下面的代码:

public static void main(String[] args) {
		// TODO Auto-generated method stub
		Trycatchfinally t1=new Trycatchfinally();
	        //System.out.println("test1方法执行完毕!result的值为:"+t1.test1());
	    int result=t1.test2();
	    System.out.println("test2方法执行完毕!"+result);
	}

	/**
    
     * @return
     */
    public int test2(){
        int divider=2;
        int result=0;
        try{
            while(divider>-1){
                divider--;
                result=result+100/divider;
            }
            return result;
        }catch(Exception e){
            e.printStackTrace();
            System.out.println("异常抛出了!!");
            return result=999;
        }finally{
            System.out.println("这是finally,哈哈哈!!");
            System.out.println("result的值为:"+result);
            result=1000;
          
        }
        
    }

上面方法中,在执行到divider=0时候,爆出异常。转到catch代码块,此时result=100。
在catch代码块中,对result重新赋值=999,在return之前,会先继续执行finally块。
在finally语句块中,打印result的值,为999。 对于后面的 result=1000赋值,对方法的返回结果没有影响。
然后返回catch语句块的return语句,整个方法返回。此时result=999.
即finally块中的语句是在函数返回前执行的,但函数返回值是在finally块中语句执行前确定的。
但是如果在finally中加入return result。那么最终的返回值就变成了1000了。

1.2.2.2 通过在方法头加入throws抛出异常让上级处理

比如下面的方法

    public static int test3() throws Exception{
       
    	int i=0;
    	if (i==0) {
			throw new Exception("除数不能为0");
		}else{
			return 1;
		}       
    }

如果我们在main方法中调用这个方法,就需要对这个test3()方法进行异常处理。

	public static void main(String[] args)  {
		try {
			test3();
		} catch (Exception e) {
			// TODO: handle exception
			
		}
		
	}
	

事实上,上面test3()方法,是一个除数为0的错误,这种错误属于运行时异常错误,是不需要预先处理的。所以,如果我们写成下面的形式,那么我们在main方法中就不需要通过try-catch进行包裹。

  public static int test3() throws Exception{
      
    	int i=0;
    	if (i==0) {
			throw new ArithmeticException("除数不能为0");
		}else{
			return 1;
		}       
    }

有时候,我们需要把异常统一成规定的模式进行抛出。那么我们可以在catch语句块中,处理完异常后(不处理也是一种处理方法),然后抛出我们自己定义的异常。

介绍完异常的知识后,下面介绍两种特殊情况下的异常:

2 多线程中的异常处理(UncaughtExceptionHandler)

线程是独立执行的代码片断,线程的问题应该由线程自己来解决,而不要委托到外部。”基于这样的设计理念,在Java中,线程方法的异常都应该在线程代码边界之内(run方法内)进行try catch并处理掉。换句话说,我们不能捕获从线程中逃逸的异常。
比如下面的代码:

public class MultiThreadExceptionTest implements Runnable{

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		try {
			ExecutorService service=Executors.newFixedThreadPool(2);
			service.execute(new MultiThreadExceptionTest());
		} catch (RuntimeException  e) {
			// TODO: handle exception
			System.out.println("Exception has been handled!");
		}
	}

	/* (non-Javadoc)
	 * @see java.lang.Runnable#run()
	 */
	@Override
	public void run() {
		// TODO Auto-generated method stub
		
		throw new RuntimeException();
	}

}

其运行结果:

Exception in thread "pool-1-thread-1" java.lang.RuntimeException
	at com.andy.multiexception.MultiThreadExceptionTest.run(MultiThreadExceptionTest.java:49)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:744)

即我们的主线程中,没有抓到子线程爆出的异常。多线程运行不能按照顺序执行过程中捕获异常的方式来处理异常,异常会被直接抛出到控制台(由于线程的本质,使得你不能捕获从线程中逃逸的异常。)

2.1 如何捕获多线程的异常

可以通过使用UncaughtExceptionHandler。它能检测出某个线程由于未捕获的异常而终结的情况。当一个线程由于未捕获异常而退出时,JVM会把这个事件报告给应用程序提供的UncaughtExceptionHandler异常处理器(这是Thread类中的接口):

class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler{
	


/* (non-Javadoc)
 * @see java.lang.Thread.UncaughtExceptionHandler#uncaughtException(java.lang.Thread, java.lang.Throwable)
 */
@Override
public void uncaughtException(Thread t, Throwable e) {
	// TODO Auto-generated method stub
	System.out.println("caught    "+e);
	}
}

然后,在线程工厂中绑定这个UncaughtExceptionHandler

class HanlderThreadFactory implements ThreadFactory{
    @Override
    public Thread newThread(Runnable r) {      
        Thread t = new Thread(r);
        t.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());//设定线程工厂的异常处理器       
        return t;
    }
}

最后,我们的线程生成,是通过我们的线程工厂。这样,主线程就能捕获到子线程异常。

public class OneThreadExceptionTest2 implements Runnable{

   /**
    * @param args
    */
   public static void main(String[] args) {
   	// TODO Auto-generated method stub
   	
   	try {
   		ExecutorService service=Executors.newFixedThreadPool(2, new HanlderThreadFactory());
   		service.execute(new OneThreadExceptionTest2());
   
   	} catch (RuntimeException  e) {
   		// TODO: handle exception
   		System.out.println("Exception has been handled!");
   	}
   }

   /* (non-Javadoc)
    * @see java.lang.Runnable#run()
    */
   @Override
   public void run() {
   	// TODO Auto-generated method stub
   	
   	throw new RuntimeException("子线程异常");
   }
   
   public void test(){
   	
   	throw new RuntimeException();
   	
   }

}

运行结果为:

 caught    java.lang.RuntimeException: 子线程异常

反射过程中的异常处理InvocationTargetException

在反射时,如果反射的类的方法抛出异常,我们想在调用反射的代码块中抓这个异常是不能实现的。 比如下面的代码:

public class Man {

	 private String work;

	    public String getWork() {
	        return work;
	    }

	    public void setWork(String work) throws MyException {
	        this.work = work;
	        System.out.println(work);
	        throw new MyException("man is died");
	    }
	    

在setWork方法中抛出自定义异常。我们在test方法中通过反射调用Man类的setWork方法时,想捕获这个MyException异常,是无法捕获的:

	public static void main(String[] args)  {
		// TODO Auto-generated method stub
		
		Class clazz = null;
		try {
			clazz = Class.forName("com.andy.multiexception.reflect.Man");
		} catch (ClassNotFoundException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		Method method = null;
		try {
			method = clazz.getMethod("setWork", String.class);
		}
		catch (NoSuchMethodException | SecurityException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		
		Object object = null;
		try {
			object = clazz.newInstance();
		} catch (InstantiationException | IllegalAccessException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		
		try {
			try {
				method.invoke(object, "work hard!");
			}
			
			catch(MyException e){    // 1
				
			}
			catch (IllegalAccessException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}catch (IllegalArgumentException | InvocationTargetException e) {
			
//			 Throwable cause = e.getCause();
//	            if(cause instanceof MyException){
//	                System.out.println("ok,we catch SimpleException:"+(MyException)cause);
//	            }
	
			e.printStackTrace();
		}
		
		

	}

在1处的catch(MyException e)中会报Unreachable catch block for MyException. This exception is never thrown from the try statement body的错误。
那么如何能捕获反射的方法中的异常呢。可以通过后面的InvocationTargetException 异常。即

catch ( InvocationTargetException e) {
			
			 Throwable cause = e.getCause();
            if(cause instanceof MyException){
                System.out.println("ok,we catch SimpleException:"+(MyException)cause);
            }
	
			e.printStackTrace();
		}

你可能感兴趣的:(java)