五、參數數量可變的方法
在Java中使用省略號...的形式來創建參數數量可變的方法。
public static double max( double... values);
調用方式:1> max(3.1, 40.4,-5);
2>也運行將一個數組傳遞給可變參數方法的最後一個參數。
注:因此,可以將已經存在且最後一個參數是數組的方法重新定義爲可變參數的方法,而不會破壞任何已經存在的代碼。
例如:main方法可修改爲 public static void main(String... args)
六、枚舉類
例如: public enum Size {SMALL,MEDIUM,LARGE,EXTRA_LARGE};
這個聲明定義的類型時一個類,它剛好有4個實例,在此儘量不要構造新對象。
注:比較兩個枚舉類型的值,不需要調用equals,而直接使用“==”就可以了。
1>可以在枚舉類型中添加一些構造器、方法和域。當然,構造器只是在構造枚舉常量的時候調用。
所有枚舉類型都是Enum類的子類。它們繼承了這個類的許多方法。其中最有用的一個是toString,這個方法能夠返回枚舉常量名。public enum Size { SMALL("S"),MEDIUM("M"),LARGE("L"),EXTRA_LARGE("XL"); private String abbreviation; private Size(String abbreviation){ this.abbreviation = abbreviation; } public String getAbbreviation(){ return abbreviation; } }
2>toString的逆方法時靜態方法valueOf。
3>每個枚舉類型都有一個靜態的values方法,它將返回一個包含全部枚舉值的數組。
4>ordinal方法返回enum聲明中枚舉常量的位置,位置從0開始計數
如同Class類一樣,鑑於簡化的考慮,Enum類省略了一個類型參數。
七、反射
反射庫提供了一個非常豐富且精心設計的工具集,以便編寫能夠動態操縱Java代碼的程序。
使用反射,Java可以支持Visual Basic用戶習慣使用的工具。特別是在設計或運行中添加新類時,能夠快速地應用開發工具動態地查詢新添加類的能力。
1>能夠分析類能力的程序稱爲反射。
2>反射機制可以用來:
a>在運行中分析類的能力。
b>在運行中查看對象,例如,編寫一個toString方法供所有類使用。
c>實現通用的數組操作代碼。
d>利用Method對象,這個對象很想C++中的函數指針。
注:反射是一種功能強大且複雜的機制。使用的主要人員是工具構造者,而不是應用程序員。
( 1 )Class類
在程序運行期間,Java運行時系統始終爲所有對象維護一個被稱爲運行時的類型標識。這個信息跟蹤着每個對象所屬的類。
虛擬機利用運行時類型信息選擇相應的方法執行。
可以通過專門的Java類訪問這些信息。保存這些信息的類被稱爲Class。
1>獲取Class類型實例的方式:
a>Object類中的getClass()方法。
b>Class類中的靜態方法forName獲取Class對象。
注:使用forName方法將拋出一個checkedexception(已檢查異常)
c>如果T是任意的Java類型,T.class將代表匹配的類對象。注:一個Class對象實際上表示的是一個類型,而這個類型未必一定是一種類。
例如:int不是類,但是int.class是一個Class類型的對象。
從Java SE5.0開始,Class類已參數化。例如,Class<Employee>
2>虛擬機爲每個類型管理一個Class對象。因此,可以利用==運算符實現兩個類對象比較的操作。
3>使用newInstance()可以快速創建一個類的實例。
e.getClass().newInstance();
newInstance方法調用默認的構造器初始化新創建的對象,如果這個類沒有默認的構造器,將拋出一個異常。
注:如果需要以這種方式向希望按名稱創建的類的構造器提供參數,就不要使用上面那條語句,而必須使用Constructor類中的newInstance方法。
( 2 )捕獲異常
1>異常有兩種類型:未檢測異常和已檢查異常。
對於已檢查異常,編譯器將會檢查是否提供了處理器;對於未檢查異常,編譯器不會查看是否爲這些錯誤提供了處理器。
如訪問null引用。
2>將可能拋出已檢查異常的一個或多個方法調用代碼放在try塊中,然後在catch子句中提供處理器代碼。
注:在catch子句中可以利用Throwable類的printStackTrace方法打印出棧的軌跡。Throwable是Exception類的子類。
( 3 )利用反射分析類的能力
在java.lang.reflect包中有三個類Field、Method和Constructor分別用於描述類的域、方法和構造器。
Field類有一個getType方法,用於返回描述域所屬類型的Class對象。
Method和Constructor類有能夠報告參數類型的方法。
如下代碼爲本人學習反射的初步應用:
public static void testClass() { //從控制檯獲取類全名 Scanner in = new Scanner(System.in); String className = in.next(); try { Class myClass = Class.forName(className); //獲取全部的方法 Method[] myMethod = myClass.getDeclaredMethods(); //調用無參構造器創建一個實例對象 Object obj = myClass.newInstance(); for (Method m : myMethod) { //判斷當前方法是否含有參數 if(m.getParameterTypes().length == 0){ //打印當前方法名和調用結果 System.out.println(m.getName()+" "+m.invoke(obj, null)); } } } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InstantiationException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalArgumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InvocationTargetException e) { // TODO Auto-generated catch block e.printStackTrace(); } in.close(); }
( 4 )在運行時使用反射分析對象
1>使用反射可以查看任意對象的數據域名稱和類型,但是如果查看私有域將會拋出一個IllegalAccessException。
除非擁有訪問權限,否則Java安全機制只運行查看任意對象的那些域,而不允許讀取它們的值。
2>反射機制的默認行爲受限於Java的訪問控制,但是可以通過調用Field、method或Constructor對象的setAccessible方法來不受安全管理器的控制,達到覆蓋訪問控制。
3>setAccessible方法是AccessibleObject類中的一個方法,它是Field、Method和Constructor類的公共超類。這個特性是爲了調試、持久存儲和相似機制提供的。
注:
a> void setAccessible(boolean flag) //java.lang.reflect.AccessibleObject
爲反射對象設置可訪問標誌。flag爲true表明屏蔽Java語言的訪問檢查,使得對象的私有屬性也可以被查詢和設置。
b> static void setAccessible(AccessibleObject[] array,boolean flag)
設置對象數組可訪問標誌的快捷方式。
( 5 )使用反射編寫泛型數組代碼import java.lang.reflect.AccessibleObject; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; /** * @author Dyce * @date 2015年12月28日 上午11:05:36 * @version 1.0 */ public class ObjectAnalyzer { private ArrayList<Object> visited = new ArrayList<>(); /** * 通用的toString方法 * @param obj * 調用方式: 重寫本類的toString方法 * new ObjectAnalyzer().toString(this); * @return */ public String toString(Object obj) { if (obj == null) return "null"; if (visited.contains(obj)) return "..."; visited.add(obj); Class cl = obj.getClass(); if (cl == String.class) return (String) obj; if (cl.isArray()) { String r = cl.getComponentType() + "[]{"; for (int i = 0; i < Array.getLength(obj); i++) { if (i > 0) r += ","; Object val = Array.get(obj, i); if (cl.getComponentType().isPrimitive()) r += val; else r += toString(val); } return r + "}"; } String r = cl.getName(); // inspect the fields of this class and all superclasses do { r += "["; Field[] fields = cl.getDeclaredFields(); AccessibleObject.setAccessible(fields, true); // get the names and values of all fields for (Field f : fields) { if (!Modifier.isStatic(f.getModifiers())) { if (!r.endsWith("[")) r += ","; r += f.getName() + "="; try { Class t = f.getType(); Object val = f.get(obj); if (t.isPrimitive()) r += val; else r += toString(val); } catch (Exception e) { e.printStackTrace(); } } } r += "]"; cl = cl.getSuperclass(); } while (cl != null && cl != Object.class); return r; } }
java.lang.reflect包中的Array類允許動態地創建數組。
過程:
1>首先獲得a數組的類對象
2>確定它是一個數組
3>使用Class類的getComponentType方法確認數組對應的類型。
public static Object goodCopyOf(Object a, int newLength) { Class cl = a.getClass(); if (!cl.isArray()) return null; Class componentType = cl.getComponentType(); int length = Array.getLength(a); Object newArray = Array.newInstance(componentType, newLength); System.arraycopy(a, 0, newArray, 0, Math.min(length, newLength));//通過已有的數組來創建新數組 return newArray; }
( 6 )調用任意方法
Method類中有一個invoke方法,它允許調用包裝在當前Method對象中的方法。
invoke方法的簽名是:Object invoke(Object obj,Object... args)
第一個參數是隱式參數,其餘的對象提供顯示參數。
注:對於靜態方法,第一個參數可以被忽略,即可以將它設置爲null。
總結:
1>invoke的參數和返回值必須是Object類型,這意味着必須進行多次的類型轉換,這會使編譯器錯過檢查代碼的機會。等到測試階段才發現錯誤,找到和修改變得更加困難。
2>使用反射獲得方法指針的代碼要比僅僅直接調用方法明顯慢一些。
3>鑑於Java開發者不要使用Method對象的回調功能。使用接口進行回調會使得代碼的執行速度更快,更易於維護。