zl程序教程

您现在的位置是:首页 >  后端

当前栏目

理解Java异常处理机制

JAVA异常 处理 理解 机制
2023-09-14 09:04:17 时间

推荐资源站:https://zhimalier.com/

面试常见问题

/*try块中的退出语句
笔试面试题解析
public class ExceptionTest5
{
    public void method()
    {
        try
        {
            System.out.println("进入到try块");
            //return;
            //会先执行finally块再返回
            //虚拟机退出
            //System.exit(0);
            //不会执行finally块中的语句,直接退出
        }
        catch (Exception e)
        {
            System.out.println("异常发生了!");
        }
        finally
        {
            System.out.println("进入到finally块");
        }
        
        System.out.println("后续代码");    
    }
    
    public static void main(String[] args)
    {
        ExceptionTest5 test = new ExceptionTest5();
        test.method();
    }
}
在加上return语句前,程序输出:
	进入到try块
	进入到finally块
	后续代码
 如果在try块中加入return语句:
  程序执行输出:
	进入到try块
	进入到finally块
说明try块中有return语句时,仍然会首先执行finally块中的语句,然后方
法再返回。
  如果try块中存在System.exit(0);语句,那么就不会执行finally块中
的代码,因为System.exit(0)会终止当前运行的Java虚拟机,程序会在虚拟
机终止前结束执行。*/

try..catch..finally相信大家再熟悉不过了,但是它没想象中那么简单!

Java异常的概念和体系结构
Java把异常当作对象来处理,并定义一个基类java.lang.Throwable作为所有异常的超类。 
         在Java API中已经定义了许多异常类,这些异常类分为两大类,错误Error和异常Exception。 
         Java异常体系结构呈树状  如图:

 

 

Thorwable类所有异常和错误的超类,有两个子类Error和Exception,分别表示错误和异常。 
 其中异常类Exception又分为运行时异常(RuntimeException)和非运行时异常

1、Error与Exception 

      Error(错误):是程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。

例如,Java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)

一般会选择线程终止 

      Exception是程序本身可以处理的异常,这种异常分两大类运行时异常和非运行时异常。 程序中应当尽可能去处理这些异常。
      注意:异常和错误的区别:异常能被程序本身可以处理,错误是无法处理。   

Java中exception的异常分为两大类:

        1.Checked Exception(非Runtime Exception)
  2.Unchecked Exception(Runtime Exception)
运行时异常
  这些异常是不检查异常 RuntimeException类是Exception类的子类,它叫做运行时异常,Java中的所有运行时异常都会直接或者间接地继承自RuntimeException类。

这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。

非运行时异常

Java中凡是继承自Exception,而不继承自RuntimeException类的异常都是非运行时异常。必须处理!

Java异常处理涉及到五个关键字,分别是:try、catch、finally、throw、throws。

1、try语句块,表示要尝试运行代码,try语句块中代码受异常监控,其中代码发生异常时,会抛出异常对象。    

catch语句块会捕获try代码块中发生的异常并在其代码块中做异常处理,catch语句带一个Throwable类型的参数,  表示可捕获异常类型。当try中出现异常时,catch会捕获到发生的异常,并和自己的异常类型匹配,  若匹配,则执行catch块中代码,并将catch块参数指向所抛的异常对象。catch语句可以有多个, 用来匹配多个中的一个异常,一旦匹配上后,就不再尝试匹配别的catch块了。 通过异常对象可以获取异常发生时完整的JVM堆栈信息,以及异常信息和异常发生的原因等。 

 finally语句块是紧跟catch语句后的语句块,这个语句块总是会在方法返回前执行,  而不管是否try语句块是否发生异常。并且这个语句块总是在方法返回前执行。  目的是给程序一个补救的机会。这样做也体现了Java语言的健壮性。 

2、 try、catch、finally三个语句块应注意的问题 
    第一、try、catch、finally三个语句块均不能单独使用,三者可以组成 try...catch...finally、try...catch、   try...finally三种结构,catch语句可以有一个或多个,finally语句最多一个。 
    第二、try、catch、finally三个代码块中变量的作用域为代码块内部,分别独立而不能相互访问。 如果要在三个块中都可以访问,则需要将变量定义到这些块的外面。 
    第三、多个catch块时候,只会匹配其中一个异常类并执行catch块代码,而不会再执行别的catch块, 并且匹配catch语句的顺序是由上到下。 

3、throw、throws关键字 
    throw关键字是用于方法体内部,用来抛出一个Throwable类型的异常。如果抛出了检查异常,  则还应该在方法头部声明方法可能抛出的异常类型。该方法的调用者也
