您的位置:首页 > 编程语言 > Java开发

白话Java高级特性之异常

2015-12-15 16:28 477 查看
白话Java高级特性之异常
对于本文的内容,属于基础知识研究范畴,切勿以为读完此文就能将异常知识掌握到家。切记:操千曲而后晓声,观千剑而后识器,所以我觉得没有大量的源码阅读经验,你很难知道什么时候需要自定义异常,什么时候需要抛出异常。


异常机制概述

异常机制是指当程序出现错误后,程序如何处理。具体来说,异常机制提供了程序退出的安全通道。当出现错误后,程序执行的流程发生改变,程序的控制权转移到异常处理器。


异常处理的流程

当程序中抛出一个异常后,程序从程序中导致异常的代码处跳出,java虚拟机检测寻找和try关键字匹配的处理该异常的catch块,如果找到,将控制权交到catch块中的代码,然后继续往下执行程序,try块中发生异常的代码不会被重新执行。如果没有找到处理该异常的catch块,在所有的finally块代码被执行和当前线程的所属的ThreadGroup的uncaughtException方法被调用后,遇到异常的当前线程被中止。


先说说异常处理技术带来的好处

异常处理比传统的错误管理技术更具优势。使用异常处理可以将错误错误代码与正常代码分开。在传统的编程技术中,错误的检测,报告和处理代码往往会是程序的处理逻辑变得异常繁琐复杂。


异常的结构

异常的继承结构:Throwable为基类,Error和Exception继承Throwable,RuntimeException和IOException等继承Exception。Error和RuntimeException及其子类成为未检查异常(unchecked),其它异常成为已检查异常(checked)。



对于读取文件的readFile()方法,其处理逻辑的伪代码如下。

readFile{

打开文件;

得到文件的长度;

分配足够大的内存空间。

读取文件内容到内存中。

关闭文件。

}

上述逻辑虽然简单,但是却忽略了对可能出现的错误处理。

使用异常处理技术之后的伪码

readFile{

try{

打开文件;

得到文件的长度;

分配足够大的内存空间。

读取文件内容到内存中。

关闭文件。

}catch(文件打开失败){

//dosomething

}

Catch(读取文件床都失败){

//dosomething

}

Catch(分配内存空间失败){

//dosomething

}

Catch(读取文件内容失败){

//dosomething

}

Catch(关闭文件见失败){

//dosomething

}

}

采用异常处理技术时分别在不同的地方编写正常的代码和错误的处理代码。虽安异常处理技术并没有减少程序员进行错误处理的工作量,但可以帮助程序员将错误处理工作组织的更加有效。

异常处理机制:

1, 捕获异常

2, 声明抛出异常



  java.lang.Exception类是Java中所有异常的直接或间接父类。即Exception类是所有异常的根类。
  比如程序: 

public
class ExceptionTest
{

public static void main(String[] args)
{

int a =3;

int b =0;

int c = a/ b;
System.out.println(c);
}
}


  编译通过,执行时结果:
  Exception inthread "main" java.lang.ArithmeticException: / by zero
  at com.learnjava.exception.ExceptionTest.main(ExceptionTest.java:9)
  因为除数为0,所以引发了算数异常。

  比较常见的异常还有这种:空指针异常
  java.lang.NullPointerException是空指针异常,出现该异常的原因在于某个引用为null,但却调用了它的某个方法,这时就会出现该异常。

Java中的异常分为两大类:
  1.Checked Exception(非RuntimeException
  2.Unchecked ExceptionRuntime Exception
运行时异常
  RuntimeException类是Exception类的子类,它叫做运行时异常,Java中的所有运行时异常都会直接或者间接地继承自RuntimeException类。
  Java中凡是继承自Exception,而不继承自RuntimeException类的异常都是非运行时异常

异常处理的一般结构


try
{

// 可能发生异常的代码

  // 如果发生了异常,那么异常之后的代码都不会被执行
}

catch (Exception e)
{

// 异常处理代码
}

finally
{

// 不管有没有发生异常,finally语句块都会被执行
}

  比如本文最开始的除法运算代码,加入异常处理之后: 

public
class ExceptionTest
{

public static void main(String[] args)
{

int c =0;

try
{

int a =3;
int b = 0;


// 这块代码出现了异常
c = a / b;


// 那么异常之后的代码都不会被执行
System.out.println("HelloWorld");
}

catch(ArithmeticException e)
{
e.printStackTrace();
}

finally
{

//不管有没有发生异常,finally语句块都会被执行
System.out.println("Welcome");
}

System.out.println(c);

// 当b为0时,有异常,输出为c的初始值0
}
}


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

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

public
class ExceptionTest2
{

public void method()
throwsException // 将异常抛出,由调用这个方法的方法去处理这个异常,如果main方法也将异常抛出,则交给Java虚拟机来处理
{
System.out.println("HelloWorld");


// 抛出异常

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()
throwsException // 将异常抛出,由调用这个方法的方法去处理这个异常,如果main方法也将异常抛出,则交给Java虚拟机来处理
{
System.out.println("HelloWorld");


// 抛出异常

throw new Exception();
}


public static void main(String[] args)
throwsException // main方法选择将异常继续抛出
{
ExceptionTest2 test =
new ExceptionTest2();

test.method();
// main方法需要对异常进行处理

//
执行结果:
// Hello World
// Exception in thread "main"java.lang.Exception
// atcom.learnjava.exception.ExceptionTest2.method(ExceptionTest2.java:10)
// atcom.learnjava.exception.ExceptionTest2.main(ExceptionTest2.java:17)
}

}


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

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

public
class MyException extendsException
{

public MyException()
{

super();
}

public MyException(String message)
{

super(message);
}
}

  一种异常处理方式:

public
class ExceptionTest4
{


public void method(String str)
throwsMyException
{

if(null == str)
{

throw new MyException("传入的字符串参数不能为null!");
}

else
{
System.out.println(str);
}
}


public static void main(String[] args)
throwsMyException //异常处理方式1,不断向外抛出
{
ExceptionTest4 test =
new ExceptionTest4();
test.method(null);
}
}

  另一种异常处理方式:

public
class ExceptionTest4
{


public void method(String str)
throwsMyException
{

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 extendsException
{


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)
throwsMyException, 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 (MyExceptione)
{
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编译器会报错。
  如果异常类型是独立的,那么它们的前后顺序没有要求。
  如对上面的代码进行改动后,如下列出:

public
class ExceptionTest4
{


public void method(String str)
throwsException // 也可以声明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 (Exceptione)
{

//虽然需要加上,但是这块代码不会被执行,只是为了编译成功
System.out.println("进入到MyException catch块");
e.printStackTrace();

//如果去掉前面两个catch块或其中之一,则发生该异常时就会进入此catch块
//catch块的匹配是按照从上到下的顺序,所以这个块如果放在最前面就会捕获所有的异常,后面的块永远不会执行,这时候会提示编译错误
}

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

}
}


面试常考题型
  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();
}
}


  说明try块中有return语句时,仍然会首先执行finally块中的语句,然后方法再返回。
  如果try块中存在System.exit(0);语句,那么就不会执行finally块中的代码,因为System.exit(0)会终止当前运行的Java虚拟机,程序会在虚拟机终止前结束执行。
好啦,以上就是我对java异常的总结。

参考资料:http://swiftlet.net/archives/998
http://www.cnblogs.com/mengdd/archive/2013/02/03/2890923.html 《java程序设计》祝庆生 古平。清华大学出版社
张龙老师Java SE系列视频教程
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: