Thinking in Java from Chapter 11
From Thinking in Java 4th Edition
持有对象
// Simple container example (produces compiler warnings.) // {ThrowsException} import java.util.*; class Apple { private static long counter; private final long id = counter++; public long id() { return id;} } class Orange {} public class ApplesAndOrangesWithoutGenerics { @SuppressWarnings("unchecked") public static void main(String[] args) { ArrayList apples = new ArrayList(); for(int i = 0; i < 3; ++i) apples.add(new Apple()); // Not prevented from adding an Orange to apples: apples.add(new Orange()); for(int i = 0; i < apples.size(); ++i) ((Apple)apples.get(i)).id(); // Orange is detected only at run time } }
抛出一个异常
if(t == null) throw new NullPointerException();
所有标准异常都有两个构造器:一个是默认构造器;另一个是接受字符串作为参数,以便能把相关信息放入异常对象的构造器:
throw new NullPointerException("t = null");
异常类型的根类:Throwable对象
捕获异常
try { // Code that might generate exceptions } catch(Type1 id1) { // Handle exceptions of Type1 } catch(Type2 id2) { // Handle exceptions of Type2 } catch(Type3 id3) { // Handle exceptions of Type3 } // etc...
异常处理必须紧跟在try块之后。当异常被抛出时,异常处理机制将负责搜寻参数与异常类型相匹配的第一个处理程序。然后进入catch自居执行,此时认为异常得到了处理。
建立自己的异常必须从已有的异常类继承:
// Creating your own exceptions. class SimpleException extends Exception {} public class InheritingExceptions { public void f() throws SimpleException { System.out.println("Throw SimpleException from f()"); throw new SimpleException(); } public static void main(String[] args){ InheritingExceptions sed = new InheritingExceptions(); try { sed.f(); } catch(SimpleException e) { System.out.println("Caught it!"); } } } /* Output: Throw SimpleException from f() Caught it! */
也可以为异常类定义一个接受字符串参数的构造器:
class MyException extends Exception { public MyException() {} public MyException(String msg) { super(msg);} } public class FullConstructors { public static void f() throws MyException { System.out.println("Throwing MyException from f()"); throw new MyException(); } public static void g() throws MyException { System.out.println("Throwing MyException from g()"); throw new MyException("Originated in g()"); } public static void main(String[] args){ try { f(); } catch(MyException e) { e.printStackTrace(System.out); } try { g(); } catch(MyException e) { e.printStackTrace(System.out); } } } /* Output: Throwing MyException from f() MyException at FullConstructors.f(FullConstructors.java:11) at FullConstructors.main(FullConstructors.java:19) Throwing MyException from g() MyException: Originated in g() at FullConstructors.g(FullConstructors.java:15) at FullConstructors.main(FullConstructors.java:24) */
为了说明某个方法可能会抛出的异常类型,Java运用了异常说明。它属于方法声明的一部分,紧跟在形式参数列表之后,并使用附加的关键字throws。所以,方法定义可能看起来像这样:
void f() throws TooBig, TooSmall, DivZero { // ... }
但是,要是写成这样:
void f() { // ... }
就表示此方法不会抛出任何异常(除了RuntimeException继承的异常,它们可以在没有异常说明的情况下被抛出。)
捕获所有异常
通过捕获异常类型的基类Exception,就可以捕获所有异常(事实上还有其他的积累,但Exception是同编程活动相关的基类):
catch (Exception e) { System.out.println("Caught an exception"); }
最好将它放在处理程序列表的末尾,以防它抢在其他处理程序之前先把异常捕获了。
可以从Exception的基类Throwable继承的方法:
String getMessage()
String getLocalizedMessage()
用来获取详细信息,活用本地语言表示的详细信息。
String toString()返回对Throwable的简单描述,要是有详细信息的话,也会把它包含在内。
void printStackTrace()
void PrintStackTrace(PrintStream)
void PrintStackTrace(java.io.PrintWriter)
打印Throwable和Throwable的调用栈轨迹。调用栈显示了“把你带到异常抛出地点”的方法调用序列。
也可以使用Throwable从其基类Object继承的方法。对于异常处理,getClass()将返回一个表示此对象类型的信息,然后可以使用getName()方法查询这个Class对象包含包信息的名称,或者使用只产生类名称的getSimpleName()方法。
使用Exception类型的方法:
import static net.mindview.util.Print.*; public class ExceptionMethods { public static void main(String[] args){ try { throw new Exception("My Exception"); } catch (Exception e) { print("Caught Exception"); print("getMessage():" + e.getMessage()); print("getLocalizedMessage()" + e.getLocalizedMessage()); print("toString():" + e); print("printStackTrace():"); e.printStackTrace(System.out); } } }
栈轨迹
printStackTrace()方法所提供的信息可以通过getStackTrace()方法来直接访问,这个方法将:
1. 返回一个由栈轨迹中的元素所构成的数组,其中每个元素都表示栈中的一帧
2. 元素0是栈顶元素,并且是调用序列中的最后一个方法调用
3. 数组中的最后一个元素,即栈底,是调用序列中的第一个方法调用
public class WhoCalled { static void f() { // Generate an exception to fill in the stack trace try { throw new Exception(); } catch(Exception e) { for(StackTraceElement ste : e.getStackTrace()) System.out.println(ste.getMethodName()); } } static void g() { f();} static void h() { g();} public static void main(String[] args){ f(); System.out.println("-----------------------"); g(); System.out.println("-----------------------"); h(); System.out.println("-----------------------"); } } /* Output: f main ----------------------- f g main ----------------------- f g h main */
重新抛出异常会把异常抛给上一级环境中的异常处理程序,同一个try块的后续catch子句将被忽略。
如果只是把异常对象重新抛出,printStackTrace()方法显示的将是原来异常抛出点的调用栈信息。要更新这个信息,可以调用fillIngStackTrace()方法,这将返回一个Throwable对象,它是通过把当前调用栈信息填入原来那个异常对象而建立的:
public class Rethrowing { public static void f() throws Exception { System.out.println("originating the exception in f()"); throw new Exception("thrown from f()"); } public static void g() throws Exception { try { f(); } catch(Exception e) { System.out.println("Inside g(), e.printStackTrace()"); e.printStackTrace(System.out); throw e; } } public static void h() throws Exception { try { f(); } catch(Exception e) { System.out.println("Inside h(), e.printStackTrace()"); e.printStackTrace(System.out); throw (Exception)e.fillInStackTrace(); } } public static void main(String[] args){ try { g(); } catch(Exception e) { System.out.println("main: printStackTrace()"); e.printStackTrace(System.out); } try { h(); } catch(Exception e) { System.out.println("main: printStackTrace()"); e.printStackTrace(System.out); } } } /* Output: originating the exception in f() Inside g(), e.printStackTrace() java.lang.Exception: thrown from f() at Rethrowing.f(Rethrowing.java:7) at Rethrowing.g(Rethrowing.java:11) at Rethrowing.main(Rethrowing.java:29) main: printStackTrace() java.lang.Exception: thrown from f() at Rethrowing.f(Rethrowing.java:7) at Rethrowing.g(Rethrowing.java:11) at Rethrowing.main(Rethrowing.java:29) originating the exception in f() Inside h(), e.printStackTrace() jva.lang.Exception: thrown from f() at Rethrowing.f(Rethrowing.java:7) at Rethrowing.g(Rethrowing.java:20) at Rethrowing.main(Rethrowing.java:35) main: printStackTrace() java.lang.Exception: thrown from f() at Rethrowing.h(Rethrowing.java:24) at Rethrwoing.main(Rethrowing.java:35) */
有可能在捕获异常之后抛出另一个异常,其效果类似于使用fillInStackTrace(),有关原来异常发生点的信息会丢失,剩下的是与新的抛出点有关的信息:
class OneException extends Exception { public oneException(String s) { super(s);} } class TwoException extends Exception { public TwoException(String s) { super(s);} } public class RethrowNew { public static void f() throws OneException { System.out.println("originating the exception in f()"); throw new OneException("thrown from f()"); } public static void main(String[] args){ try { try { f(); } catch(OneExceptione) { System.out.println("Caught in inner try, e.printStackTrace()"); e.printStackTrace(System.out); throw new TwoException("from inner try"); } } catch(TwoException) { System.out.println("Caught in outer try, e.printStackTrace()"); e.printStackTrace(System.out); } } } /* Output: originating the exception in f() Caught in inner try, e.printStackTrace() OneException: thrown from f() at RethrowNew.f(RethrowNew.java:15 at RethrowNew.main(RethrowNew.java:20) Caught in outer try, e.printStackTrace() TwoException: from inner try at RethrowNew.main(RethrowNew.java:25) */
异常链
所有的Throwable的子类在构造器中都可以接受一个cause对象作为参数。这个cause就用来表示原始异常。在Throwable子类中,只有三种基本的异常类提供了带cause参数的构造器:Error, Exception, RuntimeException。
在运行时动态地向DynamicFields对象添加字段:
import static net.mindview.util.Print.*; class DynamicFieldsException extends Exception {} public class DynamicFields { private Object[][] fields; public DynamicFields(int initialSize) { fields = new Object[initialSize][2]; for(int i = 0; i < initialSize; ++i) fields[i] = new Object[] {null, null}; } public String toString() { StringBuilder result = new StringBuilder(); for(Object[] obj : fields) { result.append(Obj[0]); result.append(": "); result.append(Obj[1]); result.append("\n"); } return result.toString(); } private int hasField(String id) { for(int i = 0; i < fields.length; ++i) if(id.equals(fields[i][0])) return i; return -1; } private int getFieldNumber (String id) throws NoSuchFieldException { int fieldNum = hasField(id); if(fieldNum == -1) throw new NoSuchFieldException(); return fieldNum; } private int makeField(String id) { for(int i = 0; i < fields.length; ++i) if(fields[i][0] == null){ field[i][0] = id; return i; } // No empty fields. Add one: Object[][] tmp = new Object[fields.length + 1][2]; for(int i = 0; i < fields.length; ++i) tmp[i] = fieldsp[i]; for(int i = fields.length; i < tmp.length; ++i) tmp[i] = new Object[] {null, null}; fields = tmp; // Recursive call with expanded fields: return makeField(id); } public Object getField(String id) throws NoSuchFieldException { return fields[getFieldNumber(id)][1]; } public Object setField(String id, Object value) throws DynamicFieldsException { if(null == value) { // Most exceptions don't have a "cause" constructor. // In these cases you must use initCause(). DynamicFieldsException dfe = new DynamicFieldsException(); dfe.initCause(new NullPointerException()); throw dfe; } int fieldNumber = hasField(id); if(-1 == fieldNumber) fieldNumber = makeField(id); Object result = null; try { result = getField(id); // Get old value } catch(NoSuchFieldException e) { // Use constructor that takes "cause": throw new RuntimeException(e); } fields[fieldNumber][1] = value; return result; } public static void main(String[] args){ DynamicFields df = new DynamicFields(3); print(df); try { df.setField("d", "A value for d"); df.setField("number", 47); df.setField("number2", 48); print(df); df.setField("d", "A new value for d"); df.setField("number3", 11); print("df: " + df); print("df.getField(\"d\") : " + df.getField("d")); Object field = df.setField("d", null); // Exception } catch(NoSuchFieldException e) { e.printStackTrace(System.out); } catch(DynamicFieldsException e) { e.printStackTrace(System.out); } } } /* Output: null: null null: null null: null d: A value for d number: 47 number2: 48 df: d: A new value for d number: 47 number2: 48 number3: 11 df.getField("d") : A new value for d DynamicFieldsException at DynamicFields.setField(DynamicFields.java:64); at DynamicFields.main(DynamicFields.java:94); Caused by: java.lang.NullPointerException at DynamicFields.setFields(DynamicFields.java:66); */
Java标准对象
RuntimeException是一个特例,它也许会穿越所有的执行路径直达main()方法,而不会被捕获。编译器不需要异常说明,其输出被报告给了System.err。在程序退出前,将调用异常的printStackTrace()方法。
使用finally进行清理
无论try块中是否抛出异常,都希望执行的代码可以放在finally子句当中:
try { // The guarded region: Dangerous activities // that might throw A, B or C } catch(A a1) { // Handler for situation A } catch(B b1) { // Handler for situation B } catch(C c1) { // Handler for situation C } finally { // Activities that happen every time }
实例:
class ThreeException extends Exception {} public class FinallyWorks { static int count = 0; public static void main(String[] args){ while(true) { try { // Post-increment is zero first time: if(count++ = 0) throw new ThreeException(); System.out.println("No exception"); } catch(ThreeException e) { System.out.println("ThreeException"); } finally { System.out.println("In finally clause"); if(2 == count) break; // out of "while" } } } } /* Output: ThreeException In finally clause No exception In finally clause */
当需要把除内存之外的资源恢复到它们的初始状态时,就要用到finally子句。这种需要清理的资源包括
1. 已经打开的文件
2. 已经打开的网络
3. 在屏幕上画的图形
4. 甚至可以是外部世界的某个开关
import static net.mindview.uti.Print.*; public class Switch { private boolean state = false; public boolean read() { return state; } public void on() { state = true; print(this); } public void off() { state = false; print(this); } public String toString() { return state ? "on" : "off"; } } public class OnOffException1 extends Exception {} public class OnOffException2 extends Exception {} // Why use finally? public class OnOffSwitch { private static Switch sw = new Switch(); public static void f() throws OnOffException1, OnOffException2 {} public static void main(String[] args){ try { sw.on(); // Code that can throw exceptions ... f(); sw.off(); } catch(OnOffException1 e){ System.out.println("OnOffException1"); sw.off(); } catch(OnOffException2 e){ System.out.println("OnOffException2"); sw.off(); } } } /* Output: on off */
程序的目的是要保证main()方法结束的时候开关必须关闭。但有可能异常被抛出,但没被处理程序捕获,这时候sw.off()就得不到调用。但是有了finally,只有把try块中的清理代码放在一处即可:
public class WithFinally { static Switch sw = new Switch(); public static void main(String[] args){ try { sw.on(); // Code that can throw exceptions ... OnOffSwitch.f(); } catch(OnOffException1 e){ System.out.println("OnOffException1"); } catch(OnOffException2 e){ System.out.println("OnOffException2"); } finally { sw.off(); } } } /* Output: on off */
甚至在异常美哦与被当前的异常处理程序捕获的情况下,异常处理机制也会在跳到更高一层的异常处理程序之前,执行finally子句:
import static net.mindview.util.Print.*; class FourException extends Exception {} public class AlwaysFinally { public static void main(String[] args){ print("Entering first try block"); try { print("Entering second try block"); try { throw new FourException(); } finally { print("finally in 2nd try block"); } } catch(FourException e){ System.out.println("Caught FourException in 1st try block"); } finally { System.out.println("finally in 1st try block"); } } } /* Output: Entering first try block Entering second try block finally in 2nd try block Caught FourException in 1st try block finally in 1st try block */
在return中使用finally
import static net.mindview.util.Print.*; public class MultipleReturns { public static void f(int i) { print("Initialization that requires cleanup"); try { print("Point 1"); if(1 == i) return; print("Point 2"); if(2 == i) return; print("Point 2"); if(2 == i) return; print("Point 3"); if(3 == i) return; print("End"); return; } finally { print("Performing cleanup"); } } public static void main(String[] args){ for(int i = 1; i <= 4; ++i) f(i); } } /* Output: Initialization that requires cleanup Point 1 Performing cleanup Initialization that requires cleanup Point 1 Point 2 Performing cleanup Initialization that requires cleanup Point 1 Point 2 Point 3 Performing cleanup Initialization that requires cleanup Point 1 Point 2 Point 3 End Performing cleanup */
可以看出,无论从哪里返回,finally总是会执行。
用某些方式使用finally可能会导致异常被忽略
class VeryImportantException extends Exception { public String toString() { return "A very important exception!"; } } class HoHumException extends Exception { public String toString() { return "A trivial exception"; } } public class LostMessage { void f() throws VeryImportantException { throw new VeryImportantException(); } void dispose() throws HoHumException { throw new HoHumException(); } public static void main(String[] args){ try { LostMessage lm = new LostMessage(); try { lm.f(); } finally { lm.dispose(); } } catch(Exception e){ System.out.println(e); } } } /* Output: A trivial exception */
一种更加简单的丢失异常的方式是从finally子句中返回:
public class ExceptionSilencer { public class void main(String[] args){ try { throw new RuntimeException(); } finally { // Using 'return' inside the finally block // will silence any thrown exception return; } } }
异常的限制
当覆盖方法的时候,只能抛出在基类方法的异常说明列出的那些异常。
class BaseballException extends Exception {} class Foul extends BaseballException {} class Strike extends BaseballException {} abstract class Inning { public Inning() throws BaseballException {} public void event() throws BaseballException { // Doesn't actually have to throw anything } public abstract void atBat() throws Strike, Foul; public void walk(){} // Throws no checked exceptions } class StormException extends Exception {} class RainedOut extends StormException {} class PopFoul extends Foul {} interface Storm { public void event() throws RainedOut; public void rainHard() throws RainedOut; } public class StormyInning extends Inning implements Storm { // OK to add new exceptions for constructors, but you // must deal with the base constructor exceptions; public StormyInning() throws RainedOut, BaseballException {} public StormyInning(String s) throws Foul, BaseballException {} // Regular methods must conform to base class: //! void walk() throws PopFoul {} // Compile error // Interface CANNOT add exceptions to existing // methods from the base class: //! public void event() throws RainedOut {} // If the method doesn't already exist in the // base class, the exception is OK: public void rainHard() throws RainedOut {} // You can choose to not throw any exceptions, // even if the base version does: public void event() {} // Overridden methods can throw inherited exceptions: public void atBat() throws PopFoul {} public static void main(String[] args){ try { StormyInning si = new StormyInning(); si.atBat(); } catch(PopFoul e) { System.out.println("Pop foul"); } catch(RainedOut e){ System.out.println("Rained out"); } catch(BaseballException e){ System.out.println("Generic baseball exception"); } // Strike not thrown in derived version. try { // What happens if you upcast? Inning i = new StormyInning(); i.atBat(); // You must catch the exceptions from the // base-class version of the method: } catch(Strike e){ System.out.println("Strike"); } catch(Foul e){ System.out.println("Foul"); } catch(RainedOut e){ System.out.println("Rained Out"); } catch(BaseballException e){ System.out.println("Generic baseball exception"); } } }
1. Ok to add new exceptions for constructors, but you must deal with the base constructor exceptions:
public StormyInning() throws RainedOut, BaseballException {}
2. Regular methods must CONFORM to base class:
//! void walk() throws PopFoul {} // Compile error, because the method in base class throws no checked exceptions
3. Interface CANNOT add exceptions to existing methods from the base class:
//! public void event() throws RainedOut {} // event() method in base class only throws BaseballException
More over, if we throws both Baseball and RaindeOut Exceptions:
public void event() throws RainedOut, BaseballException {}
then compiler will say can't implement event() in storm overriden method doesn't throw BaseballException.
4. If the method doesn't already exists in the base class, the excpetion is OK:
public void rainHard() throws RainedOut {}
5. You can choose to not throw any exceptions even if the base version does:
public void event() {}
6. Overridden methods can throw inherited exceptions:
public void atBat() throws PopFoul {} // because PopFoul is inherited from Foul which is the exception throws from atBat() in base class
从第5点可以看出,一个出现在基类方法的异常说明中的异常,不一定会出现在派生类方法的异常说明里。这点同继承的规则明显不同,在继承中,基类的方法必须出现在派生类里。
构造器
构造器会把对象设置成安全的初始状态,但还会有别的动作,比如打开一个文件,这样的动作只有在对象使用完毕并且用户调用了特殊的清理方法之后才能得以清理。如果在构造器内部抛出异常,这些清理行为也许就不能正常工作了。
也许会认为finally可以解决问题,但因为finally会每次都执行清理代码。如果构造器在其执行过程中半途而废,也许该对象的某些部分还没有被成功创建,而这些部分在finally子句中却要被清理:
import java.io.*; public class InputFile { private BufferReader in; public InputFile(String fname) throws Exception { try { in = new BufferedReader(new FileReader(fname)); // Other code that might throw exceptions } catch(FileNotFoundException e) { System.out.println("Could not open " + fname); // Wasn't open, so don't close it throw e; } catch(Exception e) { // All other exceptions must close it try { in.close(); } catch(IOException e2) { System.out.println("in.close() unsuccessful"); } throw e; // Rethrow } finally { // Don't close it here!!! } } public String getLine() { String s; try { s = in.readLine(); } catch(IOException e) { throw new RuntimeException("readLine() failed"); } return s; } public void dispose() { try { in.close(); System.out.println("dispose() successful"); } catch(IOException e) { throw new RuntimeException("in.close() failed"); } } }
对于在构造阶段可能会抛出异常,并且要求清理,最安全的使用方式是使用嵌套的try子句:
public class Cleanup { public static void main(String[] args){ try{ InputFile in = new InputFile("Cleanup.java"); try { String s; int i = 1; while((s = in.getLine()) != null) ; // Perform line-by-line processing here... } catch(Exception e) { System.out.println("Caught Exception in main"); e.printStackTrace(System.out); } finally { in.dispose(); } } catch(Exception e) { System.out.println("InputFile construction failed."); } } } /* Output: dispose() successful */
注意这里的逻辑:对InputFile对象的构造器在其自己的try语句块中有效,如果构造失败,将进入外部的catch语句。在这种方式中,finally子句在构造失败时是不会执行的,而在构造成功时将总是执行。
这种通用的清理惯用法在构造器不抛出任何异常时也应该运用,其基本规则是:在创建需要清理的对象之后,立即进入一个try-finally语句块:
// Each disposable object must be followed by a try-finally class NeedsCleanup { // Construction can't fail private static long counter = 1; private final long id = counter++; public void dispose() { System.out.println("NeedsCleanup " + id + " disposed"); } } class ConstructionException extends Exception {} class NeedsCleanup2 { // Construction can fail: public NeedsCleanup2() throws ConstructionException {} } public class CleanupIdiom { public static void main(String[] args){ // Section 1: NeedsCleanup nc1 = new NeedsCleanup(); try{ // ... } finally { nc1.dispose(); } // Section 2: // If construction cannot fail you can group objects NeedsCleanup nc2 = new NeedsCleanup(); NeedsCleanup nc3 = new NeedsCleanup(); try { // ... } finally { nc3.dispose(); // Reverse order of construction nc2.dispose(); } // Section 3: // If construction can fail you must guard each one: try { NeedsCleanup2 nc4 = new NeedsCleanup2(); try { NeedsCleanup2 nc5 = new NeedsCleanup2(); try { // ... } finally { nc5.dispose(); } } catch(ConstructionException e) { // nc5 constructor System.out.println(e); } finally { nc4.dispose(); } } catch(ConstructionException e) { // nc4 constructor System.out.println(e); } } } /* Output: NeedsCleanup 1 disposed NeedsCleanup 3 disposed NeedsCleanup 2 disposed NeedsCleanup 5 disposed NeedsCleanup 4 disposed */
异常匹配
并不要求抛出的异常同处理程序所声明的异常完全匹配。派生类的对象也可以匹配其基类的处理程序:
// Catching exception hierarchies class Annoyance extends Exception {} class Sneeze extends Annoyance {} public class Human { public static void main(String[] args){ // Catch the exact type: try { throw new Sneeze(); } catch(Sneeze s) { System.out.println("Caught Sneeze"); } catch(Annoyance a) { System.out.println("Caught Annoyance"); } // Catch the base type: try { throw new Sneeze(); } catch(Annoyance a) { System.out.println("Caught Annoyance"); } } } /* Output: Caught Sneeze Caught Annoyance */
如果把捕获基类的catch子句放在最前面,以此想把派生类的异常全给“屏蔽”掉,就像这样:
try { throw new Sneeze(); } catch(Annoyance a) { // ... } catch(Sneeze s) { // ... }
则编译器会报错。
把异常传递给控制台
import java.io.*; public class MainException { // Pass all exceptions to the console: public static void main(String[] args) throws Exception { // Open the file FileInputStream file = new FileInputStream("MainException.java"); // Use the file ... // Close the file: file.close(); } }
main()作为一个方法也可以有异常说明,这里异常的类型是Exception,它也是所有“被检查的异常”的基类。这里是通过把它传递给控制台,就不必在main()里写try-catch子句了。
把“被检查的异常”转换为“不检查的异常”
可以直接把“被检查的异常”包装进RuntimeException里面:
try { // ... to do something useful } catch(IDontKnowWhatToDoWithThisCheckedException e) { throw new RuntimeException(e); }
这种技巧给了一种选择,可以不写try-catch子句和/或异常说明,直接忽略异常,让它自己沿着调用栈往上“冒泡”。同时,还可以用getCause()捕获并处理特定的异常:
// "Turn off" checked exceptions import java.io.*; import static net.mindview.util.Print.*; class WrapCheckedException { void throwRuntimeException(int type) { try { switch(type) { case 0: throw new FileNotFoundException(); case 1: throw new IOExceptin(); case 2: throw new RuntimeException("Where am I?"); default: return; } } catch(Exception e) { // Adapt to unchecked: throw new RuntimeException(e); } } } class SomeOtherException extends Exception {} public class TurnOffChecking { public static void main(String[] args){ WrapCheckedException wce = new WrapCheckedException(); // You can call throwRuntimeExceptino() without a try // block, and let RuntimeExceptions leave the method: wce.throwRuntimeException(3); // Or you can choose to catch exceptions: for(int i = 0; i < 4; ++i) try { if(i < 3) wce.throwRuntimeException(); else throw new SomeOtherException(); } catch(SomeOtherException e) { print("SomeOtherException: " + e); } catch(RuntimeException re) { try { throw re.getCause(); } catch(FileNotFoundException e) { print("FileNotFoundException: " + e); } catch(IOException e) { print("IOException: " + e); } catch(Throwable e) { print("Throwable: " + e); } } } } /* Output: FileNotFoundException: java.io.FileNotFoundException IOException: java.io.IOException Throwable: java.lang.RuntimeException: Where am I? SomeOtherException: SomeOtherException */
在TurnOffChecking里,可以不用try块就调用throwRuntimeException(),因为它没有抛出“被检查的异常”。但是,当你准备好去捕获异常的时候,还是可以用try块来捕获任何你想捕获的异常的。RuntimeException要放到最后去捕获。然后把getCause()的结果(也就是被包装的那个原始异常)抛出来。这样就可以把原先的那个异常给提取出来了。
泛化的Class引用
Java SE5允许你对Class引用所指定的Class对象的类型进行限定而把它的类型变得更具体一些,这里用到了泛型语法。在下例中,两种语法都是正确的:
public class GenericClassReferences { public static void main(String[] args){ Class intClass = int.class; Class<Integer> genericIntClass = int.class; genericIntClass = Integer.class; // same thing intClass = double.class; //! genericIntClass = double.class; // illegal } }
1. 普通的类引用不会产生警告信息,并可以被重新赋值为指向任何其他的Class对象。
2. 泛型引用只能赋值为指向其声明的类型。通过使用泛型语法,可以让编译器强制执行额外的类型检查。
如果希望放松这种限制,必须使用通配符“?”。直接执行类似下面的操作是不可以的:
Class<Numer> genericNumberClass = int.class;
虽然Integer继承自Number,但同样无法工作。
通配符“?”表示“任何事物”,因此可以在普通的Class引用中添加通配符:
public class WildcardClassReferences { public static void main(String[] args){ Class<?> intClass = int.class; intClass = double.class; } }
并且,Class<?>优于平凡的Class。
如果需要限定为某个类型的任意子类型,则需要将通配符和extends关键字相结合:
public class BoundedReferences { public static void main(String[] args){ Class<? extends Number> bounded = int.class; bounded = double.class; bounded = Number.class; // Or anything else derived from Number. } }
向Class引用添加泛型语法,仅仅是为了提供编译期类型检查,因此如果你操作有误,你会立即发现这一点。
下面的示例存储了一个类引用,稍后又产生了一个List,填充这个List的对象是使用newInstance()方法,通过该引用生成:
import java.util.*; class CountedInteger { private static long counter; private final long id = counter++; public String toString(){ return long.toString(id); } } public class FilledList<T> { private Class<T> type; public FilledList(Class<T> type) { this.type = type; } public List<T> create(int nElements){ List<T> result = new ArrayList<T>(); try { for(int i = 0; i < nElements; ++i) result.add(type.newInstance()); } catch(Exception e){ throw new RuntimeException(e); } return result; } public static void main(String[] args){ FilledList<CountedInteger> fl = new FilledList<CountedInteger>(CountedInteger.class); System.out.println(fl.create(15)); } } /* Output: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14] */
注意,这个类FilledList必须假定与它一同工作的任何类型都具有一个默认的构造器(无参构造器),因为T要使用newInstance()方法。
当你将泛型语法用于Class对象时,会发生一件很有趣的事情:newInstance()将返回该对象的确切类型,而不仅仅是在ToyTest.java中看到的基本的Object。这在某种程度上有些受阻:
// Testing class Class. package typeinfo.toys; public class GenericToyTest { public static void main(String[] args) throws Exception { Class<FancyToy> ftClass = FancyToy.class; // Produces exact type: FancyToy fancyToy = ftClass.newInstance(); Class<? super FancyToy> up = ftClass.getSuperclass(); // This won't compile: //! Class<Toy> up2 = ftClass.getSuperclass(); // Only produces Object: Object obj = up.newInstance(); } }
如果你手头的是超类,那编译器只允许你声明超类引用是“某个类,它是FancyToy超类”,就像表达式Class<? super FancyToy>中所看到的。而不会接受Class<Toy>这样的声明。不管怎样,由于这种含糊性,up.newInstance(的返回值不是精确类型,而只是Object。
新的转型语法
Java SE5添加了用于Class引用的转型语法,即cast()方法:
class Building {} class House extends Building {} public class ClassCasts { public static void main(String[] args){ Building b = new House(); Class<House> houseType = House.class; House h = houseType.cast(b); h = (House)b; // or just do this } }
cast()方法接受参数对象,并将其转型为Class引用类型。在你编写泛型代码时,如果你存储了Class引用,并且希望以后通过这个引用来执行转换,这种情况下就会非常有用。但这种方法被证明是一种罕见的情况。
RTTI在Java中还有一种形式,就是关键字instanceof。它返回一个布尔值,告诉我们对象是不是某个特定类型的实例:
if(x instanceof Dog){ ((Dog)x).bark(); }
如果没有其它信息告诉你这个对象是什么类型,那么使用instanceof是非常重要的。下面是继承自Individual的类的继承体系:
//: typeinfo/pets/Person.java package typeinfo.pets; public class Person extends Individual { public Person(String name) { super(name); } } //: typeinfo/pets/Pet.java package typeinfo.pets; public class Pet extends Individual { public Pet(String name) { super(name); } public Pet() { super(); } } //: typeinfo/pets/Dog.java package typinfo.pets; public class Dog extends Pet { public Dog(String name) { super(name); } public Dog() { super(); } } //: typeinfo/pets/Mutt.java package typeinfo.pets; public class Mutt extends Dog { public Mutt(String name) { super(name); } public Mutt() { super(); } } //: typeinfo/pets/Pug.java package typeinfo.pets; public class Pug extends Dog { public Pug(String name) { super(name); } public Pug() { super(); } } //: typeinfo/pets/Cat.java package typeinfo.pets; public class Cat extends Pet { public Cat(String name) { super(name); } public Cat() { super(); } } //: typeinfo/pets/EgyptianMau.java package typeinfo.pets; public class EgyptianMau extends Cat { public EgyptianMau(String name) { super(name); } public EgyptianMau() { super(); } } //: typeinfo/pets/Manx.java package typeinfo.pets; public class Manx extends Cat { public Manx(String name) { super(name); } public Manx() { super(); } } // ...
接下来,我们需要一种方法,通过它可以随机地创建不同类型的宠物,并且为方便起见,还可以创建宠物数组和List。为了使该工具能够适应多种不同的实现,我们将其定义为抽象类:
// Creates random sequences of Pets package typeinfo.pets; import java.util.*; public abstract class PetCreator { private Random rand = new Random(47); // The List of the different types of Pet to create: public abstract List<Class<? extends Pet>> types(); public Pet randomPet(){ // Create one random Pet int n = rand.nextInt(types().size()); try { return types().get(n).newInstance(); } catch(InstantiationException e){ throw new RuntimeException(e); } catch(IllegalAccessException e){ throw new RuntimeException(e); } } public Pet[] createArray(int size){ Pet[] result = new Pet[size]; for(int i = 0; i < size; ++i) result[i] = randomPet(); return result; } public ArrayList<Pet> arrayList(int size) { ArrayList<Pet> result = new ArrayList<Pet>(); Collections.addAll(result, createArray(size)); return result; } }
1. 抽象的types()方法在导出类中实现,以获取由Class对象构成的List(这是模板方法设计模式的一种变体)。
2. randomPet()随机地产生List中的索引,并使用被选取的Class对象,通过newInstace()来生成该类的新实例
3. createArray()方法使用randomPet()来填充数组
4. 而arrayList()方法使用的则是createArray()。
当你导出PetCreator的子类时,唯一所需要提供的就是你希望使用randomPet()和其他方法来创建你的宠物类型的List。types()方法通常只返回对一个静态List的引用。下面是使用forName()方法的一个具体实现:
package typeinfo.pets; import java.util.*; public class ForNameCreator extends PetCreator { private static List<Class<? extends Pet>> types = new ArrayList<Class<? extends Pet>>(); // Types that you want to be randomly created: private static String[] typeNames = {"typeinfo.pets.Mutt", "typeinfo.pets.Pug", "typeinfo.pets.EgyptianMau", "typeinfo.pets.Manx", "typeinfo.pets.Cymric", "typeinfo.pets.Rat", "typeinfo.pets.Mouse", "typeinfo.pets.Hamster"}; @SuppressWarnings("unchecked") private static void loader() { try { for(String name : typeNames) types.add((Class<? extends Pet>)Class.forName(name)); } catch(ClassNotFoundException e) { throw new RuntimeException(e); } } static { loader(); } public List<Class<? extends Pet>> types() { return types; } }
loader()方法用Class.forName()创建了Class对象的List,这可能会产生ClassNotFound.Exception异常,这么做是有意义的。因为你传递给它的是一个在编译期无法验证的String。由于Pet对象在typeinfo包中,因此必须使用包名来引用这些类。
为了对Pet进行计数,我们需要一个能够跟踪各种不同类型的Pet的数量的工具。Map是此需求的首选,其中键是Pet类型名,而值是保存Pet数量的Integer:
import typeinfo.pets.*; import java.util.*; import static net.mindview.util.Print.*; public class PetCount { static class PetCounter extends HashMap<String, Integer> { public void count(String type) { Integer quantity = get(type); if(null == quantity) put(type, 1); else put(type, quantity + 1); } } public static void CountPets(PetCreator creator) { PetCounter counter = new PetCounter(); for(Pet pet: creator.createArray(20)) { // List each individual pet: printnb(pet.getClass().getSimpleName() + " "); if(pet instanceof Pet) counter.count("Pet"); if(pet instanceof Dog) counter.count("Dog"); if(pet instanceof Mutt) counter.count("Mutt"); if(pet instanceof Pug) counter.count("Pug"); // ... } // Show the counts: print(); print(counter); } public static void main(String[] args){ countPets(new ForNameCreator()); } }
这里,我们没有办法让instanceof聪明起来,让它能够自动地创建一个Class对象组,然后将目标对象与这个数组中的对象进行逐一的比较(稍后会看到一个替代方案)。如果程序中编写了许多的instanceof表达式,就说明你的设计可能存在瑕疵。
如果我们使用类字面常量重新实现PetCount,那么改写后的结果在许多方面都会更加清晰:
package typeinfo.pets; import java.util.*; public class LiteralPetCreator extends PetCreator { // No try block needed. @SuppressWarnings("unchecked") public static final List<Class<? extends Pet>> allTypes = Collections.unmodifiableList(Arrays.asList( Pet.class, Dog.class, Cat.class, Rodent.class)); // Types for random creation: private static final List<Class<? extends Pet>> types = allTypes.subList(allTypes.indexOf(Mutt.class), allTypes.size()); public List<Class<? extends Pet>> types() { return types; } public static void main(String[] args){ System.out.println(types); } }
现在typeinfo.pets类库中有了两种PetCreator的实现。为了将第二种实现作为默认实现,我们可以创建一个使用了LiteralPetCreator的外观:
package typeinfo.pets; import java.util.*; public class Pets { public static final PetCreator creator = new LiteralPetCreator(); public static Pet randomPet() { return creator.randomPet(); } public static Pet[] createArray(int size){ return creator.createArray(size); } public static ArrayList<Pet> arrayList(int size){ return creator.arrayList(size); } }
这个类还提供了对randomPet()、createArray()、arrayList()的间接调用。
因为PetCount.countPets()接受的是一个PetCreator参数,我们可以很容易地测试LiteralPetCreator(通过上面的外观):
import typeinfo.pets.*; public class PetCount2 { public static void main(String[] args){ PetCount.countPets(Pets.creator); } }
动态的instanceof
Class.isInstance方法提供了一种动态地测试对象的途径。于是所有那些单调的instanceof语句都可以从PetCount.java的例子中移除:
// Using isInstance() import typeinfo.pets.*; import java.util.*; import net.mindview.util.*; import static net.mindview.util.Print.*; public class PetCount3 { static class PetCounter extends LinkedHashMap<Class<? extends Pet>, Integer> { public PetCounter() { super(MapData.map(LiteralPetCounter.allTypes, 0)); } public void count(Pet pet){ // Class.isInstance() eliminates instanceofs: for(Map.Entry<Class<? extends Pet>, Integer> pair : entrySet()) if(pair.getKey().isInstance(pet)) put(pair.getKey(), pair.getValue() + 1); } public String toString() { StringBuilder result = new StringBuilder("{"); for(Map.Entry<Class<? extends Pet>, Integer> pair : entrySet()){ result.append(pair.getKey().getSimpleName()); result.append("="); result.append(pair.getValue()); result.append(", "); } result.delete(result.length() - 2, result.length()); result.append("}"); return result.toString(); } } public static void main(String[] args){ PetCounter petCount = new PetCounter(); for(Pet pet : Pets.createArray(20)){ printnb(pet.getClass().getSimpleName() + " "); petCount.count(pet); } print(); print(petCount); } }
可以看到,isInstance()方法使我们不需要instanceof表达式。此外,这意味着如果要添加新类型的Pet,只需简单地改变LiteralPetCreator.java数组即可,而无需改变程序的其他部分。
递归计数
在PetCount3.PetCounter中的Map预加载了所有不同的Pet类。与预加载映射表不同的是,我们可以使用Class.isAssignableFrom(),并创建了一个不局限于对Pet计数的通用工具:
// Counts instances of a type family. package net.mindview.util; import java.util.*; public class TypeCounter extends HashMap<Class<?>, Integer>{ private Class<?> baseType; public TypeCounter(Class<?> baseType){ this.baseType = baseType; } public void count(Object obj){ Class<?> type = obj.getClass(); if(!baseType.isAssignableFrom(type)) throw new RuntimeException(obj + " incorrect type: " + type + ", should be type or subtype of " + baseType); countClass(type); } private void countClass(Class<?> type){ Integer quantity = get(type); put(type, null == quantity ? 1 : quantity + 1); Class<?> superClass = type.getSuperclass(); if(superClass != null && baseType.isAssignableFrom(superClass)) countClass(superClass); } public String toString(){ StringBuilder result = new StringBuilder("{"); for(Map.Entry<Class<?>, Integer> pair : entrySet()){ result.append(pair.getKey().getSimpleName()); result.append("="); result.append(pair.getValue()); result.append(", "); } result.delete(result.length() - 2, result.length()); result.append("}"); return result.toString(); } }
countClass()首先对该类的确切类型进行计数,然后如果其超类可以赋值给baseType(即:isAssignable),countClass()将在其超类上递归计数。
import typeinfo.pets.*; import net.mindview.util.*; import static net.mindview.util.Print.*; public class PetCount4 { public static void main(String[] args){ TypeCounter counter = new TypeCounter(Pet.class); for(Pet pet : Pets.createArray(20)){ printnb(pet.getClass().getSimpleName() + " "); counter.count(pet); } print(); print(counter); } }
以上程序对基类型和确切类型都进行了计数。
注册工厂
生成Pet继承结构中的对象存在一个问题,即每次向该继承结构添加新的Pet类型时,必须将其添加到LiteralPetCreator中的项。如果在系统中已经存在了一个继承结构,然后在其上要添加更多的类,那么就有可能会出现问题。
你可能会考虑在每个子类中添加静态初始化器,以使得该初始化器可以将它的类添加到某个List中。遗憾的是,静态初始化器只有在类首先被加载的情况下才能被调用,因此就碰上了“先有鸡还是先有蛋的问题”:生成器在其列表中不包含这个类,因此它永远不能创建这个类的对象;而这个类也就不能被加载并置于这个列表中。
这主要是因为你被强制要求自己去手工创建这个列表。
最佳的做法是:将这个列表置于一个位于中心的、位置显眼的地方,而我们感兴趣的继承结构的基类可能就是这个最佳位置。
这里我们需要的修改,就是使用工厂方法设计模式,将对象的创建工作交给类自己去完成。工厂方法可以被多态地调用,从而为你创建恰当类型的对象。在下面这个非常简单的版本中,工厂方法就是Factory接口中的create()方法:
package typeinfo.factory; public interface Factory<T> { T create(); }
泛型参数T使得create()可以在每种Factory实现中返回不同的类型。
基类Part包含一个工厂对象的列表。对应于这个由createRandom()方法产生的类型,它们的工厂都被添加到了partFactories List中,从而被注册到了基类中:
import typeinfo.factory.*; import java.util.*; class Part { public String toString() { return getClass().getSimpleName(); } static List<Factory<? extends Part>> partFactories = new ArrayList<Factory<? extends Part>>(); static { // Collections.addA() gives an "unchecked generic // array creation ... for varargs parameter" warning. partFactory.add(new FuelFilter.Factory()); partFactory.add(new AirFilter.Factory()); partFactory.add(new CabinAirFilter.Factory()); partFactory.add(new OilFilter.Factory()); partFactory.add(new FanBelt.Factory()); partFactory.add(new PowerSteeringBelt.Factory()); partFactory.add(new GeneratorBelt.Factory()); } private static Random rand = new Random(47); public static Part createRandom() { int n = rand.nextInt(partFactories.size()); return partFactories.get(n).create(); } } class Filter extends Part {} class FuelFilter extends Filter { // Create a Class Factory for each specific type: public static class Factory implements typeinfo.factory.Factory<FuelFilter> { public FuelFilter create() { return new FuelFilter(); } } } class AirFilter extends Filter { // Create a Class Factory for each specific type: public static class Factory implements typeinfo.factory.Factory<AirFilter> { public AirFilter create() { return new AirFilter(); } } } class CabinAirFilter extends Filter { // Create a Class Factory for each specific type: public static class Factory implements typeinfo.factory.Factory<CabinAirFilter> { public CabinAirFilter create() { return new CabinAirFilter(); } } } class OilFilter extends Filter { // Create a Class Factory for each specific type: public static class Factory implements typeinfo.factory.Factory<OilFilter> { public OilFilter create() { return new OilFilter(); } } } class Belt extends Part {} class FanBelt extends Belt { public static class Factory implements typeinfo.factory.Factory<FanBelt> { public FanBelt create() { return new FanBelt(); } } } class GeneratorBelt extends Belt { public static class Factory implements typeinfo.factory.Factory<GeneratorBelt> { public GeneratorBelt create() { return new GeneratorBelt(); } } } class PowerSteeringBelt extends Belt { public static class Factory implements typeinfo.factory.Factory<PowerSteeringBelt> { public PowerSteeringBelt create() { return new PowerSteeringBelt(); } } } public class RegisteredFactories { public static void main(String[] args){ for(int i = 0; i < 10; ++i) System.out.println(Part.createRandom()); } }
并非所有在继承结构中的类都应该被实例化。Filter和Belt只是分类标示符,因此不应该创建它们的实例,而是创建它们的子类的实例。
反射:运行时的类信息
instanceof和Class的等价性
在查询类型信息时,以instanceof的形式(即以instanceof或isInstance()的形式)与直接比较Class对象有一个很重要的差别:
// The difference between instanceof and class package typeinfo; import static net.mindview.util.Print.*; class Base {} class Derived extends Base {} public class FamilyVsExactType { static void test(Object x){ print("Testing x of type " + x.getClass()); print("x instanceof Base " + (x instanceof Base)); print("x instanceof Derived " + (x instanceof Derived)); print("Base.isInstance(x) " + Base.class.isInstance(x)); print("Derived.isInstance(x) " + Derived.class.isInstance(x)); print("x.getClass() == Base.class " + (x.getClass() == Base.class)); print("x.getClass() == Derived.class " + (x.getClass() == Derived.class)); print("x.getClass().equals(Base.class) " + (x.getClass().equals(Base.class))); print("x.getClass().equals(Derived.class) " + (x.getClass().equals(Derived.class))); } public static void main(String[] args){ test(new Base()); test(new Derived()); } } /* Output: Testing x of type class Base x instanceof Base true x instanceof Derived false Base.isInstance(x) true Derived.isInstance(x) false x.getClass() == Base.class true x.getClass() == Derived.class false x.getClass().equals(Base.class) true x.getClass().equals(Derived.class) false Testing x of type class Derived x instanceof Base true x instanceof Derived true Base.isInstance(x) true Derived.isInstance(x) true x.getClass() == Base.class false x.getClass() == Derived.class true x.getClass().equals(Base.class) false x.getClass().equals(Derived.class) true */
test()方法使用了两种形式的instanceof作为参数来执行类型检查。
1. instanceof和isInstance的结果一样
2. equals和==的结果一样
3. instanceof考虑了类型的概念,如“你是这个类吗,或者你是这个类的派生类吗”;而==比较实际的Class对象,就没有考虑继承。
反射机制
RTTI有一个限制:这个类型在编译时必须已知,这样才能使用RTTI识别它,并利用这些信息做一些有用的事情。假如你从磁盘文件,或者网络连接中获取了一串字节,并且告诉你这些字节代表了一个类。既然这些类在编译器为你的程序生成了代码之后才出现,那么怎么才能使用这些类呢?
反射提供了一种机制:用来检查可用的方法,并返回方法名。
一般并不需要直接使用反射机制,在Java中它一般用来支持其他特性,如对象序列化和JavaBean。反射机制提供了一种方法,使我们能够编写可以自动展示完整接口的简单工具:
// Using reflection to show all the methods of a class, // even if the methods are defined in the base class. // {Args: ShowMethods} import java.lang.reflect.*; import java.util.regex.*; import static net.mindview.util.Print.*; public class ShowMethods { private static String usage = "usage: \n" + "ShowMethods qualified.class.name\n" + "To show all methods in class or:\n" + "ShowMethods qualified.class.name word\n" + "To search for methods involving 'word'"; private static Pattern p = Pattern.compile("\\w+\\."); public static void main(String[] args){ if(args.length < 1) { print(usage); System.exit(0); } int lines = 0; try { Class<?> c = Class.forName(args[0]); Method[] methods = c.getMethods(); Constructor[] ctors = c.getConstructors(); if(1 == args.length){ for(Method method : methods) print(p.matcher(method.toString()).replaceAll("")); for(Constructor ctor : ctors) print(p.matcher(ctor.toString()).replaceAll("")); lines = methods.length + ctors.length; } else { for(Method method : methods) if(method.toString().indexOf(args[1]) != -1) { print(p.matcher(method.toString()).replaceAll("")); ++lines; } for(Constructor ctor : ctors) if(ctor.toString().indexOf(args[1]) != -1) { print(p.matcher(ctor.toString()).replaceAll("")); ++lines; } } } catch(ClassNotFoundException e) { print("No such class: " + e); } } } /* Output: public static void main(String[]) public final void wait() throws InterruptedException public final void wait(long,int) throws InterruptedException public final native void wait(long) throws InterruptedException public boolean equals(Object) public String toString() public native int hashCode() public final native Class getClass() public final native void notify() public final native void notifyAll() public ShowMethods() */
动态代理
代理是基本的设计模式之一,它是你为了提供额外的、或不同的操作,而插入的用来代替“实际”对象的对象。这些操作通常涉及与“实际”对象的通信,因此代理通常充当着中间人的角色:
import static net.mindview.util.Print.*; interface Interface { void doSomething(); void somethingElse(String arg); } class RealObject implements Interface { public void doSomething() { print("doSomething"); } public void somethingElse(String arg) { print("somethingElse " + arg); } } class SimpleProxy implements Interface { private Interface proxied; public SimpleProxy(Interface proxied){ this.proxied = proxied; } public void doSomething() { print("SimpleProxy doSomething"); proxied.doSomething(); } public void somethingElse(String arg) { print("SimpleProxy somethingElse " + arg); proxied.somethingElse(arg); } } public class SimpleProxyDemo { public static void consumer(Interface iface) { iface.doSomething(); iface.somethingElse("bonobo"); } public static void main(String[] args){ consumer(new RealObject()); consumer(new SimpleProxy(new RealObject())); } } /* Output: doSomething somethingElse bonobo SimpleProxy doSomething doSomething SimpleProxy somethingElse bonobo somethingElse bonobo */
这里SimpleProxy就被插入到了客户端和RealObject之间。
任何时刻,只要你想要将额外的操作从“实际”对象中分离到不同的地方,特别是当你希望能够很容易地做出修改,从没有使用额外操作转为使用这些操作,或者反过来时,代理就显得特别有用(设计模式的关键就是封装修改——因此你需要修改事务以证明这种模式的正确性)。
Java的动态代理比代理的思想更向前迈进了一步,因为它可以动态地创建代理并动态地处理对所代理方法的调用。在动态代理上所做的所有调用都会被重定向到单一的调用处理器上,它的工作是揭示调用的类型并确定相应的策略:
import java.lang.reflect.*; class DynamicProxyHandler implements InvocationHandler { private Object proxied; public DynamicProxyHandler(Object proxied){ this.proxied = proxied; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // Print the information of proxy: (class, method, args) System.out.println("**** proxy: " + proxy.getClass() + ", method: " + method + ", args: " + args); // Print the args if(args != null) for(Object arg : args) System.out.println(" " + arg); // Invoke the method return method.invoke(proxied, args); } } public class SimpleDynamicProxy { public static void consumer(Interface iface) { iface.doSomething(); iface.somethingElse("bonobo"); } public static void main(String[] args){ RealObject real = new RealObject(); consumer(real); // Insert a proxy: Interface proxy = (Interface)Proxy.newProxyInstance( Interface.class.getClassLoader(), new Class[] { Interface.class }, new DynamicProxyHandler(real)); // Call the method in the form of proxy: consumer(proxy); } } /* Output: doSomething somethingElse bonobo **** proxy: class $Proxy2, method: public abstract void Interface.doSomething(), args: null doSomething **** proxy: class $Proxy2, method: public abstract void Interface.somethingElse(java.lang.String), args: [Ljava.lang.Object;@19245e9 bonobo somethingElse bonobo */
通过调用Proxy.newProxyInstance()可以创建动态代理,这个方法需要:
1. 一个类加载器(通常可以从已经被加载的对象中获取其类加载器,然后传递给它)
2. 一个你希望该代理实现的接口列表(不是类或者抽象类)
3. InvocationHandler接口的一个实现
动态代理可以将所有调用重定向到调用处理器,因此通常会向调用处理器的构造器传递一个“实际”对象的引用,从而使得调用处理器在执行其中介任务时,可以将请求转发。
在invoke()方法内部,在代理上调用方法时需要格外小心,因为对接口的调用将被重定向为对代理的调用。
通常,你会执行被代理的操作,然后使用method.invoke()将请求转发给被代理的对象,并传入必要的参数。
这初看起来可能有些限制,就像你只能执行泛化操作一样。但是,你可以通过传递其他的参数,来过滤掉某些方法调用:
// Looking for particular methods in a dynamic proxy. import java.lang.reflect.*; import static net.mindview.util.Print.*; class MethodSelector implements InvocationHandler { private Object proxied; public MethodSelector(Object proxied){ this.proxied = proxied; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if(method.getName().equals("interesting")) print("Proxy detected the interesting method"); return method.invoke(proxied, args); } } interface SomeMethods { void boring1(); void boring2(); void interesting(String arg); void boring3(); } class Implementation implements SomeMethods { public void boring1() { print("boring1"); } public void boring2() { print("boring2"); } public void interesting(String arg){ print("interesting " + arg); } public void boring3() { print("boring3"); } } public class SelectingMethods { public static void main(String[] args){ SomeMethods proxy = (SomeMethods)Proxy.newProxyInstance( SomeMethods.class.getClassLoader(), new Class[] { SomeMethods.class }, new MethodSelector(new Implementation())); proxy.boring1(); proxy.boring2(); proxy.interesting("bonobo"); proxy.boring3(); } } /* Output: boring1 boring2 Proxy detected the interesting method interesting bonobo boring3 */
这里,我们只查看了方法名,但是你还可以查看方法签名的其他方面,甚至可以搜索特定的参数值。
空对象
空对象可以接受传递给它的所有代表的对象的消息,但是将返回表示为实际上并不存在任何“真实”对象的值。通过这种方式,你可以假定所有的对象都是有效的,而不必浪费编程精力去检查null。
空对象最有用之处在于它更靠近数据,因为对象表示的是问题空间内的实体。使用标记接口:
package net.mindview.util; public interface Null {}
这使得instanceof可以探测空对象,更重要的是,这并不要求你在所有的类中都添加isNull()方法:
// A class with Null Object. import net.mindview.util.*; class Person { public final String first; public final String last; public final String address; // etc. public Person(String first, String last, String address){ this.first = first; this.last = last; this.address = address; } public String toString() { return "Person: " + first + " " + last + " " + address; } public static class NullPerson extends Person implements Null { private NullPerson() { super("None", "None", "None"); } public String toString() { return "NullPerson"; } } public static final Person NULL = new NullPerson(); }
通常,空对象都是单例,因此这里将其作为静态final实例创建。
你可以将Person空对象放在每个Position上:
class Position { private String title; private Person person; public Position(String jobTitle, Person employee) { title = jobTitle; person = employee; if(null == person) person = Person.NULL; } public Position(String jobTitle) { title = jobTitle; person = Person.NULL; } public String getTitle() { return title; } public void setTitle(String newTitle) { title = newTitle; } public Person getPerson() { return person; } public void setPerson(Person newPerson) { person = newPerson; if(null == person) person = Person.NULL; } public String toString() { return "Position: " + title + " " + person; } }
有了Person,你就不需要创建空对象了,因为Person.NULL的存在就表示一个空Position。之后你可能会发现需要增加显式的用于Position的空对象,但是YAGNI(You Aren't Going to Need It)声明:
在你的设计草案的初稿中,应该努力使用最简单而且可以工作的事物,直至程序的某个方面要求你添加额外的特性,而不是一开始就假设它是必须的。
Staff类现在可以在你填充职位时查询空对象:
import java.util.*; public class Staff extends ArrayList<Position> { public void add(String title, Person person) { add(new Position(title, person)); } public void add(String... titles) { for(String title : titles) add(new Position(title)); } public Staff(String... titles) { add(titles); } public boolean positionAvailable(String title) { for(Position position : this) { if(position.getTitle().equals(title) && position.getPerson() == Person.NULL) return true; } return false; } public void fillPosition(String title, Person hire) { for(Position position : this) if(position.getTitle().equals(title) && position.getPerson() == Person.NULL){ position.setPerson(hire); return; } throw new RuntimeException("Position " + title + " not available"); } public static void main(String[] args) { Staff staff = new Staff("President", "CTO", "Marketing Manager", "Product Manager", "Project Leader", "Software Engineer", "Software Engineer", "Software Engineer", "Software Engineer", "Test Engineer", "Technical Writer"); staff.fillPosition("President", new Person("Me", "Last", "The Top, Lonely At")); staff.fillPosition("Project Leader", new Person("Janet", "Planner", "The Burbs")); if(staff.positionAvailable("Software Engineer")) staff.fillPosition("Software Engineer", new Person("Bob", "Coder", "Bright Light City")); System.out.println(staff); } } /* Output: [Position: President Person: Me Last The Top, Lonely At, Position: CTO NullPerson, Position: Marketing Manager NullPerson, Position: Product Manager NullPerson, Position: Project Leader Person: Janet Planner The Burbs, Position: Software Engineer Person: Bob Coder Bright Light City, Position: Software Engineer NullPerson, Position: Software Engineer NullPerson, Position: Software Engineer NullPerson, Position: Test Engineer NullPerson, Position: Technical Writer NullPerson] */
如果你使用接口取代具体类,那么就可以使用DynamicProxy来自动地创建空对象。
假设我们有一个Robot接口,它定义了:
- 一个名字
- 一个模型
- 一个描述Robot行为能力的List<Operation>。
Operation包含:
- 一个描述
- 一个命令(这是一种命令模式类型)
public interface Operation { String description(); void command(); }
可以通过operations()来访问Robot的服务:
import java.util.*; import net.mindview.util.*; public interface Robot { String name(); String model(); List<Operation> operations(); class Test { public static void test(Robot r) { if(r instanceof Null) System.out.println("[Null Robot]"); System.out.println("Robot name: " + r.name()); System.out.println("Robot model: " + r.model()); for(Operation operation : r.operations()){ System.out.println(operation.description()); operation.command(); } } } }
这里也使用了嵌套类来执行测试。
我们可以创建一个扫雪Robot:
import java.util.*; public class SnowRemovalRobot implements Robot { private String name; public SnowRemovalRobot(String name) { this.name = name; } public String name() { return name; } public String model() { return "Snow Series 11"; } public List<Operation> operations() { return Arrays.asList( new Operation(){ public String description() { return name + " can shovel snow"; } public void command() { System.out.println(name + " shovelling snow"); } }, new Operation() { public String description() { return name + " can chip ice"; } public void command(){ System.out.println(name + " chipping ice"); } }, new Operation() { public String description() { return name + " can clear the roof"; } public void command() { System.out.println(name + " clearing roof"); } } ); } public static void main(String[] args){ Robot.Test.test(new SnowRemovalRobot("Slusher")); } } /* Output: Robot name: Slusher Robot model: Snow Series 11 Slusher can shovel snow Slusher shovelling snow Slusher can chip ice Slusher chipping ice Slusher can clear the roof Slusher clearing roof */
假设存在许多不同类型的Robot,我们想对每一种Robot类型都创建一个空对象,去执行某些特殊操作——在本例中,即提供空对象所代表的Robot确切类型的信息。这些信息是通过动态代理捕获的:
// Using a dynamic proxy to create a Null Object. import java.lang.reflect.*; import java.util.*; import net.mindview.util.*; class NullRobotProxyHandler implements InvocationHandler { private String nullName; private Robot proxied = new NRobot(); NullRobotProxyHandler(Class<? extends Robot> type) { nullName = type.getSimpleName() + " NullRobot"; } private class NRobot implements Null, Robot { public String name() { return nullName; } public String model() { return nullName; } public List<Operation> operations() { return Collections.emptyList(); } } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return method.invoke(proxied, args); } } public class NullRobot { public static Robot newNullRobot(Class<? extends Robot> type){ return (Robot)Proxy.newProxyInstance( NullRobot.class.getClassLoader(), new Class[] { Null.class, Robot.class }, new NullRobotProxyHandler(type) ); } public static void main(String[] args){ Robot[] bots = { new SnowRemovalRobot("SnowBee"), newNullRobot(SnowRemovalRobot.class) }; for(Robot bot : bots) Robot.Test.test(bot); } } /* Output: Robot name: SnowBee Robot model: Snow Series 11 SnowBee can shovel snow SnowBee shovelling snow SnowBee can chip ice SnowBee chipping ice SnowBee can clear the roof SnowBee clearing roof [Null Robot] Robot name: SnowRemovalRobot NullRobot Robot model: SnowRemovalRobot NullRobot */
无论何时,如果你需要一个空Robot对象,只需要调用newNullRobot(),并传递需要代理的Robot类型。代理会满足Robot和Null接口的需要,并提供它所代理的类型的确切名字。
接口与类型信息
interface关键字的一种重要目标就是允许程序员隔离构件,进而降低耦合性。如果你编写接口,那么就可以实现这一目标,但是通过类型信息,这种耦合性还是会传播出去——接口并非是对解耦的一种无懈可击的保障。
下面有一个实例,显示一个接口:
public interface A { void f(); }
然后实现这个接口,你可以看到其代码如何围绕着实际的实现类型潜行的:
// Sneaking around an interface. class B implements A { public void f() {} public void g() {} } public class InterfaceViolation { public static void main(String[] args) { A a = new B(); a.f(); //! a.g(); // Compile error System.out.println(a.getClass().getName()); if(a instanceof B){ B b = (B)a; b.g(); } } } /* Output: B */
通过使用RTTI,我们发现a是被当做B实现的。通过将其转型为B,我们可以调用不在A中的方法。
这是完全合法和可接受的,但是你也学并不想让客户端程序员这么做,因为这给了它们一个机会,使得他们的代码的耦合程度超过你的期望。也就是说,你可能认为interface在保护你,但是它并没有,在本例中使用B来实现A这一事实是公开有案可查的。
一种解决方案是直接声明,如果程序员决定使用实际的类而不是接口,他们需要自己对自己负责。这在很多情况下都是合理的,但“可能”还不够,也许你希望用一些更严苛的控制。
最简单的方式是对实现使用包访问权限,这样在包外的客户端就不能看到它了:
package typeinfo.packageaccess; import typeinfo.interfacea.*; import static net.mindview.util.Print.*; class C implements A { public void f() { print("public C.f()"); } public void g() { print("public C.g()"); } void u() { print("package C.u()"); } protected void v() { print("protected C.v()"); } private void w() { print("private C.w()"); } } public class HiddenC { public static A makeA() { return new C(); } }
在这个包中,唯一public的部分是HiddenC。在被调用时将产生A接口类型的对象。
这里的有趣之处在于:即是你从makeA()返回了一个C类型,你在包的外部仍旧不能使用A之外的任何方法,因为你不能在包的外部命名C。
现在,你如果试图将其向下转型为C,则将被禁止,因为在包的外部没有任何C类型可用:
// Sneaking around package access import typeinfo.interfacea.*; import typeinfo.packageaccess.*; import java.lang.reflect.*; public class HiddenImplementation { public static void main(String[] args) throws Exception { A a = HiddenC.makeA(); a.f(); System.out.println(a.getClass().getName()); /*! typeinfo.packageaccess.C is not public in typeinfo.packageaccess; *! cannot be accessed from outside package if(a instanceof C) { C c = (C)a; c.g(); } */ // Oops! Reflection still allows us to call g(); callHiddenMethod(a, "g"); // And even methods that are less accessible! callHiddenMethod(a, "u"); callHiddenMethod(a, "v"); callHiddenMethod(a, "w"); } static void callHiddenMethod(Object a, String methodName) throws Exception { Method g = a.getClass().getDeclaredMethod(methodName); g.setAccessible(true); g.invoke(a); } } /* Output: public C.f() typeinfo.packageaccess.C public C.g() package C.u() protected C.v() private C.w() */
正如以上代码所显示的,通过使用反射,仍旧可以到达并调用所有方法,甚至是private方法!如果知道方法名,你就可以在其Method对象上调用setAccessible(true) ,就像在callHiddenMethod()中看到的那样。
你可能会认为,可以通过发布编译后的代码来阻止这种情况。但这并不解决问题。因为只需运行javap,一个随JDK发布的反编译器即可突破这一限制。下面是一个使用它的命令:
javap -private C
-private标志表示所有的成员都应该显示,甚至包括私有成员。下面是输出:
class typeinfo.packageaccess.C implements typeinfo.interfacea.A { typeinfo.packageaccess.C(); public void f(); public void g(); void u(); protected void v(); private void w(); }
因此任何人都可以获取你最私有的方法的名字和签名,然后调用它们。
如果你将接口实现为一个私有内部类,又会怎么呢?下面展示了这种情况:
// Private inner classes can't hide from reflection. import typeinfo.interfacea.*; import net.mindview.util.Print.*; class InnerA { private static class C implements A { public void f() { print("public C.f()"); } public void g() { print("public C.g()"); } void u() { print("package C.u()"); } protected void v() { print("protected C.v()"); } private void w() { print("private C.w()"); } } public static A makeA() { return new C(); } } public class InnerImplementation { public static void main(String[] args) throws Exception { A a = InnerA.makeA(); a.f(); System.out.println(a.getClass().getName()); // Reflection still gets into the private class: HiddenImplementation.callHiddenMethod(a, "g"); HiddenImplementation.callHiddenMethod(a, "u"); HiddenImplementation.callHiddenMethod(a, "v"); HiddenImplementation.callHiddenMethod(a, "w"); } } /* Output: public C.f() InnerA$C public C.g() package C.u() protected C.v() private C.w() */
这里对反射仍旧没有隐藏任何东西。那么如果是匿名类呢?
// Anonymous inner classes can't hide from reflection. import typeinfo.interfacea.*; import static net.mindview.util.Print.*; class AnonymousA { public static A makeA() { return new A(){ public void f() { print("public C.f()"); } public void g() { print("public C.g()"); } void u() { print("package C.u()"); } protected void v() { print("protected C.v()"); } private void w() { print("private C.w()"); } }; } } public class AnonymousImplementation { public static void main(String[] args) throws Exception { A a = AnonymouA.makeA(); a.f(); // Reflection still gets into the anonymous class: HiddenImplementation.callHiddenMethod(a, "g"); HiddenImplementation.callHiddenMethod(a, "u"); HiddenImplementation.callHiddenMethod(a, "v"); HiddenImplementation.callHiddenMethod(a, "w"); } } /* Output: public C.f() AnonymousA$1 public C.g() package C.u() protected C.v() private C.w() */
看起来没有任何方式可以阻止反射到达并调用那些非公共访问权的方法。对于域来说,的确如此,即便是private域:
import java.lang.reflect.*; class WithPrivateFinalField { private int i = 1; private final String s = "I'm totally safe"; private String s2 = "Am I safe?"; public String toString() { return "i = " + i + ", " + s + ", " + s2; } } public class ModifyingPrivateFields { public static void main(String[] args) throws Exception { WithPrivateFinalField pf = new WithPrivateFinalField(); System.out.println(pf); Field f = pf.getClass().getDeclaredField("i"); f.setAccessible(true); System.out.println("f.getInt(pf): " + f.getInt(pf)); f.setInt(pf, 47); System.out.println(pf); f = pf.getClass().getDeclaredField("s"); f.setAccessible(true); System.out.println("f.getInt(pf): " + f.get(pf)); f.set(pf, "No, you're not"); System.out.println(pf); f = pf.getClass().getDeclaredField("s2"); f.setAccessible(true); System.out.println("f.getInt(pf): " + f.get(pf)); f.set(pf, "No, you're not"); System.out.println(pf); } } /* Output: i = 1, I'm totally safe, Am I safe? f.getInt(pf): 1 i = 47, I'm totally safe, Am I safe? f.getInt(pf): I'm totally safe i = 47, I'm totally safe, Am I safe? f.getInt(pf): Am I safe? i = 47, I'm totally safe, No, you're not */
但是,final域实际上在遭遇修改时是安全的。运行时系统在不会抛异常的情况下接受任何修改尝试,但实际上不会发生任何修改。