异常处理流程

有两种办法抛出异常:

  • 通过try-catch抛出,例如
public void method A(int money)
{
try
{
if(--money<=0)
{
throw new SpecialException("Outof money");
}
}
catch(SpecialException e)
{
处理异常
}
}
  • 在方法声明处声明抛出
public void method A(int money)throws SpecialException
{
if(--money<=0)
{
throw new SpecialException("Out of money");
}
}

每次抛出异常之后,都要找到catch块,如果在当前方法中没有找到,那么它将会弹出栈帧,到了上一级继续寻找知道找到。找到之后就执行catch块内容然后退出。

如果到最底部也没有找到catch块,那么就调用异常对象的printStackTrace()方法,打印异常信息。

如果该线程不是主线程,那么就会退出这一个线程,如果是主线程(main),那么就会退出程序。

抛出异常和处理异常最好在同一方法,不然对性能影响较大。

finally 在任何情况下都要执行的代码

因为异常处理会打断正常进程,所以可能会导致一些占用的资源不会被释放。在c++中就是动态内存分配的问题,在java中额例如关闭数据库链接,关闭输入流。

finally跟在catch后面,并且一定不能在前面。

throws 抛出可能的异常

如果一个方法需要抛出异常,但没有能力解决异常,可以在方法头声明throws语句,在前面已经举过例子了。

规则

  • try后面可以有多个catch块,只能有至多一个finally块,也可以只跟finally块。
  • 在try块中定义的变量,在catch和finally中不能访问。
  • 每当try后面有多个catch时,会依次对catch块进行匹配,只需要匹配同一继承分支上的父类就会执行。例如, IOException是Exception的子类,如果抛出IOExpception且Exception的catch块在前那么就只会执行Exception。例:
try
{
code1;//抛出FileNotfoundException
}
catch(SQLExceptio e)
{
System.out.println("SOLException");
}
catch(Exception e)
{
System.out.println("Exception");
}
catch(IOException)
{
System.out.println("IOException");
}

这个只会输出Exception,因为Exception和FileNotFoundException在还有IOException在同一继承分支上,每一次直接走Exception了,而不会走IOException。所以让子类的catch在前面,防止错误的catch

  • 为了简化,可以用 | 分隔两个异常在一个catch块中进行处理。例如 catch( FileNotFoundException | InterruptedIOException e){…}
  • 如果一个地方出现受检查异常,要么用throws,要么用try,不然会出现编译错误(这样也就方便些try了,不然写代码的时候还真不好说哪里有异常)。

退出

正常流程是捕捉异常,执行catch,执行finally,退出。有两种退出方式

第一种使用System.exit(number);这种方法会直接退出程序,

第二种是return,退出本方法,这个时候如果有返回值还可以弄个返回值以便其他部分正常运行。finally执行于return之前。一般return是在catch中的,但是也可以在finally中,但是这样可能会导致问题。

  • 返回值覆盖,因为catch执行于return之前,所以如果要执行catch中的return,会先执行finally中的return,这个时候catch中的return就不会执行了。
  • 丢失异常,如果在catch中抛出异常且finally中有return就可能发生异常丢失。这个时候catch中的异常就不会被抛出。此外,如果catch和finally中都抛出异常,那么catch中的异常将会丢失。

为了解决丢失异常的问题,Throwable接口中有两个默认实现的方法

public final void addSuppressed(Throwable exception)
public final Throwable[] getSuppressed()

其中addSuppressed()方法就是把丢失的异常保存下来,getSuppressed就是返回所有保存下来的异常。当然,并不是系统自动添加,还要手动用这些方法去添加。

异常类

java中用类来描述异常。所有异常的祖先是java.lang.Throwable类。它的实例就是具体的异常,可以通过throw抛出。它提供了一些常用方法,包括

  • getMessage() 返回String类型的异常信息
  • printStackTrace() 打印跟踪方法调用栈获得的详细异常信息

例如catch到这个异常后,可以System.out.println(e.getMessage())来输出异常信息。例如e里面的信息时Out of money(就是前面的),那么就会输出这个异常信息。

