本謎題測(cè)試的是你對(duì)某些規(guī)則的掌握程度,這些規(guī)則用于聲明從方法中拋出并被catch語句塊所捕獲的異常。下面的三個(gè)程序每一個(gè)都會(huì)打印些什么?不要假設(shè)它們都可以通過編譯:
import java.io.IOException;
public class Arcane1 {
public static void main(String[] args) {
try {
System.out.println("Hello world");
} catch(IOException e) {
System.out.println("I’ve never seen
println fail!");
}
}
}
public class Arcane2 {
public static void main(String[] args) {
try {
// If you have nothing nice to say, say nothing
} catch(Exception e) {
System.out.println("This can’t
happen");
}
}
}
interface Type1 {
void f() throws CloneNotSupportedException;
}
interface Type2 {
void f() throws InterruptedException;
}
interface Type3 extends Type1, Type2 {
}
public class Arcane3 implements Type3 {
public void f() {
System.out.println("Hello world");
}
public static void main(String[] args) {
Type3 t3 = new Arcane3();
t3.f();
}
}
第一個(gè)程序,Arcane1,展示了被檢查異常的一個(gè)基本原則。它看起來應(yīng)該是可以編譯的:try子句執(zhí)行I/O,并且catch子句捕獲IOException異常。但是這個(gè)程序不能編譯,因?yàn)閜rintln方法沒有聲明會(huì)拋出任何被檢查異常,而IOException卻正是一個(gè)被檢查異常。語言規(guī)范中描述道:如果一個(gè)catch子句要捕獲一個(gè)類型為E的被檢查異常,而其相對(duì)應(yīng)的try子句不能拋出E的某種子類型的異常,那么這就是一個(gè)編譯期錯(cuò)誤[JLS 11.2.3]。
基于同樣的理由,第二個(gè)程序,Arcane2,看起來應(yīng)該是不可以編譯的,但是它卻可以。它之所以可以編譯,是因?yàn)樗腸atch子句檢查了Exception。盡管JLS在這一點(diǎn)上十分含混不清,但是捕獲Exception或Throwble的catch子句是合法的,不管與其相對(duì)應(yīng)的try子句的內(nèi)容為何。盡管Arcane2是一個(gè)合法的程序,但是catch子句的內(nèi)容永遠(yuǎn)的不會(huì)被執(zhí)行,這個(gè)程序什么都不會(huì)打印。
第三個(gè)程序,Arcane3,看起來它也不能編譯。方法f在Type1接口中聲明要拋出被檢查異常CloneNotSupportedException,并且在Type2接口中聲明要拋出被檢查異常InterruptedException。Type3接口繼承了Type1和Type2,因此,看起來在靜態(tài)類型為Type3的對(duì)象上調(diào)用方法f時(shí),有潛在可能會(huì)拋出這些異常。一個(gè)方法必須要么捕獲其方法體可以拋出的所有被檢查異常,要么聲明它將拋出這些異常。Arcane3的main方法在靜態(tài)類型為Type3的對(duì)象上調(diào)用了方法f,但它對(duì)CloneNotSupportedException和InterruptedExceptioin并沒有作這些處理。那么,為什么這個(gè)程序可以編譯呢?
上述分析的缺陷在于對(duì)“Type3.f可以拋出在Type1.f上聲明的異常和在Type2.f上聲明的異?!彼龅募僭O(shè)。這并不正確,因?yàn)槊恳粋€(gè)接口都限制了方法f可以拋出的被檢查異常集合。一個(gè)方法可以拋出的被檢查異常集合是它所適用的所有類型聲明要拋出的被檢查異常集合的交集,而不是合集。因此,靜態(tài)類型為Type3的對(duì)象上的f方法根本就不能拋出任何被檢查異常。因此,Arcane3可以毫無錯(cuò)誤地通過編譯,并且打印Hello world。
總之,第一個(gè)程序說明了一項(xiàng)基本要求,即對(duì)于捕獲被檢查異常的catch子句,只有在相應(yīng)的try子句可以拋出這些異常時(shí)才被允許。第二個(gè)程序說明了這項(xiàng)要求不會(huì)應(yīng)用到的冷僻案例。第三個(gè)程序說明了多個(gè)繼承而來的throws子句的交集,將減少而不是增加方法允許拋出的異常數(shù)量。本謎題所說明的行為一般不會(huì)引發(fā)難以捉摸的bug,但是你第一次看到它們時(shí),可能會(huì)有點(diǎn)吃驚。
import java.io.IOException;
public class Arcane1 {
public static void main(String[] args) {
try {
System.out.println("Hello world");
} catch(IOException e) {
System.out.println("I’ve never seen
println fail!");
}
}
}
public class Arcane2 {
public static void main(String[] args) {
try {
// If you have nothing nice to say, say nothing
} catch(Exception e) {
System.out.println("This can’t
happen");
}
}
}
interface Type1 {
void f() throws CloneNotSupportedException;
}
interface Type2 {
void f() throws InterruptedException;
}
interface Type3 extends Type1, Type2 {
}
public class Arcane3 implements Type3 {
public void f() {
System.out.println("Hello world");
}
public static void main(String[] args) {
Type3 t3 = new Arcane3();
t3.f();
}
}
第一個(gè)程序,Arcane1,展示了被檢查異常的一個(gè)基本原則。它看起來應(yīng)該是可以編譯的:try子句執(zhí)行I/O,并且catch子句捕獲IOException異常。但是這個(gè)程序不能編譯,因?yàn)閜rintln方法沒有聲明會(huì)拋出任何被檢查異常,而IOException卻正是一個(gè)被檢查異常。語言規(guī)范中描述道:如果一個(gè)catch子句要捕獲一個(gè)類型為E的被檢查異常,而其相對(duì)應(yīng)的try子句不能拋出E的某種子類型的異常,那么這就是一個(gè)編譯期錯(cuò)誤[JLS 11.2.3]。
基于同樣的理由,第二個(gè)程序,Arcane2,看起來應(yīng)該是不可以編譯的,但是它卻可以。它之所以可以編譯,是因?yàn)樗腸atch子句檢查了Exception。盡管JLS在這一點(diǎn)上十分含混不清,但是捕獲Exception或Throwble的catch子句是合法的,不管與其相對(duì)應(yīng)的try子句的內(nèi)容為何。盡管Arcane2是一個(gè)合法的程序,但是catch子句的內(nèi)容永遠(yuǎn)的不會(huì)被執(zhí)行,這個(gè)程序什么都不會(huì)打印。
第三個(gè)程序,Arcane3,看起來它也不能編譯。方法f在Type1接口中聲明要拋出被檢查異常CloneNotSupportedException,并且在Type2接口中聲明要拋出被檢查異常InterruptedException。Type3接口繼承了Type1和Type2,因此,看起來在靜態(tài)類型為Type3的對(duì)象上調(diào)用方法f時(shí),有潛在可能會(huì)拋出這些異常。一個(gè)方法必須要么捕獲其方法體可以拋出的所有被檢查異常,要么聲明它將拋出這些異常。Arcane3的main方法在靜態(tài)類型為Type3的對(duì)象上調(diào)用了方法f,但它對(duì)CloneNotSupportedException和InterruptedExceptioin并沒有作這些處理。那么,為什么這個(gè)程序可以編譯呢?
上述分析的缺陷在于對(duì)“Type3.f可以拋出在Type1.f上聲明的異常和在Type2.f上聲明的異?!彼龅募僭O(shè)。這并不正確,因?yàn)槊恳粋€(gè)接口都限制了方法f可以拋出的被檢查異常集合。一個(gè)方法可以拋出的被檢查異常集合是它所適用的所有類型聲明要拋出的被檢查異常集合的交集,而不是合集。因此,靜態(tài)類型為Type3的對(duì)象上的f方法根本就不能拋出任何被檢查異常。因此,Arcane3可以毫無錯(cuò)誤地通過編譯,并且打印Hello world。
總之,第一個(gè)程序說明了一項(xiàng)基本要求,即對(duì)于捕獲被檢查異常的catch子句,只有在相應(yīng)的try子句可以拋出這些異常時(shí)才被允許。第二個(gè)程序說明了這項(xiàng)要求不會(huì)應(yīng)用到的冷僻案例。第三個(gè)程序說明了多個(gè)繼承而來的throws子句的交集,將減少而不是增加方法允許拋出的異常數(shù)量。本謎題所說明的行為一般不會(huì)引發(fā)難以捉摸的bug,但是你第一次看到它們時(shí),可能會(huì)有點(diǎn)吃驚。