框架都要用到反射技術,反射都要用到一個類Class.
java程序中的各個java類屬于同一類事物,描述這類事物的java類名就是Class.
得到字節碼的方式有三種:
Date.class;new Date().getClass();Class.forName("java.lang.String");最后一種是有直接返回,沒有則從硬盤加載。
有9個預定義的Class實例對象,8種基本數據類型加void.class:參考Class.isPrimitive方法doc;
public class ReflectTest {public static void main(String[] args) throws Exception {// args:javaplay.TestArgumentsString str1 = "abc";Class<?> cls1 = str1.getClass();Class<?> cls2 = String.class;Class<?> cls3 = Class.forName("java.lang.String");System.out.println(cls1 == cls2);// trueSystem.out.println(cls1 == cls3);// trueSystem.out.println(cls1.isPrimitive());// falseSystem.out.println(int.class.isPrimitive());// trueSystem.out.println(int.class == Integer.class);// false// Integer.TYPE代表它所包裝的基本類型字節碼,請查看docSystem.out.println(int.class == Integer.TYPE);// trueSystem.out.println(int[].class.isPrimitive());// falseSystem.out.println(int[].class.isArray());// trueSystem.out.println(void.class == Void.class);// false// 總之,在源程序中出現的類型,都有各自的Class實例對象,例如int[],void// 通常方式:String str = new String(new StringBuffer("abc"));// jdk5之前只能接受數組,不接受可變參數// Constructor constructor = String.class.getConstructor(new Class<?>[]// { StringBuffer.class, int.class });// 得到所有public構造方法Constructor[] constructors = Class.forName("java.lang.String").getConstructors();// 得到一個構造方法Constructor constructor1 = String.class.getConstructor(StringBuffer.class);// 反射方式(詳見doc 一目了然,不能是"abc",要用到與StringBuffer相同類型的對象)String str2 = (String) constructor1.newInstance(/* "abc" */new StringBuffer("abc"));System.out.println(str2.charAt(2));// c// Class.newInstance()方法,可省去class->constructors->new object中間獲取構造方法的環節// 從而直接創建對象,但它只能調用默認的無參構造方法,該方法內部先得到默認構造方法,然后用該構造方法創建對象,其中// 用到了緩存機制來保存默認構造方法的實例,這樣能省點事,直接調用newInstance而不用獲取構造方法的反射String o = (String) Class.forName("java.lang.String").newInstance();// 成員變量的反射ReflectPoint pt1 = new ReflectPoint(3, 5);Field fieldY = pt1.getClass().getField("y");// fieldY的值是多少?是5,錯!fieldY不是對象身上的變量,而是類上,要用它去取某個對象上對應的值System.out.println(fieldY.get(pt1));Field fieldX = pt1.getClass().getDeclaredField("x");// 私有成員要用DeclaredfieldX.setAccessible(true);// 設置true才可以訪問,暴力反射System.out.println(fieldX.get(pt1));// 將任意一個對象中的所有String類型的成員變量所對應的字符串內容中的"b"改成"a"changeStringValue(pt1);System.out.println(pt1);// 成員方法的反射// 普通方式:str.charAt(1),反射方式如下:Method methodCharAt = String.class.getMethod("charAt", int.class);System.out.println(methodCharAt.invoke(str1, 1));// System.out.println(methodCharAt.invoke(null, 1));//靜態方法第一個參數才為null// 1.4還有可變參數,只能用數組,又因為參數五花八門,所以用Object,1.4的調用語法如下:System.out.println(methodCharAt.invoke(str1, new Object[] { 1 }));// 對接受數組參數的成員方法進行反射// 寫程序的時候不知道要執行哪個類的mainTestArguments.main(new String[] { "111", "222", "333" });String startingClassName = args[0];Method mainMethod = Class.forName(startingClassName).getMethod("main", String[].class);mainMethod.invoke(null, new Object[] { new String[] { "111", "222", "333" } });// 原因在于1.5要兼容1.4mainMethod.invoke(null, (Object) new String[] { "111", "222", "333" });// 也行,總之,只能接受一個參數// mainMethod.invoke(null, (Object) new String[] { "111" }, (Object) new// String[] { "222" });// 不行// mainMethod.invoke(null, new String[] { "111" });// 不行/*** 啟動java程序的main方法的參數是一個字符串數組,通過反射方式調用時,如何傳遞參數呢,按1.5的語法,整個數組是一個參數,* 而按1.4的語法,數組中的每個元素對應一個參數,當把一個字符串數組作為參數傳遞給invoke方法時,javac會按照哪種方法進行* 處理呢?1.5肯定要兼容1.4,會按1.4的語法來處理,即把數組打散成若干個單獨的參數,所以,在給main方法傳遞參數時,不能使用* mainMethod.invoke(null,new* String[]{"xxx"}),javac會把它當作1.4語法來理解,會把里面的"xxx"當作需要傳遞的String[]{},所以* 參數類型不對。*//** 數組與Object的關系及其反射類型 . 1.具有相同維數和元素類型的數組屬于同一個類型,即具有相同的Class實例對象* 2.代表數組的Class實例對象的getSuperclass()方法返回的父類為Object類對應的Class* 3.基本類型的一維數組可以被當作Object類型使用,不能當作Object[]類型使用;非基本類型的一維數組,既* 可以當做Object類型使用,又可以當作Object[]類型使用* 4.Arrays.asList()方法處理int[]和String[]時的差異*/int[] a1 = new int[] { 1, 2, 3 };Integer[] a11 = new Integer[] { 1, 2, 3 };int[] a2 = new int[4];int[][] a3 = new int[2][3];String[] a4 = new String[] { "a", "b", "c" };System.out.println(a1.getClass() == a2.getClass());// true 數組的類型和維度相同就一樣// System.out.println(a1.getClass() == a4.getClass());//false// System.out.println(a1.getClass() == a3.getClass());//falseSystem.out.println(a1.getClass().getName());// [I Class的getName方法有詳細說明[ISystem.out.println(a1.getClass().getSuperclass().getName());// java.lang.ObjectSystem.out.println(a4.getClass().getSuperclass().getName());// java.lang.ObjectSystem.out.println(a4.getClass().getName());// [Ljava.lang.String;System.out.println(String.class.getName());// java.lang.StringSystem.out.println(String.class.getSuperclass().getName());// java.lang.ObjectSystem.out.println(a11.getClass().getName());// [Ljava.lang.Integer;System.out.println(a11.getClass().getSuperclass().getName());// java.lang.ObjectObject aObj1 = a1;Object bObj2 = a4;Object aObj11 = a11;// Object[] aObj3 = a1;// 不能賦值,int不是ObjectObject[] aObj4 = a3;// a3是一維數組的數組,Object相當于一個一維int數組,此時Object[]表示一維數組(int[])的數組Object[] aObj5 = a4;// String是ObjectObject[] aObj6 = a11;// 由此引申的一個問題System.out.println(a1);// [I@5fb7a531System.out.println(a4);// [Ljava.lang.String;@11be650f// 確實轉換成了數組,只是數組里只有一個元素,這個元素也是數組System.out.println(Arrays.asList(a1));// [[I@5fb7a531]// 字符串卻可以轉換,原因在于asList接受的是Object[](1.4)和T...(1.5),如果是String[]就按// 1.4的形式(因為符合1.4的形式,要向下兼容)轉換成了list了,// 如果是int[]就不符合Object[]的形式,就會按1.5的形式T...來處理,也即當成一個int[](即T)類型的元素System.out.println(Arrays.asList(a4));// [a, b, c]// 數組的反射printObject(a1);// 1 2 3printObject(a4);// a b cprintObject("xyz");// xyz// 有沒有辦法得到數組的類型?// 目前沒有 Object[] a = new Object[]{"a",1};// a[0].getClass();// 即只能得到某個具體元素的類型,不能得到整個數組的元素類型// 寫框架這些東西是必備,不寫框架,翻某些磚頭似的書像翻小說一樣了,this is 進步。}public static void changeStringValue(Object obj) throws Exception {Field[] fields = obj.getClass().getFields();for (Field field : fields) {if (field.getType() == String.class) {// 同一份字節碼,推薦用==String oldValue = (String) field.get(obj);String newValue = oldValue.replace("b", "a");field.set(obj, newValue);}}}public static void printObject(Object obj) {Class clazz = obj.getClass();if (clazz.isArray()) {// 對數組進行反射的類Arrayint len = Array.getLength(obj);for (int i = 0; i < len; i++) {System.out.println(Array.get(obj, i));}} else {System.out.println(obj);}}}class TestArguments {public static void main(String[] args) {for (String arg : args) {System.out.println(arg);}}
}
public class ReflectPoint {private int x;public int y;public String str1 = "ball";public String str2 = "basketball";public String str3 = "itcast";public ReflectPoint(int x, int y) {super();this.x = x;this.y = y;}@Overridepublic int hashCode() {final int prime = 31;int result = 1;result = prime * result + x;result = prime * result + y;return result;}@Overridepublic boolean equals(Object obj) {if (this == obj)return true;if (obj == null)return false;if (getClass() != obj.getClass())return false;ReflectPoint other = (ReflectPoint) obj;if (x != other.x)return false;if (y != other.y)return false;return true;}@Overridepublic String toString() {return "ReflectPoint [x=" + x + ", y=" + y + ", str1=" + str1 + ", str2=" + str2 + ", str3=" + str3 + "]";}}
反射就是把java類中的各種成分映射成相應的java類,例如,一個java類用一個Class類的對象表示,一個類中的組成部分:成員變量、方法、構造方法、包等信息也用一個個的java類來表示,它們是Field、Method、Constructor、Package等。
一個類中的每個成員都可以用相應的反射API類的一個實例對象來表示,通過調用Class類的方法可以得到這些實例對象。
ArrayList_HashSet比較及hashcode分析:
public class ReflectTest2 {public static void main(String[] args) {Collection<ReflectPoint> collections = new HashSet<>();ReflectPoint pt1 = new ReflectPoint(3, 3);ReflectPoint pt2 = new ReflectPoint(5, 5);ReflectPoint pt3 = new ReflectPoint(3, 3);collections.add(pt1);collections.add(pt2);collections.add(pt3);collections.add(pt1);pt1.y = 7;// 修改后輸出2,不修改,輸出1collections.remove(pt1);// new ArrayList<>();輸出4,添加時不比較直接按順序存儲引用變量、// new HashSet<>();輸出3,存放時會先判斷集合里有沒有這個對象,有就不放不是覆蓋,想覆蓋要先remove再放// HashSet比較對象時調用對象默認equals是比較內存地址(通常是根據內存地址換算出來的,效果與==等價)// 實現equals和hashcode方法后輸出2,如果此時注釋hashcode或者equals其中任意一個則又輸出3了// 原因在于它會先根據hashcode的值,算出自己要存放的最終位置所在的區域,如果只注釋hashcode是有可能輸出2的,因為存放的區域可能相同,也可能不同// 再調用equals進行比較是否有相等的對象(hashcode只在hash算法的集合中才有意義,其它集合沒有任何價值),// 這就是輸出3的原因,pt1和pt3存放的區域不一樣,原因是沒有實現hashcode則根據內存地址進行計算存放的區域,// 為了讓相等的對象也肯定放在相同的區域,就有一個說法,如果兩個對象equals相等,則應該也要讓hashcode相等(不是hash集合就不要搞hashcode)// 注意:當一個對象被存儲進hashset集合中,就不能修改這個對象中那些參與計算哈希值的字段了。否則就刪除不了了,日積月累,// 不斷的添加、修改、刪除對象,而程序卻一直使用這個hash集合就會造成內存泄漏!因為hash集合中有很多對象已經沒用了卻沒釋放// 由hashcode的作用講到內存泄漏,由內存泄漏講到hashcode的作用!System.out.println(collections.size());}}
反射的作用->實現框架的功能
框架與工具類有區別,工具類被用戶的類調用,而框架則是調用用戶提供的類。
用反射技術開發框架的原理:簡單框架,把要使用的類放到配置文件里面
在工程根目錄下創建config.properties文件:
className=java.util.HashSet
public class ReflectTest2 {public static void main(String[] args) throws Exception {InputStream ips = new FileInputStream("config.properties");Properties props = new Properties();props.load(ips);ips.close();// 跟操作系統說,把window干掉,把自己關聯的系統/物理資源釋放,自己則由垃圾回收器管理String className = props.getProperty("className");Collection<ReflectPoint> collections = (Collection<ReflectPoint>) Class.forName(className).newInstance();// Collection<ReflectPoint> collections = new HashSet<>();ReflectPoint pt1 = new ReflectPoint(3, 3);ReflectPoint pt2 = new ReflectPoint(5, 5);ReflectPoint pt3 = new ReflectPoint(3, 3);collections.add(pt1);collections.add(pt2);collections.add(pt3);collections.add(pt1);// pt1.y = 7;// 修改后輸出2,不修改,輸出1// collections.remove(pt1);System.out.println(collections.size());// config.properties文件內容是HashSet:2,ArrayList:4}}
用類加載器的方式管理資源和配置文件
public class ReflectTest2 {public static void main(String[] args) throws Exception {// eclipse會自動把包下面的java文件或者普通文件編譯后拷貝到classpath路徑下// InputStream ips = new FileInputStream("config.properties");// 類加載器能加載.class文件,那加載普通文件也是順帶的事,下表示在classpath指定的根目錄下去找指定的文件// 根目錄下肯定沒有,只是根目錄下的javaplay目錄下才有,此時不能定寫成/javaplay// ssh框架內部用的就是用的類加載器加載的配置文件,所以ssh的配置文件必須放在classpath指定的目錄下,原因就是它// 用的是類加載器讀取的配置文件,而第一種不僅可以讀還可以寫即保存,類加載器的方式只能讀不能寫InputStream ips11 = ReflectTest2.class.getClassLoader().getResourceAsStream("javaplay/config.properties");// 類提供了一個便捷方法不用獲取類加載器就可以加載配置文件,而且默認加載與自己同一個包內的配置文件InputStream ips2 = ReflectTest2.class.getResourceAsStream("config.properties");// 也可以相對路徑InputStream ips3 = ReflectTest2.class.getResourceAsStream("resource/config.properties");// 也可以絕對路徑,此時必須要以/開頭,不管相對還是絕對內部都是調用InputStream ips = ReflectTest2.class.getResourceAsStream("/javaplay/resource/config.properties");Properties props = new Properties();props.load(ips);ips.close();// 跟操作系統說,把window干掉,把自己關聯的系統/物理資源釋放,自己則由垃圾回收器管理String className = props.getProperty("className");Collection<ReflectPoint> collections = (Collection<ReflectPoint>) Class.forName(className).newInstance();// Collection<ReflectPoint> collections = new HashSet<>();ReflectPoint pt1 = new ReflectPoint(3, 3);ReflectPoint pt2 = new ReflectPoint(5, 5);ReflectPoint pt3 = new ReflectPoint(3, 3);collections.add(pt1);collections.add(pt2);collections.add(pt3);collections.add(pt1);System.out.println(collections.size());// config.properties文件內容是HashSet:2,ArrayList:4}}
?
?
?
?
?