而如果用后面那个方法,将会说明哪个类,哪一行出现了异常。

异常类图

  • Error类,表示单靠程序本身无法恢复的严重错误,例如内存不足,或者栈溢出。
  • Exception类,表示程序可以处理的异常。出现这些异常时,可以进行处理而不退出程序。
  • IOException 输入输出时产生的异常。
  • ArithmeticException 数学异常。例如除以0
  • NullpointerException,空指针异常。当引用变量时null是,试图使用这个变量将会出现。
  • IndexOutOfBoundsException 下标越界异常
  • ClassCastException 类型转换异常,例如父类转子类
  • IllegalArgumentException 非法参数,例如 if(name==null)throw new IllegalArgumentException(“姓名不能为空”)。

运行时异常

RuntimeException一个子树都可以叫运行时异常。这种异常特点是编译器不会检查他。例如上面说的数学异常。当这种异常出现的时候程序将异常终止。其他的都是受检查异常。

自定义异常

可以通过继承来实现自定义异常。一般挂在Exception或RuntimeException上。

自定义异常要一般要写以下部分。

异常数据,异常原因(String类型),然后是一个带参数的构造函数。之后提供方法说明这个异常。

异常转义异常链

原始的异常对于用户来说看不懂,这个时候我们就要抛出一些更人性化的异常。但是与此同时我们也要把原始异常保存易于我们排错。这个时候我们就可以在原始异常上进行扩展。扩展类的数据域中可以来一个Throwable的引用变量,然后在构造函数中把原始异常导入。这样就保存了原始异常。

例:

public class BaseException extends Exception
{
protected Throwable cause = null;
public BaseException();
public BaseException(Throwable cause)
{
this.cause = cause;
}
public BaseException(String msg, Throwable cause)
{
super(msg);
this.cause = cause;
}
...

处理多样化异常

就是一次性抛出多个异常,这就需要先自定义一个异常类,收集多种异常然后一次性输出。

例:

public class BaseException extends Exception
{
private int size = 0;
private List<Throwable>exceptions = new ArrayList<Throwable>();
public void addExcpetion(BaseException ex)
{
exceptions.add(ex);
size++;
}
}

记录日志

记录日志的作用:监视代码中变量情况,周期性的记录到文件中供其他应用统计分析。承担继承开发环境中调试器作用。

要在程序中输出日志,最普通的办法是用println输出。比较好的做法是用一个日志操作类。

现在可以直接使用java.util.logging日志操作包。这个包中主要有四个类

  • Logger类:生成日志,并对日志信息分级筛选,确定什么等级被输出,什么不输出。
  • Handler: 负责输出日志信息。它有两个子类:ConcoleHandler(输出到Dos控制台),FileHandler(输出到文件中)
  • Level类: 表示日志各个界别。

创建及设置级别

创建 Logger mylogger = Logger.getLogger("mylogger");

getlogger就是用来创建对象的。如果mylogger存在,那么直接返回引用。

级别: SEVERE(严重), WARNING(警告),INFO , CONFIG(确认),FINE(好),CONFIG,FINE,FINER,FINEST

默认情况下,只会输出最高三个级别的。可以使用Logger类的setLevel()来设置级别。例:mylogger.setLevel("Level.FINE")//把日志设置成FINE级别。这样设置FINE及以上级别都会被设置。还有Level.on开启所有级别和Level.off关闭所有级别。

mylogger.info("这是一条普通提示消息")用级别的名字设置提示消息

输出日志到文件

FileHandler fileHandler = new FileHandler("C:\\test.log");
fileHandler.setLevel(Level.INFO);//设定向文件中写日志的级别
mylogger.addHandler(fileHandler);//将FileHandler与Logger关联

断言

语法: assert 条件表达式 或 assert 条件表达式 : 包含错误信息的表达式。

作用: 当条件表达式为false时,会抛出AssertError,这是一个错误。如果后面有包含错误信息的表达式,那么将会输出后面的内容

例如: assert b!=0 : ”b不能为0“;

启用断言需要-ea参数,而IDEA中默认是关闭的。