必须检查处理抛出的异常。 如果所有方法都层层上抛获取的异常,最终JVM会进行处理,处理也很简单,就是打印异常消息和堆栈信息。  如果抛出的是Error或RuntimeException,则该方法的调用者可选择处理该异常。有关异常的转译会在下面说明。  throws关键字用于方法体外部的方法声明部分,用来声明方法可能会抛
出某些异常。仅当抛出了检查异常,  该方法的调用者才必须处理或者重新抛出该异常。当方法的调用者无力处理该异常的时候,应该继续抛出,  而不是囫囵吞枣一般在catch块中打印一下堆栈信息做个勉强处理。

异常处理的机制为:抛出异常,捕捉异常。

抛出异常:当一个方法出现错误引发异常时,方法创建异常对象并交付运行时系统,异常对象中包含了异常类型和异常出现时的程序状态等异常信息。运行时系统负责寻找处置异常的代码并执行。  

捕获异常:在方法抛出异常之后,运行时系统将转为寻找合适的异常处理器(exception handler)。潜在的异常处理器是异常发生时依次存留在调用栈中的方法的集合。当异常处理器所能处理的异常类型与方法抛出的异常类型相符时,即为合适 的异常处理器。运行时系统从发生异常的方法开始,依次回查调用栈中的方法,直至找到含有合适异常处理器的方法并执行。当运行时系统遍历调用栈而未找到合适 的异常处理器,则运行时系统终止。同时,意味着Java程序的终止。

异常处理的一般结构

try
    {
         // 可能发生异常的代码
        // 如果发生了异常,那么异常之后的代码都不会被执行
    }
    catch (Exception e)
    {
        // 异常处理代码
    }
    finally
    {
        // 不管有没有发生异常,finally语句块都会被执行
    }

多个catch结构  一个try后面可以跟多个catch,但不管多少个,最多只会有一个catch块被执行。

异常处理方法

对于非运行时异常(checked exception),必须要对其进行处理,否则无法通过编译。
处理方式有两种:
1.使用try..catch..finally进行捕获;
2.在产生异常的方法声明后面写上throws 某一个Exception类型,如throws Exception,将异常抛出到外面一层去。对非运行时异常的处理详见代码例子:
处理方式1:将异常捕获

public class ExceptionTest2
{
    public void method() throws Exception // 将异常抛出,由调用这
个方法的方法去处理这个异常,如果main方法也将异常抛出,则交给Java虚拟
机来处理
    {
        System.out.println("Hello World");
        // 抛出异常
        throw new Exception();
    }

    public static void main(String[] args)
    {
        ExceptionTest2 test = new ExceptionTest2();
        try
        {
            test.method();
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        finally
        {
            System.out.println("Welcome");
        }

    }

}

处理方式2:将异常继续向外抛出

public class ExceptionTest2
{
    public void method() throws Exception // 将异常抛出,由调用这
个方法的方法去处理这个异常,如果main方法也将异常抛出,则交给Java虚拟
机来处理
    {
        System.out.println("Hello World");
        // 抛出异常
        throw new Exception();
    }

    public static void main(String[] args) throws Exception // 

main方法选择将异常继续抛出
    {
        ExceptionTest2 test = new ExceptionTest2();
        test.method(); // main方法需要对异常进行处理
        // 执行结果:
        // Hello World
        // Exception in thread "main" java.lang.Exception
        // at com.learnjava.exception.ExceptionTest2.method
(ExceptionTest2.java:10)
        // at com.learnjava.exception.ExceptionTest2.main
(ExceptionTest2.java:17)
    }

}

对于运行时异常(runtime exception),可以对其进行处理,也可以不处理。推荐不对运行时异常进行处理。

自定义异常

所谓自定义异常,通常就是定义一个类,去继承Exception类或者它的子类。因为异常必须直接或者间接地继承自Exception类。通常情况下,会直接继承自Exception类,一般不会继承某个运行时的异常类。
      自定义异常可以用于处理用户登录错误,用户输入错误提示等。
自定义异常的例子:
自定义一个异常类型:

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

异常处理方式一:

public class ExceptionTest4
{

    public void method(String str) throws MyException
    {
        if(null == str)
        {
            throw new MyException("传入的字符串参数不能为null!");
        }
        else
        {
            System.out.println(str);
        }
    }
   
    public static void main(String[] args) throws MyException //异常处理方式1,不断向外抛出
    {
        ExceptionTest4 test = new ExceptionTest4();
        test.method(null);
    }
}

异常处理方式二

public class ExceptionTest4
{
    public void method(String str) throws MyException
    {
        if (null == str)
        {
            throw new MyException("传入的字符串参数不能为null!");
        }
        else
        {
            System.out.println(str);
        }
    }

