java day7
第七章 异常和线程
- 7.1 异常
-
- 异常类
- 7.2 管理异常
-
- 7.2.1 异常一致性检测
- 7.2.2 保护代码和捕获异常
- 7.2.3 finally 子句
- 7.3 声明可能引发异常的方法
-
- 7.3.1 throws 子句
- 7.3.2 应引发哪些异常
- 7.3.3 传递异常
- 7.3.4 throws和继承
- 7.4 创建并引发自己的异常
-
- 7.4.1 引发异常
- 7.4.2 创建自己的异常
- 7.4.3 结合使用throws、try和throw
- 7.5 在什么情况下不使用异常
-
- 7.5.1 糟糕的异常使用方式
- 7.6 线程
-
- 7.6.1 编写线程化程序
- 7.6.2 线程化应用程序
- 7.6.3 终止线程
7.1 异常
在Java中,导致程序出错的奇怪事件被称为异常。
如果使用if…case和switch…case 块等处理异常,这些错误管理代码会导致java类难以阅读和维护。且用此方法来处理错误,将无法让编译器采用一致的方式进行如下检查:调用方法时是否使用了正确的参数;是否将变量设置成了正确的对象。
异常类
1.异常: 发生异常时,应用程序可能退出,并且屏幕上会显示一组难以理解的错误。这些错误就是异常。
2. 引发异常:程序没有完成其工作就退出时,说明引发了异常。
3. 捕获异常:捕获异常指的是对异常情况进行处理,以避免程序崩溃,后面将对此做更详细的介绍。
在Java中,异常是对象,即它们是从类Throwable派生而来的类的实例。当异常被引发时,将创建Throwable类的一个实例。Throwable有两个子类:Error和Exception。
- Error实例是JVM内部的错误,这种错误很少见,但通常是致命的,对于它们,您无能为力(要么捕获它们,要么引发它们)。
- Exception类与编程的关系更为紧密。Exception的子类分为两组。
- unchecked异常(RuntimeException类的子类),如ArrayIndexOutofBounds、SecurityException和NullPointeerException。
- checked异常,如EOFException和MalformedURLException。
unchecked异常也叫运行阶段异常,通常是由于代码不够健壮造成的。
如果程序导致unchecked异常,应通过改善代码来消除这些问题。对于可在创建Java程序时消除的编程错误,不要依赖于异常管理来处理
主要的异常类(Throwable、Exception和RuntimeException)都位于java.lang包中。在Java类库中,还有很多其他的包定义了其他异常,这些异常用于整个类库中。
7.2 管理异常
java编译器将要求您对异常进行管理:您必须在代码中对这些异常进行处理,否则程序将不能通过编译。
7.2.1 异常一致性检测
编译器以相同的方式对代码进行检测,以确保您调用方法时提供的参数数目和类型都是正确的。它降低了程序由于致命错误而崩溃的可能性,因为您将预先知道程序使用的方法将引发什么样的异常。
7.2.2 保护代码和捕获异常
假设您编写代码,并在编译时遇到异常消息。根据该消息,您必须捕获这种错误或声明方法必须引发它。
首先捕获可能的异常,这需要完成两项工作:
- 将包含可能引发异常的方法的代码放在try块中
- 在catch块中对异常进行处理
try{// ....}
catch(NumberFormatException nfe){// ...}
catch(IOException | FileNotFoundException exp){// ...
}
7.2.3 finally 子句
假设不管发生什么情况,也无论异常是否被引发,代码中的一些操作必须都被执行。这通常是释放外部资源,关闭打开的文件等。虽然可以同时将执行这些操作的代码放在catch块中和块外,但编程时应尽可能避免将代码放在两个不同的地方。相反,应将这种代码放在try…catch块的可选部分finally中
try {readTextFile();
} catch(IOException ioe){// deal with IO errors
} finally {closeTextFile();
}
7.3 声明可能引发异常的方法
前面介绍了如何处理可能引发异常的方法(通过保护代码并捕获可能发生的异常)。Java编译器将进行检查,确保您对方法的异常进行了处理,但它如何知道需要处理哪些异常呢?
要指出方法可能引发的异常,可在方法定义中使用特殊子句throws。
7.3.1 throws 子句
要指出方法中的某些代码可能引发异常,只需在方法的右括号后面加上关键字throws和将引发的异常的名称,例如:
public void getPoint(int x,int y) throws NumberFormatException,EOFException{// body of method
}
记住,将关键字throws加入到方法定义中只是意味着:如果发生错误,该方法可能引发异常,而不是发生这种情况。throws子句只是在方法定义提供了有关潜在异常的信息,并让java能够确保其他类正确地使用该方法。
7.3.2 应引发哪些异常
决定指出方法可能引发异常后,必须判断它可能引发哪些异常,并实际引发它们或调用一个将引发它们的方法。
具体地说,在throw子句中,无须列出类Error、RuntimeException(或其子类)的异常。这是因为这些异常可能发生在Java程序中的任何地方,而且通常这不是由程序员直接造成的。
所有其他异常都是需要检查的异常,您可以在方法的throws子句中列出它们。
7.3.3 传递异常
有时候,在方法中对某个异常进行处理是不合理的,由于调用该方法的方法进行处理更合适。
7.3.4 throws和继承
如果您的方法定义覆盖率超类中包含throws子句的方法,则对于覆盖方法如何处理throws,有一些特殊的规则。
public class HexReader {public void startPlaying() throws SoundException{// body of method}
}public class StereoPlayer extends HexReader{public void startPlaying(){// body of method}
}
新方法可以引发更少的异常,甚至可以不引发任何异常。但反过来不成立:子类方法不能引发其超类方法没有引发的checked异常,包括其他类型的异常和更笼统的异常。
7.4 创建并引发自己的异常
每个异常都有两面:引发和捕获。被捕获前,异常可能被多次传递给多个方法,但最终它将被捕获并得到处理。
7.4.1 引发异常
要引发异常,必须创建该异常类的实例,然后使用throw语句来引发它。
NotInServiceException nise = new NotInServiceException();
throw nise;
异常的构造函数可能接受参数,这取决于您使用的异常类。最常见的参数是字符串,它让您能够更详细地描述问题。
NotInServiceException nise = new NotInServiceException("Database Not in Service");
throw nise;
7.4.2 创建自己的异常
所有用户创建的异常都应是Exception而非Error层次结构的一部分,后者用于表示涉及JVM的错误。
异构类通常有两个构造函数:第一个接受任何参数;第二个接受一个字符串参数。对于后一种构造函数,应在其中调用super(),以确保该字符串被应用到正确的地方。
public class SunSpotException extends Exception{public SunSpotException(){}public SunSpotException(String message){super(message);}
}
7.4.3 结合使用throws、try和throw
仅仅使用try和catch无法传递异常,而仅仅使用throws子句则无法处理异常。
要管理异常并把它传递给调用方法,需要使用3种机制:throws子句、try语句和throw语句(显示的引发异常)
public void readMessage() throws IOException{MessageReader mr = new MessageReader();try{mr.loadHeader();}catch (IOException e){// do something to handle the// IO exception and then rethrow// the exceptionthrow e;}
}
在上述情况下:在文件阅读器中使用了一条try-catch语句来捕获IOException;由于阅读到了文件末尾而发生EOFException;该异常被catch块捕获,因为IOException是EOFException的超类。
如果使用了throw来引发异常,它引发的将是IOException,而不是EOFException。java8中引入了一种技术,让您能够引发更精确的异常:在catch语句中指定的异常类型前面使用关键字final。
try{mr.loadHeader();}catch (final IOException e){// do something to handle the// IO exception and then rethrow// the exceptionthrow e;}
}
7.5 在什么情况下不使用异常
- 首先,对于可使用代码轻松避免的情形,不使用异常。
- 此外,如果用户输入的数据必须是整数,则引发异常并在其他地方进行处理相比,对其进行检测以确保它是整数将好得多。
异常将占用大量的处理时间。简单的测试比异常处理的速度快得多,可提高程序的效率。仅当您无法控制异常情况时,才应使用异常。
7.5.1 糟糕的异常使用方式
虽然在方法中加入空的catch子句或throws语句是合法的,但不经处理就将异常丢弃将破坏Java编译器进行的检查工作
7.6 线程
7.6.1 编写线程化程序
在Java中,线程是用包java.lang中的Thread类实现的。
- 最简单的线程用法是,让程序暂停一段时间,并在此期间处于空闲状态。为此,可以调用Thread类的sleep(long)方法,并将要暂停的时间(单位为毫秒)作为参数传递给它。
try{Thread.sleep(3000);
} catch (InterruptedException ie) {// do nothing
}
线程的用途之一是,将所有耗时的行为都放到单独的类中。
创建线程的方式有两种:
- 创建一个从Thread派生而来的类;
- 在另一个类中实现Runnable接口。
创建线程的第一步是创建一个到Thread对象的引用:
Thread runner;
上述语句创建一个线程引用,但没有将Thread对象赋予它。要创建线程,可调用构造函数Thread(Object),并将要线程化的对象作为参数传递给它。下面的语句创建了一个线程化的StockTicker对象:
StockTicker tix = new StockTicker();
Thread tickerThread = new Thread(tix);
适合创建线程的两个地方是应用程序的构造函数和组件的构造函数。
要启动线程,可调用它的start()方法,
tickerThread.start();
Thread runner = null;
if (runner == null){runner =new Thread(this);runneer.start();
}
将对象赋值给变量之前,它的值为null,因此if语句确保线程不会被多次启动。
7.6.2 线程化应用程序
7.6.3 终止线程
终止线程比启动线程复杂些
- 终止线程的最佳方式是,让线程的run()方法的循环在某个变量的值发生变化后结束,如下例所示:
Public void run(){while(okToRun == true){// ...}
}
变量okToRun可以是线程类的实例变量,如果它变成false,则run()方法中的循环将结束。
2. 另一种终止线程的方式是,仅当当前运行的线程有一个指向它的变量时,才执行方法run()中的循环。类方法Thread.currentThread()返回指向当前线程(即对象在其中运行的线程)的引用。只要runner()和currentThread()指向相同的对象,下面的方法run()就将继续循环。
public void run(){Thread thisThread = Thread.currentThread();while(runner == thisThread){// body of loop}
}
如果您使用了类似这样的循环,则可以在类的任何地方使用下面的语句来终止线程:
runner = null;