一、异常处理概述
异常是从方法中抛出的。方法的调用者可以捕获以及处理该异常。
try-throw-catch块的模板:
try{
Code to run;
A statement or a method that may throw an exception;
More code to run;
}
catch (type ex){ //type指定了catch块可以捕获的异常类型,一旦捕获该异常就能从catch块体中 的参数访问这个抛出的值
Code to process the exception;
}
二、异常类型
异常是对象,而对象都采用类来定义。异常的根类是java.lang.Throwable。
Throwable类是所有异常类的根。所有的Java异常类都直接或者间接地继承自Throwable。可以通过继承Exception或者Exception的子类来创建自己的异常类。
异常类主要可以分为:系统错误、异常、运行时异常
系统错误是由Java虚拟机抛出的,用Error类表示。Error类描述的是内部系统错误。这样的错误很少发生,除了通知用户以及尽量稳妥地终止程序外,几乎什么也不能做。
类 | 可能引起异常的原因 |
LinkageError | 一个类对另一个类有某种依赖性,但是在编译前者后,后者进行了修改,变得不兼容 |
VirtualMachineError | Java虚拟机崩溃,或者运行所必需的资源已经耗尽 |
异常是Exception类表示的,它描述的是由程序和外部环境所引起的错误,这些错误能被程序捕获和处理。
类 | 可能引起异常的原因 |
ClassNotFoundException | 试图使用一个不存在的类。 |
IOException | 同输入/输出相关的操作,例如,无效的输入、读文件时超过文件尾、打开一个不存在的文件等。 |
运行时异常是用RuntimeException类表示的,它描述的是程序设计错误,如:错误的类型转换、访问一个越界数组或数值错误。运行时异常通常由Java虚拟机抛出的。
类 | 可能引起异常的原因 |
ArithmeticException | 一个整数除以0。注意,浮点数的算术运算不抛出异常。 |
NullPointerException | 试图通过一个null引用变量访问一个对象 |
IndexOutOfBoundsException | 数组的下标超出范围 |
IllegalArgumentException | 传递给方法的参数非法或不合适 |
RuntimeException、Error以及它们的子类都称为免检异常。所有的其他异常都称为必检异常,意思是指编译器会强制程序员检查并通过try-catch块来处理它们,或者在方法头进行声明。
三、关于异常处理的更多知识
异常的处理器是通过从当前的方法开始,沿着方法调用链,按照异常的方向传播方向找到的。
Java的异常处理模型基于三种操作:声明一个异常、抛出一个异常、捕获一个异常
1、声明异常
在Java中,当前执行的语句必属于某个方法。Java解释器调用main方法开始执行一个程序。每个方法都必须声明它可能抛出的必检异常的类型。这称为声明异常。因为任何代码都可能发生系统错误和运行时错误,因此Java不要求在方法中显示声明RuntimeException、Error。但是方法要抛出的其他异常都必须在方法头中显示声明。
在方法中声明一个异常,就要在方法投中使用关键字throws,如下所示:
public void myMethod() throws IOException
关键词throws表明myMethod方法可能会抛出异常IOException。如果方法可能会抛出多个异常,就可以在关键字throws后添加一个逗号分隔的异常列表:
public void myMethod() throws Exception1,Exception2,Exception3
如果方法没有在父类中声明异常,那么就不能在子类中对其进行继承来声明异常。
2、抛出异常
检测到错误的程序可以创建一个合适的异常类型的实例并抛出它,称为抛出一个异常。
eg:传递给方法的参数与方法的合约不符,可以创建IllegalArgumentException的实例并抛出它:
IllegalArgumentException ex=new IllegalArgumentException("Wrong Argument");
throw ex;
或:throw new IllegalArgumentException("Wrong Argument");
Java API中的每个异常类至少有两个构造方法:一个是无参构造方法和一个带可描述这个异常的String参数的构造方法。该参数称为异常消息,可以用getMessage()获取
3、捕获异常
当抛出一个异常时,可以在try-catch块中捕获和处理它:
try{
statements;// statements that may throw exceptions
}
catch(Exception1 exVar1){
handler for exception1;
}
catch(Exception2 exVar2){
handler for exception2;
}
如果在执行try块的过程中没有出现异常,则跳过catch子句。
如果try块中的某条语句抛出一个异常,Java就会跳过try块中剩余的语句,然后开始查找处理这个异常的代码的过程。处理这个异常的代码称为异常处理器。寻找处理器的过程称为捕获一个异常。
从一个通用的父类可以派生出各种异常类。如果一个catch块可以捕获一个父类的异常对象,它就能捕获那个父类的所有子类的异常对象。
对于使用同样的处理代码处理多个异常的情况,简化语法是:
catch (Exception1 | Exception2 | Exception3 ex){
//Same code for handing these exceptions
}
4、从异常中获取信息
异常对象中包含关于异常的有价值的信息。可以利java.lang.Throwable类中的实例方法来获取有关异常的信息。
java.lang.Throwable | |
+getMessage():String | 返回描述该异常对象的信息 |
+toString():String | 返回三个字符串的连接:1、异常类的全名;2、":"(一个冒号和空白)3、getMessage(方法) |
+printStackTrace():void | 在控制台上打印Throwable对象和它的调用堆栈信息 |
+getStackTrace(): StrackTracceElement[] | 返回和该异常对象相关的代表堆栈跟踪的一个堆栈跟踪元素的数组 |
四、finally子句
无论异常是否产生,finally子句总是或被执行。
finally子句语法:
try{
statements;
}
catch(TheException ex){
handling ex;
}
finally{
finalStatements;
}
使用finally子句时可以省略catch块。
五、何时使用异常
当错误需要被方法的调用者处理时,方法应该抛出一个异常。
异常处理通常需要更多的时间和资源。当处理不可预料的错误状况时应该使用它。
六、重新抛出异常
如果异常处理器不能处理一个异常,或者只是简单的希望它的调用者注意到该异常,Java允许该异常处理器重新抛出异常。
重新抛出异常的语法:
try{
statements;
}
catch(TheException ex){
perform operation before exits;
throw ex;
}
语句throw ex 重新抛出异常给调用者,以便调用者的其他处理器获得处理异常ex的机会。
七、链式异常
和其他异常一起抛出一个异常,构成了链式异常。
catch块可能重新抛出原始的异常。可能需要同原始异常一起抛出一个新异常,这称为链式异常。
八、创建自定义异常类
可以通过派生java.lang.Exception类来定义一个自定义异常类。
Java提供了相当多的异常类,尽量使用它们而不要创建自己的异常类。然而,如果遇到一个不能用预定义异常类描述的问题,可以通过派生Exception类或其子类。
九、File类
File类包含了获得一个文件/目录的属性,以及对文件/目录进行改名和删除的方法。
在文件系统中,每个文件都存放在一个目录下。绝对文件名是由文件名和它的完整路径以及驱动器字母组成。
相对文件名是相对于当前工作目录的。
File类意图提供了一种抽象,这种抽象是指以不依赖机器的方式来处理很多依赖于机器的文件和路径名的复杂性。
java.io.File | |
+File(pathname:String) | 为一个指定的路径名创建一个File对象。路径名可能是一个目录或者文件 |
+File(parent:String,child:String) | 在目录parent下创建一个子路径的File对象,子路径可能是一个目录或者文件 |
+File(parent:File,child:String) | 在目录parent下创建一个子路径的File对象。parent是一个File对象。之前的构造方法中,parent是一个字符串 |
+exists():boolean | File对象代表的文件和目录存在,返回true |
+canRead():boolean | File对象代表的文件存在且可读,返回true |
+canWrite():boolean | File对象代表的文件存在且可写,返回true |
+isDirectory():boolean | File对象代表的是一个目录,返回true |
+isFile():boolean | File对象代表的是一个文件,返回true |
+isAbsolute():boolean | File对象采用绝对路径名创建,返回true |
+isHidden():boolean | 如果File对象代表的文件是隐藏的,返回true,隐藏的确切定义是系统相关的。Windows系统中,可以在文件属性对话框中标记一个文件隐藏。Unix系统中,如果文件以字符(.)开始,则文件是隐藏的 |
+getAbsolutePath():String | 返回File对象代表的文件和目录的完整绝对路径名 |
+getCanonicalPath():String | 和getAbsolutePath()返回相同,除了从路径名去掉了冗余的名字,比如"."和".."以及解析符链接(Unix中),将盘符转换为标准的大写形式(Windows中) |
+getName():String | 返回File对象代表的目录和文件名的最后名字。 |
+getPath():String | 返回File对象代表的完整的目录和文件名。 |
+getParent():String | 返回File对象代表的当前目录和文件完整的父目录。 |
+lastModified():long | 返回文件最后修改时间 |
+length():long | 返回文件的大小,如果不存在或者是一个目录的话,返回0 |
+listFile():File[] | 返回一个目录File对象下面的文件 |
+delete():boolean | 删除File对象代表的文件或目录。如果删除成功,方法返回true |
+renameTo(dest:File):boolean | 将该File对象代表的文件或者目录改名为dest中指定的名字。如果操作成功,方法返回true |
+mkdir():boolean | 创建该File对象代表的目录。如果目录成功创建,则返回true |
+mkdirs():boolean | 和mkdir()相同,除开在父目录不存在的情况下,将和父目录一起创建 |
在Windows中目录的分隔符是反斜杠(\)。但是在Java中,反斜杠是一个特殊的字符,应该写成\\的形式。
构建一个File实例并不会在机器上创建一个文件。不管文件是否存在,都可以创建任意文件名的File实例。可以调用File实例上的exists()方法来判断这个文件是否存在。
在程序中,不要直接使用绝对文件名。如果使用了像c:\\book\\Welcome.java之类的文件名那么它能在Windows上工作,但是不能在其他平台上工作。应该使用与当前目录相关的文件名。eg:
可以使用new File(Welcome.java)为在当前目录下的文件Welcome.java创建一个File对象。
可以使用new File("image/us.gif")为在当前目录下image目录下的文件us.gif创建一个File对象。
斜杠/是Java的目录分隔符,和Unix一样。
十、文件输入和输出
使用Scanner类从文件中读取文件数据,使用PrintWriter类向文本文件写入数据。
1、使用PrintWriter写数据
java.io.PrintWriter类可以用来创建一个文件并向文本文件写入数据。首先必须为一个文本文件创建一个PrintWriter对象,如:PrintWriter output = new PrintWriter(filename);
然后可以调用PrintWriter对象上的方法来向文件中写入数据。
java.io.PrintWriter | |
+PrintWriter(file:File) | 为指定的文件对象创建一个PrintWriter对象 |
+PrintWriter(filename:String) | 为指定的文件名字符串对象创建一个PrintWriter对象 |
+print(s:String):void | 将字符串写入文件中 |
+print(c:Char):void | 将字符写入文件中 |
+print(cArray:Char[]):void | 将字符数组写入文件中 |
+print(i:int):void | 将一个int值写入文件中 |
+print(l:long):void | 将一个long值写入文件中 |
+print(f:float):void | 将一个float值写入文件中 |
+print(d:double):void | 将一个double值写入文件中 |
+print(b:boolean):void | 将一个boolean值写入文件中 |
也包含重载的println方法 | println方法和print方法类似;额外的,它打印一个换行。换行字符串由系统定义。 |
也包含重载的printf方法 | printf方法在第四章中介绍 |
eg:
public class WriteDate {
public static void main(String[] args) throws IOException {
java.io.File file = new java.io.File("scores.txt");
if(file.exists()){
System.out.println("File already exists");
System.exit(1);//退出程序
}
java.io.PrintWriter output = new java.io.PrintWriter(file);
output.print("Hello");
output.close();//关闭文件,没有数据无法正确的保存在文件中
}
}
2、使用try-with-resources自动关闭资源
用法:
public class WriteDate {
public static void main(String[] args) throws IOException {
java.io.File file = new java.io.File("scores.txt");
if(file.exists()){
System.out.println("File already exists");
System.exit(1);//退出程序
}
try(
java.io.PrintWriter output = new java.io.PrintWriter(file);
){
output.print("Hello");
output.close();//关闭文件,没有数据无法正确的保存在文件中
}
}
}
3、使用Scanner读数据
java.util.Scanner类用来从控制台读取字符串和基本类型数值。
Scanner可以将输入分为由空白字符分隔的标记。
为了能从键盘读取,需要为System.in创建一个Scanner,如:Scanner input = new Scanner(System.in);
为了能从文件中读取,需要为文件创建一个Scanner,如:Scanner input = new Scanner(new File(filename));
java.util.Scanner | |
+Scanner(source:File) | 创建一个Scanner,从指定的文件中扫描标记 |
+Scanner(source:String) | 创建一个Scanner,从指定的字符串中扫描标记 |
+close() | 关闭该Scanner |
+hasNext():boolean | 如果Scanner还有更多数据读取,则返回true |
+next():String | 从该Scanner中读取下一行标记作为字符串返回 |
+nextLine():String | 从该Scanner中读取一行,以换行结束 |
+nextByte():byte | 从该Scanner中读取下一个标记作为byte值返回 |
+nextShort():short | 从该Scanner中读取下一个标记作为short值返回 |
+nextInt():int | 从该Scanner中读取下一个标记作为int值返回 |
+nextLong():long | 从该Scanner中读取下一个标记作为long值返回 |
+nextFloat():float | 从该Scanner中读取下一个标记作为float值返回 |
+nextDouble():double | 从该Scanner中读取下一个标记作为double值返回 |
+useDelimiter(pattern:String):Scanner | 设置该Scanner的分隔符,并且返回该Scanner |
4、Scanner如何工作
Scanner的方法都称为标记读取方法,因为它们会读取用分隔符分隔开的标记。默认情况下,分隔符是空格。可以使用useDelimiter(String regex)方法设置新的分隔符模式。
行分隔符字符串是由系统定义的,在Windows平台上是\r\n,而在UNIX平台上是\n。为了得到特定平台的行分隔符,使用:String lineSeparator = System.getProperty("line separator");
如果从键盘输入,每行就以回车键结束,它对应于\n字符。
标记读取方法不能读取标记后面的分隔符。如果在标记读取方法之后调用nextLine(),该方法读取从这个分隔符开始,到这行的分隔符结束的字符。这个行分隔符也被读取,但它不是nextLine()返回的字符串部分。
十一、从Web上读取数据
为了读取一个文件,首先使用java.net.URL类的这个构造方法,为该文件创建一个URL对象。
public URL(String spec) throws MalformedURLException
eg:
try{
URL url = new URL("http://www.");//http://前缀是必须的
}
catch (MalformedURLException ex){
ex.printStackTrace();
}
创建一个URL对象后,可以使用URL类中定义的openStream()方法来打开输入流和用输入流创建如下Scanner对象。
Scanner input = new Scanner(url.openStream());
之后就如同本地文件中读取数据一样。