    public static void main(String[] args)
    {
        //异常处理方式2,采用try...catch语句
        try
        {
            ExceptionTest4 test = new ExceptionTest4();
            test.method(null);

        }
        catch (MyException e)
        {
            e.printStackTrace();
        }    
        finally
        {
            System.out.println("程序处理完毕");
        }

    }
}

刚才说过,可以有多个catch块,去捕获不同的异常,真正执行的时候最多只进入一个catch块。
例:多种异常

 

public class MyException extends Exception
{

    public MyException()
    {  super();
    }
    public MyException(String message)
    {
        super(message);
    }
}

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

}

public class ExceptionTest4
{

    public void method(String str) throws MyException, MyException2
    {
        if (null == str)
        {
            throw new MyException("传入的字符串参数不能为null!");
        }
        else if ("hello".equals(str))
        {
            throw new MyException2("传入的字符串不能为hello");
        }
        else
        {
            System.out.println(str);
        }
    }

    public static void main(String[] args)
    {
        // 异常处理方式2,采用try...catch语句
        try
        {
            ExceptionTest4 test = new ExceptionTest4();
            test.method(null);

        }
        catch (MyException e)
        {
            System.out.println("进入到MyException catch块");
            e.printStackTrace();
        }
        catch (MyException2 e)
        {
            System.out.println("进入到MyException2 catch块");
            e.printStackTrace();
        }
        finally
        {
            System.out.println("程序处理完毕");
        }
    }
}

我们可以使用多个catch块来捕获异常,这时需要将父类型的catch块放到子类型的catch块之后,这样才能保证后续的catch块可能被执行,否则子类型
的catch块将永远无法到达,Java编译器会报错。如果异常类型是独立的,那么它们的前后顺序没有要求。
例子:
多个catch语句块的顺序

 

public class ExceptionTest4
{

    public void method(String str) throws Exception // 也可以声明Exception,只要声明的可以涵盖所有抛出的异常即可
    {
        if (null == str)
        {
            throw new MyException("传入的字符串参数不能为null!");
        }
        else if ("hello".equals(str))
        {
            throw new MyException2("传入的字符串不能为hello");
        }
        else
        {
            System.out.println(str);
        }
    }
    public static void main(String[] args)
    {
        // 异常处理方式2,采用try...catch语句
        try
        {
            ExceptionTest4 test = new ExceptionTest4();
            test.method(null);

        }
        catch (MyException e)
        {
            System.out.println("进入到MyException catch块");
            e.printStackTrace();
        }
        catch (MyException2 e)
        {
            System.out.println("进入到MyException2 catch块");
            e.printStackTrace();
        }
        catch (Exception e)
        {
            //虽然需要加上,但是这块代码不会被执行,只是为了编译成功
            System.out.println("进入到MyException catch块");
            e.printStackTrace();
            //如果去掉前面两个catch块或其中之一,则发生该异常时就会进入此catch块
            //catch块的匹配是按照从上到下的顺序,所以这个块如果放在最前面就会捕获所有的异常,后面的块永远不会执行,这时候会提示编译错误
        }
        finally
        {
            System.out.println("程序处理完毕");
        }
    }
}

练习

public class TestException {
	public TestException() {
	}

	boolean testEx() throws Exception {
		boolean ret = true;
		try {
			ret = testEx1();
		} catch (Exception e) {
			System.out.println("testEx, catch exception");
			ret = false;
			throw e;
		} finally {
			System.out.println("testEx, finally; return value=" + ret);
			return ret;
		}
	}

	boolean testEx1() throws Exception {
		boolean ret = true;
		try {
			ret = testEx2();
			if (!ret) {
				return false;
			}
			System.out.println("testEx1, at the end of try");
			return ret;
		} catch (Exception e) {
			System.out.println("testEx1, catch exception");
			ret = false;
			throw e;
		} finally {
			System.out.println("testEx1, finally; return value=" + ret);
			return ret;
		}
	}

	boolean testEx2() throws Exception {
		boolean ret = true;
		try {
			int b = 12;
			int c;
			for (int i = 2; i >= -2; i--) {
				c = b / i;
				System.out.println("i=" + i);
			}
			return true;
		} catch (Exception e) {
			System.out.println("testEx2, catch exception");
			ret = false;
			throw e;
		} finally {
			System.out.println("testEx2, finally; return value=" + ret);
			return ret;
		}
	}

	public static void main(String[] args) {
		TestException testException1 = new TestException();
		try {
			testException1.testEx();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

/*答案:
i=2
i=1
testEx2, catch exception
testEx2, finally; return value=false
testEx1, finally; return value=false
testEx, finally; return value=false

注意说明:

finally语句块不应该出现 应该出现return。上面的return ret最好是其他

语句来处理相关逻辑。*/