对于异常情况,例如可以造成程序崩溃的错误输入,Java使用一种称为异常处理(exception handing)的错误捕获机制处理。Java中的异常处理与C++和Delphi中的异常处理十分类似。
在测试期间,需要进行大量的检测以验证程序的正确性,这些检测十分耗时,在测试完成后也不必保留他们,因此,可以将检测删掉,在其他测试需要时再贴回来,我们将介绍如何使用断言来有选择的启用检测
当程序出现错误时,并不总是能够与用户或者终端进行沟通,这是,可能希望记录下出现的问题,以备日后进行分析,我们将介绍讨论标准Java日志框架
处理错误
在Java中,如果某个方法不能够采用正常的途径完成他的任务,就可以通过另外一个途径退出方法。在这种情况下,方法并不返回任何值,而是抛出throw一个封装了错误信息的对象。需要注意的是,这个方法将会立即退出,并不返回任何值。此外,调用这个方法的代码也将无法继续执行,取而代之的是,异常处理机制开始搜索能处理这些异常情况的异常处理器(exception handler)。
异常分类
在java程序设计语言中,异常对象都是派生于Throwable类的一个实例。稍后还可以看到,如果java中内置的异常类不能够满足需求,用户还可以创建自己的异常类。
需要注意的是,所有的异常都是由Throwable继承来的,但是在下一层立即分解为两个分支:Error和Exception。
Error类层次结构描述了java运行时系统的内部错误或者资源耗尽错误。应用程序不应该抛出这种类型的对象。如果出现了这样的内部错误,除了通告给用户,并尽力是程序安全的终止以外,再也无能为力了。这种情况很少出现。
在设计java程序时,需要关注Exception层次结构。这个层次结构有分解为两个分支,一个分支派生于RuntimeException;另一个分支包含其他异常。划分两个分枝的规则是:有程序错误导致的异常属于RuntimeException;而程序本身没有问题,但由于像I/O错误这类问题导致的一场属于其他异常。
派生于RuntimeException的一场包涵下面几个情况:
不是派生于RuntimeException的异常包括:
“如果出现RuntimeException异常,那么就一定是你的问题”是一条相当有道理的规则。应该通过检测数组下标是否越界来避免ArrayIndexOutOfBoundsException异常;应该通过在使用变量之前检测是否为null来杜绝NullPointerException异常的发生。
如何处理不存在的文件的呢?难道不能先检查文件是否存在在打开它吗?这个文件有可能在你检查他是否存在之前就已经被删除了。因此,“是否存在”取决于环境,而不只是取决于你的代码。
Java语言规范将派生于Error类或者RuntimeException类的所有异常称为非受查(unchecked)异常。所有的其他的异常成为受查(checked)异常。这是两个很有用的术语,在后面还会用到。编译器将核查是否为所有的受查异常提供了异常处理器。
RuntimeException这个名字很容易让人混淆。实际上,现在讨论的所有错误都发生在运行时。
RuntimeException属于程序中的逻辑错误。
声明受查异常
如果遇到了无法处理的情况,那么java的方法可以抛出一个异常。这个道理很简单:一个方法不仅需要告诉编译器将要返回什么值,还要告诉编译器有可能发出什么错误。例如:一段读取文件的代码有可能读取的文件不存在,或者内容为空,因此,试图处理文件信息的代码就需要通知编译器可能会抛出IOException类的异常。
方法应该在其首部声明所有可能抛出的异常。
在Java中,没有throws说明符的方法将不能抛出任何受查异常。
java throws在编译时执行,而不是在运行时执行
如何抛出异常:
抛出EOFException的语句:
throw new EOFException();
//或者
EOFException e=new EOFException();
throw e;
//下面将这些代码放在一起:
String readData(Scanner in)throws EOFException
{
while(...)
{
if(!in.hasNext()) //EOF encounter
{
if(nthrow new EOFException();
/*
EOFException还有一个带有字符串型参数的构造器
String gripe="a";
throw new EOFException(gripe);
*/
}
}
return s;
}
在前面已经看到,对于一个已经存在的异常类,将其抛出非常容易,在这种情况下:
创建异常类:
在程序中,可能会遇到任何标准类都没有能充分描述清楚的问题,在这种情况下,创建自己的异常类就很有必要了。我们需要做的只是定义一个派生于Exception的类,或者派生于Exception子类的类。例如:定义一个派生于IOException的类。习惯上,定义的类应该包含两个构造器,一个是默认的构造器,另一个是带有详细描述信息的构造器(超类Throwable的toString方法将会打印出这些详细信息,者在调试中非常有用。
class FileFormatException extends IOException
{
public FileFormatException(){};
public FileFormatException(String gripe)
{
super(gripe);
}
}
//again
class FileFormatException extends IOException
{
public FileFormatException(){};
public FileFormatException(String gripe)
{
super(gripe);
}
}
现在,就可以抛出自己定义的异常类型了
String readData(BufferedReader in) throws FileFormatException
{
...
while(...)
{
if(ch==-1) //EOF encounter 遇到了EOF
{
if(nreturn s;
}
String gripe =“a“;
throw new EOFException(gripe);
java.lang.Throwable()
Throwable()
//构造一个新的Throwable对象,这个对象没有详细的描述信息
Throwable(String message)
//构造一个新的throwable对象,这个对象带有特定的详细描述信息。习惯上,所有派生的异常类都支持一个默认的构造器和一个带有详细描述信息的构造器
String getMessage()
//获得Throwable对象的详细描述信息
捕获异常
到目前为止,已经知道如何抛出一个异常。这个过程十分容易。只要将其抛出throw就不用理睬了。当然,有些代码必须捕获异常。捕获异常需要进行周密的计划,这正是以下要讲述的内容。
如果某个异常发生时没有在任何地方进行捕获,那么程序将中止执行,并在控制台上打印出异常信息,包括异常的类型和堆栈的内容。对于图形界面程序(applet和应用程序),在捕获异常之后,也会打印出堆栈的信息,但是程序将返回到用户界面的处理循环中(在调试GUI程序时,最好保证控制台窗口可见,并且没有被最小化)。
要想捕获一个异常,必须设置try/catch语句块。最简单的try语句块如下所示。
try
{
code
more code
more code
}
catch(ExceptionType e)
{
handler for this type;
}
如果在try语句块中的任何代码抛出了一个在catch子句中说明的异常类,那么
1)程序将跳过try语句块的其余代码
2)程序将执行catch子句中的处理器代码
如果在try语句块中的代码没有抛出任何异常,那么程序将跳过catch语句。
如果方法中的任何代码抛出了一个在catch子句中没有声明的异常类型,那么这个方法就会立刻退出(希望调用者为这种类型的异常设计了catch字句)
为了演示捕获异常的处理过程,下面给出了读取数据的典型程序代码。
class well_12
{
public void read(String name) throws IOException
{
try
{
InputStream in2=new FileInputStream(name);
int bc;
while((bc=in.read())!=-1)
{
//process input
}
}
catch(IOException ex)
{
ex.printStackTrace();
}
}
}
需要注意的是,try语句中的大多数代码都很容易理解:读取并处理字节,直到遇到文件结束符为止。正如在Java API中看到的那样,read方法有可能抛出一个IOException异常。在这种情况下,将跳出整个while循环,进入catch子句,并生成一个栈轨迹。对于一个普通的程序来说,这样处理异常基本上合乎情理,还有其他的选择吗?
通常,最好的选择是什么也不做。而是将异常传递给调用者。如果read方法出现了错误,就让read方法的调用者去操心!如果采用这种处理方式,就必须声明这个方法可能会抛出一个IOException。
捕获异常:通常在运行之前java不报错,但是运行后可能会出现某些未知的错误,但是还不想直接抛出到上一级,那么就需要通过”try{}catch“的形式进行异常捕获,之后根据不同的异常情况来进行相应的处理。
传递异常:通常用在多级方法调用上,最终想将异常返回到最上层进行处理的时候,那么就把异常向上抛出,知道调用的方法处,进行异常捕获。
备注:这两种情况也没法说那种就好,只能是根据实际情况来进行相应的处理。
public void read(String name) throws IOException
{
InputStream in=new FileInputStream(name);
int b;
while((b=in.read())!=-1)
{
//process input
}
}
//
public void read(String name)
{
try
{
InputStream in= new FileInputStream(name);
int b;
while((b=in.read())!=-1)
{
//process
}
}
catch(IOException ex)
{
ex.printStackTrace();
}
}
请记住,编译器严格的执行throws说明符。如果调用了一个受查异常的方法,就必须对他进行处理,或者继续传递。
哪种方法更好呢?通常,应该捕获那些知道如何处理的异常,而将那些不知道怎样处理的异常继续进行传递,知道传递到调用处。
如果想传递一个异常,就必须在方法的首部添加一个throws说明符,以便告知调用者这个方法可能会抛出异常。
仔细阅读以下Java API文档,以便知道每个方法可能抛出那种异常,然后再决定是自己处理,还是添加到throws列表中。对于后一种情况。也不必犹豫。将异常直接交给能够胜任的处理器要比压制对他的处理更好。
同时请记住,这个规则也有一个例外。前面曾经提到过:如果编写一个覆盖超类的方法,而这个方法有没有抛出异常(JComponet中的paintComponent)那么这个方法就必须捕获方法代码中的每一个受查异常。不允许在子类的throws说明符中出现超过超类方法所列出的异常类范围。
在Java中,没有与C++中catch()所对应的东西。由于Java中的所有异常类都派生于一个公共的超类,所以,没有必要使用这种机制。
捕获多个异常
在一个try语句块中可以捕获多个异常类型,并对不同类型的异常做出不同的处理。可以按照下列方式为每个异常类型使用一个单独的catch字句:
try
{
code that might throw Exceptions
...
...
}
catch(FileNotFoundException e)
{
emergency action for missing files
}
catch(UnknownHostException e) //抛出以表示无法确定主机的IP地址
{
emergency action for unknown hosts
}
catch(IOException e)
{
emergency action for all other I/O problems
}
异常对象可能包含与异常本身有关的信息,要活的对象的更多信息,可以试着使用
e.getMessage();
//public String getMessage()
返回此throwable的详细消息字符串。
得到详细的信息(如果有的话),或者使用
e.getClass().getName()
得到异常对象的实际类型
在Java SE 7中,同一个catch自句中可以捕获多个异常类型。例如,假设对应缺少文件和未知主机异常的动作是一样的(catch不同的异常却要进行相同的动作),就可以合并catch字句:
try
{
code that might throw exception
}
catch(FileNotFoundException | UnkownHostException e)
{
emergence action for missing files and unknown hosts
}
catch(IOException e)
{
emergence action for I/O problems
}
只有当捕获的异常类型彼此之间不存在子类关系时才需要这个特性。
当捕获多个异常时,异常变量隐含为final变量,例如,不能在以下子句体中为e 赋不同的值
catch(FileNotFoundException | UnknownHostException e)
{
//不可以为e赋不同的值
}
捕获多个异常不仅会让你的代码块看起来更简单,还会更高效。生成的字节码只包含一个对应公共catch子句的代码块。
再次抛出异常与异常链
在catch子句中可以抛出一个异常,这样做的目的是改变异常的类型。如果开发了一个供其他程序员使用的子系统,那么表示子系统故障的异常类型可能会产生多种解释。ServletException就是这样一个异常类型的例子。执行servlet的代码可能不想知道发生错误的细节原因,但希望明确的知道是否servlet是否有问题。
下面给出了捕获异常并将它再次抛出的基本方法。
try
{
access the database
}
catch(SQLException e)
{
throw new ServletException("database error :" +e.getMessage());
}
这里,ServletException用带有异常信息文本的构造器来构造。
不过,可以有更好的处理方法,并且将原始异常设置为新异常的“原因”
try
{
access the database
}
catch(SQLException e)
{
Throwable se=new ServletException("database error");
se.initCause(e); //为e构造一个高级异常se,但是保持e不变,从而既可以得到原始异常,又可以保留此低级异常的原因
throw se;
}
当捕获到异常时,就可以使用下面这条语句重新得到原始异常:
Throwable e=se.getCause();
getCause();
public Throwable getCause()
/* 如果原因不存在或未知,则返回此throwable的原因或null 。 (原因是引发这个可抛掷物的抛掷物)
此实现返回通过需要Throwable的Throwable函数之一提供的Throwable ,或者使用initCause(Throwable)方法创建后设置的原因 。 虽然通常不必重写此方法,但是子类可以覆盖它以返回通过其他方法设置的原因。 这是适合,早在加入链接例外的一个“遗留Throwable链机制” Throwable 。 请注意, 没有必要覆盖任何PrintStackTrace方法,所有这些方法都调用getCause方法来确定可抛出的原因。
*/
强烈建议使用这种包装技术。这样可以让用户抛出子系统中的高级异常,而不会丢失原始异常的细节。
如果在一个方法中发生了一个受查异常,而不允许抛出它,那么包装技术就十分有用,我们可以捕获这个受查异常,并将它包装为一个运行时异常。
又是你可能只是想记录一个异常,再将它重新抛出,而不作任何改变:
try
{
access the database
}
catch(Exception e)
{
logger.log(level, messages, e);
throw e;
}
/*
log
public void log(Level level,
String msg,
Object param1)
使用一个对象参数记录消息。
如果当前为给定消息级别启用了记录器,则会创建一个相应的LogRecord并转发给所有注册的输出处理程序对象。*/
throw e;
在java SE 7之前,这种方法存在一个问题。假设这个代码在以下方法中:
public void updateRecord() throws SQLException
{
try
{
access the database
}
catch(Exception e)
{
logger.log(level, messages, e);
throw e;
}
}
java编译器查看catch块中的throw语句,然后查看e的类型,会指出这个方法可以抛出任何Exception而不只是SQLException。(也就是说不可以这么做)
现在这个问题已经有所改进。编译器会跟踪到e来自try块。假设这个try块中仅有的已检查异常是SQLException实例,另外,假设e在catch中未改变,将外围方法声明为throws SQLException就是合法的
finally语句
当代码中抛出一个异常时,就会终止方法中的剩余代码的处理,并推出这个方法的执行。如果方法获得了一些本地资源,并且只有这个方法自己知道,有如果这些资源在退出方法之前必须被收回,那么就会产资源回收问题。一种解决方案是捕获并重新抛出所有的异常。但是这种解决方案比较乏味,这是因为需要在两个地方清除所分配的资源。一个在正常的代码中,另一个是在异常的代码中。
Java有一种更好的解决方案,这就是finally子句,下面将介绍java中如何恰当的关闭一个文件。如果使用java编写数据库程序,就需要使用同样的技术关闭与数据库的连接。当发生异常时,恰当的关闭所有数据库的连接是非常重要的。
不管是否有异常被捕获,finally子句中的代码都被执行。在下面的示例中,程序将在所有情况下关闭文件。
InputStream in=new FileInputStream(...);
try
{
//1
code that might throw exceptions
//2
}
catch (IOException e)
{
//3
show error message
//4
}
finally
{
//5
in.close();
}
//6
在上面这段代码中,有下列3种情况会执行finally子句:
1)代码没有抛出异常。这种情况下,程序首先执行try语句块中的全部代码,然后执行finally子句中的代码。随后,继续执行try语句块之后的第一条语句。也就是说,执行try语句块+finally语句块+//6出的代码
2)抛出一个在catch子句中捕获的异常。在上面的是利用就是IOException异常。在这种情况下,程序将执行try语句块中的所有代码,直到发生异常为止。此时,将跳过try语句块中的剩余代码,转去执行try语句块之后的第一条语句。在这里,执行标出13456处的语句。
如果catch语句抛出了一个异常,异常将被抛回这个方法的调用者。在这里,执行1,3,5处
3)代码抛出了一个异常,但这个异常不是由catch子句捕获的,在这种情况下,程序将执行try语句块中的所有语句,直到有一场被抛出位置。此时,跳过try语句中的剩余代码,然后执行finally子句中的语句,并将异常抛给这个方法中的调用者。在这里,执行标注1,5处。
try语句可以只有finally子句,而没有catch子句。例如,下面这条try语句:
InputStream in=...;
try
{
code that might throw exceptions
}
finally
{
in.close();
}
无论在try语句块中是否遇到异常,finally子句中的in.close()语句都会被执行。当然,如果真的遇到一个异常,这个异常将被重新抛出,并且必须有另一个catch子句捕获。
事实上,我们认为在需要关闭资源时,用这种方式使用finally子句是一种不错的选择。
这里,强烈建议解耦合try/catch,try/finally语句块。这样可以提高代码的清晰度
例如:
InputStream in=...;
try
{
try
{
code that might throw exception
}
finally
{
in.close();
}
}
catch(IOException e)
{
show error message;
}
//内层的try语句块只有一个职责,就是确保关闭输入流,外层的try语句块也只有一个职责,就是确定报告中出现的错误。
当finally子句中包含return 语句时,将会出现一种意想不到的结果。假设利用return 语句从try语句中退出,在方法返回前,finally子句的内容将被执行。如果finally子句中也有一个return语句,这个返回值方会覆盖原始的返回值。请看一个复杂的例子:
public static int f(int)
{
try
{
int r=n*n;
return r;
}
finally
{
if(n==2)
return 0;
}
}
如果调用f(2),那么try语句快的计算结果r=4,并执行return语句。然而,在方法真正的返回前,还要执行finally子句。finally子句将是的方法返回0,这个返回覆盖了原始的返回值4
有时候,finally子句也会带来麻烦,例如:清理资源的方法也有可能抛出异常。假设希望能确保在流处理代码中遇到异常时将流关闭。
InputStream in=...;
try
{
code that might throw exception
}
finally
{
in.close();
}
现在,假设在try语句块中的代码抛出了一些非IOException的异常,这些异常只有这个方法的调用者才能够给予处理。执行finally语句块,并调用close方法。而close方法本身也有可能抛出IOException异常。当出现这种情况时,原始的异常将会丢失,转而抛出close方法的异常。
这会有问题,因为第一个异常很可能更有意思。如果你想做适当的处理,重新抛出原来的异常,代码会变得极为繁琐。如下所示:
InputStream in=...;
Exception ex=null;
try
{
try
{
code that might throw exception;
}
catch(Exception e)
{
ex=e;
throw e;
}
}
finally
{
try
{
in.close();
}
catch(Exception e)
{
if(ex==null)
throw e;
}
}
//幸运的是,接下来你将了解到,java SE 7中关闭资源的处理会容易得多
带资源的try语句
对于以下代码模式:
open a resourse
try
{
work with the resource;
}
finally
{
close the resource;
}
假设资源属于一个实现了AutoCloseable接口的类,java SE 7为这种代码模式提供了一个很有用的快捷方式。AutoCloseable接口有一个方法:
void close() throws Exception
/*close
void close() throws 异常
关闭此资源,放弃任何潜在资源。 在try -with-resources语句管理的对象上自动调用此方法。
虽然这个接口方法被声明为抛出异常 , 强烈建议实施者声明close方法的具体实现来抛出更多特定的异常,或者如果关闭操作不能失败,则完全不会抛出任何异常。
*/
另外,还有一个Closeable接口,这是AutoCloseable的子接口,也包含一个close方法,不过,这个方法声明为抛出一个IOException。
带资源的try语句(try-with-resourses)的最简形式为:
try(Resourse res=...)
{
work with res
}
try块退出时,会自动调用res.close()。下面给出一个典型的例子,这里要读取一个文件中的左右单词:
try(Scanner in =new Scanner (new FileInputStream("...")),"")
{
while(in.hasNext())
System.out.println(in.next());
}
这个快正常退出时,或者存在一个异常时,都会调用in.close()方法,就好像使用了finally块一样。
还可以指定很多资源。例如:
try(Scanner in=new Scanner(new FileInputStream(" ")," ");
PrintWriter out =new Printer("out.txt"))
{
while (in.hasNext())
out.println(in.next().toUpperCase());
}
不论这个块如何退出,in和out都会关闭,如果你用常规方式手动编程,就需要两个嵌套的try/final 语句
上一节已经看到,如果try块抛出一个异常,而且close方法也抛出一个异常,这就会带来一个难题。带资源的try语句可以很好的处理这种情况。原来的异常会重新抛出,而close方法抛出的异常会被抑制。这些异常将自动捕获,并由addSupressed方法增加到原来的异常。如果对这些异常感兴趣,可以调用getSuppressed方法,他会得到从close方法抛出并被抑制的异常列表。
只要需要关闭资源,就要尽可能的使用带资源的try语句。
带资源的try语句自身也可以有catch子句和一个finally子句,这些子句会在关闭资源之后执行,不过在实际中,一个try语句中加入这个多的内容可能不是一个好主意。
分析堆栈轨迹元素
堆栈轨迹(stack trace)是一个方法调用过程的列表,它包含了程序执行过程中方法调用的特定位置
前面已经看到过这种列表,当java程序正常终止,而没有捕获异常时,这个列表就会显示出来。
可以调用Throwable类的printStackTrace方法访问堆栈轨迹的文本描述信息。
Throwable t=new Throwable();
StringWriter out =new StringWriter();
t.printStackTrace(new PrintWriter(out));
String description=out.toString();
/*
public class StringWriter
extends Writer
在字符串缓冲区中收集其输出的字符流,然后可以用于构造字符串。
关闭StringWriter没有任何效果。 在流已关闭后,可以调用此类中的方法,而不生成IOException 。
getStackTrace
public StackTraceElement[] getStackTrace()
提供对printStackTrace()打印的堆栈跟踪信息的编程访问 。 返回一个堆栈跟踪元素数组,每个数组代表一个堆栈帧。 数组的第零个元素(假定数组的长度不为零)表示堆栈的顶部,这是序列中最后一个方法的调用。 通常,这是创建和抛出此throwable的点。 数组的最后一个元素(假定数组的长度不为零)表示堆栈的底部,这是序列中的第一个方法调用。
*/
一种更灵活的方法是使用getStackTrace方法,他会得到StackTraceElement对象的一个数组,可以在你的程序中分析这个对象数组。
例如:
Throwable t=new Throwable();
StackTraceElement[] frames=t.getStackTrace();
for(StackTraceElement frame:frames)
analyst frame;
StackTraceElement类含有能够获得文件名和当前执行的代码行号的方法,同时,还含有能够获得类名和方法名的方法。toString方法将产生一个格式化的字符串,其中包含所获得的信息。
静态的Thread .getAllStackTrace方法,它可以产生所有线程的堆栈轨迹。下面给出使用这个方法的具体方式。
Map<Thread,StackTraceElement[]>map=Thread.getAllStackTraces();
for(Thread t:map.keySet())
{
StackTraceElement[] frames=map.get(t);
analyse frames;
}
有关Map接口与线程的更加详细的信息请参看后续介绍
下面程序打印了递归阶乘函数的堆栈情况。例如,如果计算factorial(3),将会打印下列内容:
????
java.lang.Throwable
Throwable(Throwable cause);
Throwable(String message ,Throwable cause);
用给定的原因,构造一个Throwable对象
Throwable initCause(Throwable cause);
将这个对象设置为原因,如果这个对象已经被设置为原因,则抛出一个异常,返回this引用
Throwable getCause();
获得设置为这个对象的原因的异常对象。如果没有设置原因,则返回null
StackTraceElement[] getStackTrace();
获得构造这个对象时调用堆栈的跟踪
void addSuppressed(Throwable t)
为这个异常增加一个抑制异常。这出现在带资源的try语句中,其中t是close方法抛出的异常
Throwable[] getSuppressed();
得到这个异常的所有抑制异常。一般来说,这些是带资源的try语句中close方法抛出的异常。
java.lang.Exception
Exception(Throwable cause)
Exception(String message,Throwable cause)
用给定的原因构造一个异常对象
java.lang.RuntimeException
RuntimeException(Throwable cause)
RuntimeException(String message,Throwable cause);
用给定原因构造一个RuntimeException对象
java.lang.StackTraceElement
String getFileName();
返回这个元素运行时对应的源文件名。如果这个信息不存在,则返回null
int getLineNumber();
返回这个元素运行时对应的源文件的行数。如果这个信息不存在,则返回-1;
String getClassName()
返回这个元素运行时对应的类的完全限定名
String getMethodName()
返回这个元素运行时对应的方法名。构造器名是;静态初始化器名是,这里无法区分同名的重载方法
boolean isNativeMethod()
如果这个元素运行时在一个本地方法中,则返回true
String toString()
如果存在的话,返回一个包含类名、方法名、文件名和行数的格式化字符串
使用异常机制的技巧
目前,存在着大量有关如何恰当的使用异常机制的争论。
下面给出几个使用异常机制的技巧
1、异常处理不能代替简单的测试
捕获异常时间长,因此使用异常的基本规则是,只在异常情况下使用异常机制
2、不要过分细化异常
3、不要压制异常
关闭异常的方法(出现异常也会视而不见)
//例如:
public image loadImage(String s)
{
try
{
//code that threatens to throw checked exceptions
}
catch(Exception e)
{
}
}
现在,这段代码就可以通过编译了,除非发生异常,否则他可以正常运行,即使有异常,也会不在意异常(因为catch没有对捕获到的一场做任何处理)
public void readStuff(String filename) throws IOException
{
InputStream in=new FileInputStream(filename);
}
使用断言
如果在程序中插入检查语句:
if(x<0)
throw new IllegalArgumentException("x<0");
//运行很慢
java语言中引入了关键字assert。这个关键字有两种形式:
assert 条件;
和
assert 条件:表达式;
这两种形式都会对条件进行检测,如果结果为false,则抛出一个AssertionError异常。在第二种形式中,表达式将被传入AssertionError的构造器,并转换成一个消息字符串
要想断言x是一个非负数值,只要简单的下列语句:
assert x>=0;
或者将x的实际值传递给AssertionError对象,从而可以在后面显示出来
assert x>=0 :x;
启动和禁用断言
在默认情况下,断言被禁用,可以在运行程序时用-enableassertions或者-ea选项启用:
java -enableassertions MyApp
启动或者禁用断言不必重新编译程序。启动或者禁用断言是类加载器(Class loader)的功能。当断言被禁用时,类加载器将跳过断言代码,因此,不会降低程序的运行速度
也可以在某个类或者整个包中使用断言
java -ea:MyClass -ea:com.mycompany.mylib...MyApp
//这条命令将开启MyClass类以及在com.mycompany.mylib包和他的子包中的所有类的断言
java -ea:... -da::MyClass MyApp
有些类不是由类加载器加载,而是直接由虚拟机加载。可以使用这些开噶UN有选择的启用或者禁用这些类中的断言。
然而,启用和禁用所有断言的-ea 和-da开关不能应用到那么没有类加载器的系统类上。对于这些系统类来说,需要使用-enablesystemassertions 或者-esa来启用断言
在程序中也可以控制类加载器的断言状态。(适用与IDE)
java.lang.ClassLoader
void setDefaultAssertionStatus(boolean b)
对于通过类加载器加载的所有类来说,如果没有显示的说明类或者包的断言状态,就启用或者禁用断言。
void setClassAssertionStatus(String ClassName,boolean b)
对于给定的类和它的内部类,启用或者禁用断言
void setPackageAssertionStatus(String packageName,boolean b)
对于给定包和其子包的所有类,启用或者禁用断言
void clearAssertionStatus()
移去所有类或者包的显式断言状态设置,并禁用所有通过这个类加载器加载的类的断言
一、概述
在C和C++语言中都有assert关键,表示断言。
在Java中,同样也有assert关键字,表示断言,用法和含义都差不多。
二、语法
在Java中,assert关键字是从JAVA SE 1.4 引入的,为了避免和老版本的Java代码中使用了assert关键字导致错误,Java在执行的时候默认是不启动断言检查的(这个时候,所有的断言语句都将忽略!),如果要开启断言检查,则需要用开关-enableassertions或-ea来开启。
assert关键字语法很简单,有两种用法:
1、assert
下面是一些Assert的例子:
assert 0 < value;
assert 0 < value:”value=”+value;
assert ref != null:”ref doesn”t equal null”;
assert isBalanced();
AssertinError类是Error的直接子类,因此代表程序出现了严重的错误,这种异常通常是不需要程序员使用catch语句捕捉的。
使用assert的准则:assert语句的作用是保证程序内部的一致性,而不是用户与程序之间的一致性,所以不应用在保证命令行参数的正确性。可以用来保证传递给private方法参数的正确性。因为私有方法只是在类的内部被调用,因而是程序员可以控制的,我们可以预期它的状态是正确和一致的。公有方法则不适用。此外,assert语句可用于检查任何方法结束时状态的正确性,及在方法的开始检查相关的初始状态 等等。
assert语句并不构成程序正常运行逻辑的一部分,时刻记住在运行时它们可能不会被执行。
两类参数:
参数 -esa和 -dsa:
它们含义为开启(关闭)系统类的assertion功能。由于新版本的Java的系统类中,也使了 assertion语句,因此如果用户需要观察它们的运行情况,就需要打开系统类的assertion功能 ,我们可使用-esa参数打开,使用 -dsa参数关闭。 -esa和-dsa的全名为-enablesystemassertions和-disenablesystemassertions,全名和缩写名有同样的功能。
参数 -ea和 -ea:
它们含义为开启(关闭)用户类的assertion功能:通过这个参数,用户可以打开某些类或包的assertion功能,同样用户也可以关闭某些类和包的assertion功能。打开assertion功能参数为-ea;如果不带任何参数,表示打开所有用户类;如果带有包名称或者类名称,表示打开这些类或包;如果包名称后面跟有三个点,代表这个包及其子包;如果只有三个点,代表无名包。关闭 assertion功能参数为-da,使用方法与-ea类似。
-ea和-da的全名为-enableassertions和-disenableassertions,全名和缩写名有同样的功能。
下面表格表示了参数及其含义,并有例子说明如何使用。
参数 例子 说明
-ea java -ea 打开所有用户类的assertion
-da java -da 关闭所有用户类的assertion
-ea: java -ea:MyClass1 打开MyClass1的assertion
-da: java -da: MyClass1 关闭MyClass1的assertion
-ea: java -ea:pkg1 打开pkg1包的assertion
-da: java -da:pkg1 关闭pkg1包的assertion
-ea:… java -ea:… 打开缺省包(无名包)的assertion
-da:… java -da:… 关闭缺省包(无名包)的assertion
-ea:… java -ea:pkg1… 打开pkg1包和其子包的assertion
-da:… java -da:pkg1… 关闭pkg1包和其子包的assertion
-esa java -esa 打开系统类的assertion
-dsa java -dsa 关闭系统类的assertion
不要再public的方法里面检查参数是不是为null之类的操作,例如:
public int get(String s){
assert s != null;
}
如果需要检查也最好通过 if s = null 抛出 NullPointerException来检查。
不要用assert来检查方法操作的返回值来判断方法操作的结果,例如:
assert list.removeAll();这样看起来好像没有问题 但是想想如果assert 被disable呢,那样他就不会被执行了,所以removeAll()操作就没有被执行,可以这样代替
boolean boo = list.removeAl();
assert boo;
另外,Java为了让程序也能够动态开启和关闭某些类和包的assertion功能,Java修该了Class和ClassLoader的实现,增加了几个用于操作assert的API。下面简单说明一下几个API的作用。
ClassLoader类中的几个相关的API:
setDefaultAssertionStatus:用于开启/关闭assertion功能
setPackageAssertionStatus:用于开启/关闭某些包的assertion功能
setClassAssertionStatus: 用于开启/关闭某些类的assertion功能
clearAssertionStatus:用于关闭assertion功能
基本日志
要生成简单的日志记录,可以使用全局日志记录器(global logger)并调用其info方法
Logger.getGlobal().info("hello");
如果在适当的地方调用
Logger.getGlobal().setLevel(Level.OFF);
将会取消所有的日志
高级日志
可以调用getLogger方法来串讲或者获取记录器
public static final Logger mylogger=Logger.getLogger("helo");
未被任何变量引用的日志记录其可能会被垃圾回收,所以要用一个静态变量存储日志记录器的一个引用
与包名类似,日志记录器名也有层次结构。事实上,与包名相比,日志记录器的层次性更强。对于包名来说,一个包的名字与其父包的名字之间没有语义关系,但是日志记录器的父与子之间将共享某些属性。例如:如果对com.mycompany日志记录器设置了日志级别,他的子记录器也会继承这个级别
通常,有以下7个日志记录器级别
logger.setLevel(Logger.FINE);
setLevel
public void setLevel(Level newLevel)
throws SecurityException
设置日志级别,指定该记录器将记录哪些消息级别。 消息级别低于此值将被丢弃。 级别值Level.OFF可用于关闭日志记录。
如果新级别为空,则表示该节点应从具有(非空)级别值的最近祖先继承其级别。
参数
newLevel - 日志级别的新值(可能为空)
现在,FINE和更高级别的记录都可以记录下来。
另外,还可以使用Level.ALL开启所有级别的记录,或者使用Level.OFF关闭所有级别的记录。
对于所有级别有以下几种记录方法
logger.warning(message)
warning
public void warning(String msg)
记录警告消息。
如果当前为“警告”消息级别启用了记录器,则给定的消息将转发给所有注册的输出处理程序对象。
logger.fine(Level.FINE,message); //???
默认的日志配置记录了INFO以及以上跟高级别的所有记录。因此,应该使用CONFIG,FINE、FINER,FINEST记录级别来记录那些有助于诊断,但对程序员没有太大意义的调试信息。
如果将记录级别设计为INFO或者更低,则需要修改日志处理器的配置。默认的日志处理器不会处理低于INFO级别的信息
默认的日志记录将显示包含日志调用的类名和方法名,如同堆栈队显示的信息那样。但是,如果虚拟机对执行过程进行了优化,就得不到准确的调用信息。此时,可以调用logp方法获得调用类的方法和确切位置,这个方法的签名为:
void logp(Level 1,String className , String methodName , String message)
logp
public void logp(Level level,
String sourceClass,
String sourceMethod,
String msg)
记录消息,指定源类和方法,没有参数。
如果当前对于给定的消息级别启用了记录器,则给定的消息将转发给所有注册的输出处理程序对象。
参数
level - 消息级标识符之一,例如SEVERE
sourceClass - 发出日志记录请求的类的名称
sourceMethod - 发出日志记录请求的方法的名称
msg - 字符串消息(或消息目录中的键)
下面有一些用来跟踪执行流的方法:
void entering(String className, String methodName);
void entering(String className, String methodName,Object param)
void entering(String className,String methodName,Object[] params);
void exiting(String className,String methodName);
void exiting(String className,String methodName,Object result);