黑馬程序員——反射

------- android培訓java培訓、期待與您交流! ----------

第一講    反射的應用場景

一、反射技術的概述

        Java反射機制是在運行狀態中,對於任意一個類,都能夠知道這個類中的所有屬性和方法;對於任意一個對象,都能夠調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱爲java語言的反射機制。

        簡單一句話:反射技術可以對類進行解剖。

 

二、反射的應用場景

        一個已經可以使用的應用程序,因爲程序已經做好可以運行使用,不能再進行代碼的加入了。而當後期我們新的功能加入程序時,該怎麼做呢?就如我們的電腦一樣,後期我們可能會鼠標、鍵盤等,所以電腦給我們預留了usb接口,只要符合這個接口規則的設備,電腦就可以通過加載驅動等操作來使用。

        那這個程序能用了,如何使用後期出現的功能類呢?

        常用的作法,會提供一個配置文件,來供以後實現此程序的類來擴展功能。對外提供配置文件,讓後期出現的子類直接將類名字配置到配置文件中即可。該應用程序直接讀取配置文件中的內容。並查找和給定名稱相同的類文件。進行如下操作:

        1)加載這個類。

        2)創建該類的對象。

        3)調用該類中的內容。

       應用程序使用的類不確定時,可以通過提供配置文件,讓使用者將具體的子類存儲到配置文件中。然後該程序通過反射技術,對指定的類進行內容的獲取。

        好處:反射技術大大提高了程序的擴展性。

 

第二講     反射涉及的對象

一、概述

        反射就是把Java類中的各種成分映射成相應的java類。

        例如,一個Java類中用一個Class類的對象來表示,一個類中的組成部分:成員變量,方法,構造方法,包等等信息也用一個個的Java類來表示。就像汽車是一個類,汽車中的發動機,變速箱等等也是一個個的類。表示java類的Class類顯然要提供一系列的方法,來獲得其中的變量,方法,構造方法,修飾符,包等信息,這些信息就是用相應類的實例對象來表示,它們是FieldMethodContructorPackage等等。

        一個類中的每個成員都可以用相應的反射API類的一個實例對象來表示,通過調用Class類的方法可以得到這些實例對象後,得到這些實例對象後有什麼用呢?怎麼用呢?這正是學習和應用反射的要點。

 

二、反射的基石——Class

1、所有的類文件都有共同屬性,所以可以向上抽取,把這些共性內容封裝成一個類,這個類就叫Class(描述字節碼文件的對象)。

         Class類中就包含屬性有field(字段)、method(方法)、construction(構造函數)。

        而field中有修飾符、類型、變量名等複雜的描述內容,因此也可以將字段封裝稱爲一個對象。用來獲取類中field的內容,這個對象的描述叫Field。同理方法和構造函數也被封裝成對象MethodConstructor。要想對一個類進行內容的獲取,必須要先獲取該字節碼文件的對象。該對象是Class類型。

        Class類描述的信息:類的名字,類的訪問屬性,類所屬於的包名,字段名稱的列表,方法名稱的列表等。每一個字節碼就是class的實例對象。如:classcls=Data.class;

小知識:什麼叫字節碼?

        當源程序中用到類時,首先要從硬盤把這個類的那些二進制代碼,一個類編譯成class放在硬盤上以後,就是一些二進制代碼,要把這些二進制代碼加載到內存中裏面來,再用這些字節碼去複製出一個一個對象來。

2Classclass的區別

        1classJava中的類用於描述一類事物的共性,該類事物有什麼屬性,沒有什麼屬性,至於這個屬性的值是什麼,則由此類的實例對象確定,不同的實例對象有不同的屬性值。

        2Class:指的是Java程序中的各個Java類是屬於同一類事物,都是Java程序的類,這些類稱爲Class。例如人對應的是Person類,Java類對應的就是ClassClassJava程序中各個Java類的總稱;它是反射的基石,通過Class類來使用反射。

3、獲取Class對象的三種方式

        加載XX.class文件進內存時就被封裝成了對象,該對象就是字節碼文件對象。如何獲取Class對象呢?

方式一:

        通過對象的getClass方法進行獲取。

        如:Class clazz=new Person().getClass();//Person是一個類名

        麻煩之處:每次都需要具體的類和該類的對象,以及調用getClass方法。

方式二:

        任何數據類型都具備着一個靜態的屬性class,這個屬性直接獲取到該類型的對應Class對象。

        如:Class clazz=Person.class;//Person是一個類名

        比第一種較爲簡單,不用創建對象,不用調用getClass方法,但是還是要使用具體的類,和該類中的一個靜態屬性class完成。

方式三:

        這種方式較爲簡單,只要知道類的名稱即可。不需要使用該類,也不需要去調用具體的屬性和行爲。就可以獲取到Class對象了。

        如:Class clazz=Class.forName("包名.Person");//Person是一個類名

        這種方式僅知道類名就可以獲取到該類字節碼對象的方式,更有利於擴展。

注:

        1、九個預定義的Class

                1)包括八種基本類型(byteshortintlongfloatdoublecharboolean)的字節碼對象和一種返回值爲void類型的void.class

                2Integer.TYPEInteger類的一個常量,它代表此包裝類型包裝的基本類型的字節碼,所以和int.class是相等的。基本數據類型的字節碼都可以用與之對應的包裝類中的TYPE常量表示

        2、只要是在源程序中出現的類型都有各自的Class實例對象,如int[].class。數組類型的Class實例對象,可以用Class.isArray()方法判斷是否爲數組類型的。

4Class類中的方法

        static Class forName(String className)

        返回與給定字符串名的類或接口的相關聯的Class對象。

        Class getClass()

        返回的是Object運行時的類,即返回Class對象即字節碼對象

        Constructor getConstructor()

        返回Constructor對象,它反映此Class對象所表示的類的指定公共構造方法。

        Field getField(String name)

        返回一個Field對象,它表示此Class對象所代表的類或接口的指定公共成員字段。

        Field[] getFields()

        返回包含某些Field對象的數組,表示所代表類中的成員字段。

        Method getMethod(String name,Class parameterTypes)

        返回一個Method對象,它表示的是此Class對象所代表的類的指定公共成員方法。

        Method[] getMehtods()

        返回一個包含某些Method對象的數組,是所代表的的類中的公共成員方法。

        String getName()

        以String形式返回此Class對象所表示的實體名稱。

        String getSuperclass()

        返回此Class所表示的類的超類的名稱

        boolean isArray()

        判定此Class對象是否表示一個數組

        boolean isPrimitive()

        判斷指定的Class對象是否是一個基本類型。

        T newInstance()

        創建此Class對象所表示的類的一個新實例。

5、通過Class對象獲取類實例

        通過查看API我們知道,Class類是沒有構造方法的, 因此只能通過方法獲取類實例對象。之前我們用的已知類,創建對象的做法:

        1)查找並加載XX.class文件進內存,並將該文件封裝成Class對象。

        2)再依據Class對象創建該類具體的實例。

        3)調用構造函數對對象進行初始化。

             如:Person p=new Person();

 現在用Class對象來獲取類實例對象的做法:

        1)查找並加載指定名字的字節碼文件進內存,並被封裝成Class對象。

        2)通過Class對象的newInstance方法創建該Class對應的類實例。

        3)調用newInstance()方法會去使用該類的空參數構造函數進行初始化。

             如:

                     String className="包名.Person";

                     Class clazz=Class.forName(className);

                     Object obj=clazz.newInstance();

示例:

[java] view plaincopy
  1. //Person類  
  2. package cn.itheima;  
  3.   
  4. public class Person {  
  5.     private String name;  
  6.     public int age;  
  7.     public Person(){  
  8.         System.out.println("Person is run");  
  9.     }  
  10.     public Person(String name,int age){  
  11.         this.age=age;  
  12.         this.name=name;  
  13.     }  
  14.       
  15.     public String toString(){  
  16.         return name+":"+age;  
  17.     }  
  18. }  
  19. //示例  
  20. package cn.itheima;  
  21.   
  22. public class CreateClassDemo {  
  23.     public static void main(String[] args) throws Exception {  
  24.         createPersonClass();  
  25.     }  
  26.     //通過Class對象創建類實例方法  
  27.     public static void createPersonClass() throws Exception{  
  28.         //獲取Person類的Class對象  
  29.         String className="cn.itheima.Person";  
  30.         Class clazz=Class.forName(className);  
  31.         //通過newInstance方法獲取類的無參構造函數實例  
  32.         Person p=(Person)clazz.newInstance();  
  33.     }  
  34. }  

 

三、Constructor

1、概述

        如果指定的類中沒有空參數的構造函數,或者要創建的類對象需要通過指定的構造函數進行初始化。這時怎麼辦呢?這時就不能使用Class類中的newInstance方法了。既然要通過指定的構造函數進行對象的初始化。就必須先獲取這個構造函數——ConstructorConstructor代表某個類的構造方法。

2、獲取構造方法:

        1)得到這個類的所有構造方法:如得到上面示例中Person類的所有構造方法

              Constructor[] cons = Class.forName(“cn.itheima.Person”).getConstructors();

        2)獲取某一個構造方法:

              Constructor con=Person.class.getConstructor(String.class,int.class);

3、創建實例對象:

        1)通常方式:Person p = new Person(“lisi”,30);

         2)反射方式:Person p= (Person)con.newInstance(“lisi”,30);

注:

        1、創建實例時newInstance方法中的參數列表必須與獲取Constructor的方法getConstructor方法中的參數列表一致。

         2newInstance():構造出一個實例對象,每調用一次就構造一個對象。

         3、利用Constructor類來創建類實例的好處是可以指定構造函數,而Class類只能利用無參構造函數創建類實例對象。

示例:

[java] view plaincopy
  1. //接上面的示例  
  2. //通過Constructor對象來創建類實例方法  
  3. public static void createPersonClass_2() throws Exception{  
  4.     //獲取Person類的Class對象  
  5.     String className="cn.itheima.Person";  
  6.     Class clazz=Class.forName(className);  
  7.     //Class clazz=Person.class;  
  8.           
  9.     //獲取指定構造函數的類實例  
  10.     Constructor con=clazz.getConstructor(String.class,int.class);  
  11.     Person p=(Person) con.newInstance("lisi",30);  
  12.     System.out.println(p.toString());  
  13. }  

 

四、Field

1Field類代表某個類中一個成員變量

2、方法

       Field getField(String s);//只能獲取公有和父類中公有

        Field getDeclaredField(String s);//獲取該類中任意成員變量,包括私有

        setAccessible(ture);

        //如果是私有字段,要先將該私有字段進行取消權限檢查的能力。也稱暴力訪問。

        set(Object obj, Object value);//將指定對象變量上此Field對象表示的字段設置爲指定的新值。

        Object get(Object obj);//返回指定對象上Field表示的字段的值。

示例:

[java] view plaincopy
  1. //接上示例  
  2. //獲取Person對象的成員變量  
  3. public static void getPersonField() throws Exception{     
  4. //如果想要給該變量賦值,必須先要有對象。  
  5.     Class clazz=Class.forName("cn.itheima.Person");  
  6.     Person p=(Person)clazz.newInstance();  
  7.           
  8.     //獲取所以的成員變量  
  9.     Field[] fs=clazz.getFields();  
  10.     for(Field f:fs){  
  11.         System.out.println(f);  
  12.     }  
  13.           
  14.     //獲取指定的成員變量  
  15.     Field fage=clazz.getField("age");  
  16.     Field fname=clazz.getDeclaredField("name");  
  17.           
  18.     //顯示改變後的值  
  19.     fage.set(p, 20);  
  20.     System.out.println(fage.get(p));  
  21.           
  22.     //暴力訪問私有變量  
  23.     fname.setAccessible(true);  
  24.     fname.set(p, "zhangsan");  
  25.     System.out.println(fname.get(p));  
  26. }  

 

五、Method

1、概述:Method類代表某個類中的一個成員方法。調用某個對象身上的方法,要先得到方法,再針對某個對象調用。

2、專家模式:誰調用這個數據,就是誰在調用它的專家。

如人關門:

        調用者:是門調用關的動作,對象是門,因爲門知道如何執行關的動作,通過門軸之類的細節實現。

        指揮者:是人在指揮門做關的動作,只是給門發出了關的信號,讓門執行。

        總結:變量使用方法,是方法本身知道如何實現執行的過程,也就是“方法對象”調用方法,才執行了方法的每個細節的。

3、方法

        Method[] getMethods();//只獲取公共和父類中的方法。

        Method[] getDeclaredMethods();//獲取本類中包含私有。

        Method   getMethod("方法名",參數.class(如果是空參可以寫null);

        Object invoke(Object obj ,參數);//調用方法

        如果方法是靜態,invoke方法中的對象參數可以爲null

如:

獲取某個類中的某個方法:(如String str =abc”)

        1)通常方式:str.charAt(1)

        2)反射方式:

                                  Method charAtMethod =Class.forName(“java.lang.String”).getMethod(“charAt”,int.class);

                                  charAtMethod.invoke(str,1);

說明:如果傳遞給Method對象的invoke()方法的第一個參數爲null,說明Method對象對應的是一個靜態方法

4、用反射方式執行某個main方法:

        首先要明確爲何要用反射:在寫源程序時,並不知道使用者傳入的類名是什麼,但是雖然傳入的類名不知道,而知道的是這個類中的方法有main這個方法。所以可以通過反射的方式,通過使用者傳入的類名(可定義字符串型變量作爲傳入類名的入口,通過這個變量代表類名),內部通過傳入的類名獲取其main方法,然後執行相應的內容。

此時會出現下面的問題:

        啓動Java程序的main方法的參數是一個字符串數組,即public static void main(String[] args),通過反射方式來調用這個main方法時,如何爲invoke方法傳遞參數呢?按jdk1.5的語法,整個數組是一個參數,而按jdk1.4的語法,數組中的每個元素對應一個參數,當把一個字符串數組作爲參數傳遞給invoke方法時,javac會到底按照哪種語法進行處理呢?jdk1.5肯定要兼容jdk1.4的語法,會按jdk1.4的語法進行處理,即把數組打散成爲若干個單獨的參數。所以,在給main方法傳遞參數時,不能使用代碼mainMethod.invoke(null,new String[]{“xxx”})javac只把它當作jdk1.4的語法進行理解,而不把它當作jdk1.5的語法解釋,因此會出現參數類型不對的問題。

解決辦法:

                   mainMethod.invoke(null,new Object[]{new String[]{"xxx"}});

                   mainMethod.invoke(null,(Object)new String[]{"xxx"});

        這兩種方式編譯器會作特殊處理,編譯時不把參數當作數組看待,也就不會數組打散成若干個參數了。

示例一:

[java] view plaincopy
  1. //接上示例  
  2. //獲取Person類中的方法  
  3. public static void getPersonMethod() throws Exception{  
  4.     //如果想要獲取方法,必須先要有對象。  
  5.     Class clazz=Class.forName("cn.itheima.Person");  
  6.     Person p=(Person)clazz.newInstance();  
  7.           
  8.     //獲取所以方法  
  9.     Method[] mes=clazz.getMethods();//只獲取公共的和父類中的。  
  10.     //mes=clazz.getDeclaredMethods();//獲取本類中包含私有。  
  11.     for(Method me:mes){  
  12.         System.out.println(me);  
  13.     }  
  14.           
  15.     //獲取單個方法  
  16.     Method me=clazz.getMethod("toString"null);  
  17.     Object returnVaule=me.invoke(p, null);  
  18.     System.out.println(returnVaule);      
  19. }  

注:此示例用eclipse運行時,需要在Run As——>RunConfigurations——>Arguments——>Program arguments中添加要執行的類名,如:cn.itheim.Test。

練習:

[java] view plaincopy
  1. package cn.itheima.test;  
  2.   
  3. import java.io.File;  
  4. import java.io.FileInputStream;  
  5. import java.util.Properties;  
  6.   
  7. public class ReflectTest {  
  8.   
  9.     public static void main(String[] args) throws Exception{  
  10.           
  11.         /* 
  12.          *筆記本電腦使用usb設備  
  13.          */  
  14.            
  15.         NoteBook computer=new NoteBook();  
  16.         computer.run();  
  17.           
  18.         //關聯配置文件  
  19.         File file=new File("usb.properties");  
  20.         FileInputStream fis=new FileInputStream(file);  
  21.         //將配置文件信息緩存到集合中  
  22.         Properties ps=new Properties();  
  23.         ps.load(fis);  
  24.         //System.out.println(ps.size());  
  25.         for(int x=1;x<=ps.size();x++){  
  26.             String className=ps.getProperty("usb"+x);//獲取配置文件中類名  
  27.             Class clazz=Class.forName(className);//獲取類的Class對象  
  28.             USB usb=(USB)clazz.newInstance();//得到類實例  
  29.             computer.useUSB(usb);//開始使用  
  30.               
  31.         }  
  32.         fis.close();//關流  
  33.     }  
  34. }  
  35.   
  36. //USB 接口  
  37. public interface USB {  
  38.     void open();  
  39.     void close();  
  40. }  
  41. //筆記本電腦  
  42. public class NoteBook {  
  43.     public void run(){  
  44.         System.out.println("NoteBook is run");  
  45.     }  
  46.       
  47.     public void useUSB(USB usb){  
  48.         if(usb!=null){  
  49.             usb.open();  
  50.             usb.close();  
  51.         }  
  52.     }  
  53. }  
  54. //鼠標  
  55. public class MouseUSB implements USB {  
  56.     @Override  
  57.     public void open() {  
  58.         System.out.println("Mouse is use");  
  59.     }  
  60.     @Override  
  61.     public void close() {  
  62.         System.out.println("Mouse is close");  
  63.     }  
  64. }  
  65. //鍵盤  
  66. public class KeyUSB implements USB {  
  67.     @Override  
  68.     public void open() {  
  69.         System.out.println("Key is use");  
  70.     }  
  71.     @Override  
  72.     public void close() {  
  73.         System.out.println("Key is close");  
  74.     }  
  75. }  

配置文件:

                usb.properties

                usb1=cn.itheima.test.MouseUSB

                usb2=cn.itheima.test.KeyUSB

注:此練習中,配置文件要存在工程中:工程名——>新建File

 

六、數組的反射

1、具有相同維數和元素類型的數組屬於同一個類型,即具有相同的Class實例對象。數組字節碼的名字:有[和數組對應類型的縮寫,如int[]數組的名稱爲:[I

2Object[]String[]沒有父子關係,ObjectString有父子關係,所以new Object[]{aaa,bb}不能強制轉換成new String[]{aaa,bb}; Object x =abc”能強制轉換成String x =abc”。

3、如何得到某個數組中的某個元素的類型,

        例:

              int a = new int[3]Object[] obj=new Object[]{ABC,1};

        無法得到某個數組的具體類型,只能得到其中某個元素的類型,

        如:

               Obj[0].getClass().getName()得到的是java.lang.String。

4Array工具類用於完成對數組的反射操作。

        Array.getLength(Object obj);//獲取數組的長度

        Array.get(Object obj,int x);//獲取數組中的元素

5、基本類型的一維數組可以被當作Object類型使用,不能當作Object[]類型使用;非基本類型的一維數組,既可以當做Object類型使用,又可以當做Object[]類型使用。

示例:

[java] view plaincopy
  1. package cn.itheima.Demo;  
  2.   
  3. import java.lang.reflect.Array;  
  4. import java.util.Arrays;  
  5.   
  6. public class ArrayReflect {  
  7.     public static void main(String[] args) {  
  8.         int [] a1 = new int[]{1,2,3};  
  9.         int [] a2 = new int[4];  
  10.         int[][] a3 = new int[2][3];  
  11.         String [] a4 = new String[]{"a","b","c"};  
  12.         System.out.println(a1.getClass().equals(a2.getClass()));//true  
  13.     System.out.println(a1.getClass().equals(a3.getClass()));//false  
  14.     System.out.println(a1.getClass().equals(a4.getClass()));//false  
  15.     System.out.println(a1.getClass().getName());//[I  
  16.     System.out.println(a4.getClass().getName());//[Ljava.lang.String;  
  17.     System.out.println(a1.getClass().getSuperclass());//class java.lang.Object  
  18.     System.out.println(a4.getClass().getSuperclass());//class java.lang.Object  
  19.           
  20.         Object obj1=a1;  
  21.         Object obj2=a3;  
  22.         Object obj3=a4;  
  23.           
  24. //      Object[] obj11=a1;//這樣是不行的,因爲a1中的元素是int類型,基本數據類型不是Object  
  25.         Object[] obj13=a3;  
  26.         Object[] obj14=a4;//這樣可以,因爲String數組中的元素屬於Object  
  27.           
  28.         System.out.println(a1);//[I@4caaf64e  
  29.         System.out.println(a4);//[Ljava.lang.String;@6c10a234  
  30.         System.out.println(Arrays.asList(a1));//[I@4caaf64e  
  31.         System.out.println(Arrays.asList(a4));//[a, b, c]  
  32.           
  33.         /* Arrays.asList()方法處理int[]和String[]時的差異。 
  34.          * 打印Arrays.asList(a1);還是跟直接打印a1是一樣的 
  35.             打印Arrays.asList(a4);就會把a3的元素打印出來。 
  36.             這是因爲此方法在JDK1.4版本中,接收的Object類型的數組, 
  37.             而a3可以作爲Object數組傳入。但是a1不可以作爲Object數組傳入,所以只能按照JDK1.5版本來處理。 
  38.             在JDK1.5版本中,傳入的是一個可變參數,所以a1就被當作是一個object,也就是一個參數, 
  39.             而不是數組傳入,所以打印的結果還是跟直接打印a1一樣。 
  40.          */  
  41.           
  42.         //Array工具類用於完成對數組的反射操作。如打印任意數值  
  43.         printObject(a1);  
  44.         printObject(a4);  
  45.         printObject("abc");  
  46.           
  47.     }  
  48.     //打印任意數值  
  49.     private static void printObject(Object obj) {  
  50.         Class clazz=obj.getClass();  
  51.         //如果傳入的是數組,則遍歷  
  52.         if(clazz.isArray()){  
  53.             int len =Array.getLength(obj);//Array工具類獲取數組長度方法  
  54.             for(int x=0;x<len;x++){  
  55.                 System.out.println(Array.get(obj, x));//Array工具獲取數組元素  
  56.             }  
  57.         }  
  58.         else  
  59.             System.out.println(obj);  
  60.     }  
  61. }  

 

七、HashCode的分析

        覆寫hashCode()方法的意義:只有存入的是具有hashCode算法的集合的,覆寫hashCode()方法才有價值。

1、哈希算法的由來:

        若在一個集合中查找是否含有某個對象,通常是一個個的去比較,找到後還要進行equals的比較,對象特別多時,效率很低。有這麼一種HashCode算法,有一個集合,把這個集合分成若干個區域,每個存進來的對象,可以算出一個hashCode值,根據算出來的值,就放到相應的區域中去。當要查找某一個對象,只要算出這個對象的hashCode值,看屬於第幾個區域,然後到相應的區域中去尋找,看是否有與此對象相等的對象。這樣查找的性能就提高了。

示意圖:

2、要想HashCode方法有價值的話,前提是對象存入的是hash算法這種類型的集合當中才有價值。如果不存入是hashCode算法的集合中,則不用複寫此方法。

3、如果沒有複寫hashCode方法,對象的hashCode值是按照內存地址進行計算的。這樣即使兩個對象的內容是想等的,但是存入集合中的內存地址值不同,導致hashCode值也不同,被存入的區域也不同。所以兩個內容相等的對象,就可以存入集合中。

        所以就有這樣的說法:如果兩個對象equals相等的話,你應該讓他們的hashCode也相等。如果對象存入的不是根據hash算法的集合中,就不需要複寫hashCode方法。

4、當一個對象存儲進HashSet集合中以後,就不能修改這個對象中的那些參與計算哈希值的字段了,否則對象修改後的哈希值與最初存儲進HashSet集合中的哈希值就不同了。在這種情況下,調用contains方法或者remove方法來尋找或者刪除這個對象的引用,就會找不到這個對象。從而導致無法從HashSet集合中單獨刪除當前對象,從而造成內存泄露。(程序中某一些對象不再被使用,以爲被刪掉了,但是沒有,還一直在佔用內存中,當這樣的對象慢慢增加時,就會造成內存泄露。) 

補充:

        內存泄露:某些對象不再使用了,佔用着內存空間,並未被釋放,就會導致內存泄露;也就是說當程序不斷增加對象,修改對象,刪除對象,日積月累,內存就會用光了,就導致內存溢出。

示例:

[java] view plaincopy
  1. package cn.itheima.Demo;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.Collection;  
  5. import java.util.HashSet;  
  6.   
  7. public class HashCodeDemo {  
  8.   
  9.     public static void main(String[] args) {  
  10.         //Collection collection =new ArrayList();  
  11.         Collection collection =new HashSet();  
  12.         HashCodeTest hct1=new HashCodeTest(1,2);  
  13.         HashCodeTest hct2=new HashCodeTest(3,4);  
  14.         HashCodeTest hct3=new HashCodeTest(1,2);  
  15.           
  16.         collection.add(hct1);  
  17.         collection.add(hct2);  
  18.         collection.add(hct3);  
  19.         collection.add(hct1);  
  20.           
  21.         //hct1.setX(5);  
  22.         //collection.remove(hct1);  
  23.         System.out.println(collection.size());  
  24.     }  
  25.   
  26. }  
  27.   
  28. //測試類  
  29. class HashCodeTest{  
  30.     private int x;  
  31.     public int y;  
  32.       
  33.     public HashCodeTest(int x,int y){  
  34.         this.x=x;  
  35.         this.y=y;  
  36.     }  
  37.       
  38.     public int getX() {  
  39.         return x;  
  40.     }  
  41.   
  42.     public void setX(int x) {  
  43.         this.x = x;  
  44.     }  
  45.   
  46.     public int getY() {  
  47.         return y;  
  48.     }  
  49.   
  50.     public int hashCode() {  
  51.         final int prime = 31;  
  52.         int result = 1;  
  53.         result = prime * result + x;  
  54.         result = prime * result + y;  
  55.         return result;  
  56.     }  
  57.   
  58.     public boolean equals(Object obj) {  
  59.         if (this == obj)  
  60.             return true;  
  61.         if (obj == null)  
  62.             return false;  
  63.         if (getClass() != obj.getClass())  
  64.             return false;  
  65.         HashCodeTest other = (HashCodeTest) obj;  
  66.         if (x != other.x)  
  67.             return false;  
  68.         if (y != other.y)  
  69.             return false;  
  70.         return true;  
  71.     }  
  72.   
  73.     public void setY(int y) {  
  74.         this.y = y;  
  75.     }  
  76.   
  77.     public String toString() {  
  78.         return "HashCodeTest [x=" + x + ", y=" + y + "]";  
  79.     }  
  80. }  

 

第三講     反射的作用——>實現框架的功能

一、概述

1、框架:通過反射調用Java類的一種方式。

        如房地產商造房子用戶住,門窗和空調等等內部都是由用戶自己安裝,房子就是框架,用戶需使用此框架,安好門窗等放入到房地產商提供的框架中。

        框架和工具類的區別:工具類被用戶類調用,而框架是調用用戶提供的類。

2、框架機器要解決的核心問題:

        我們在寫框架(造房子的過程)的時候,調用的類(安裝的門窗等)還未出現,那麼,框架無法知道要被調用的類名,所以在程序中無法直接new其某個類的實例對象,而要用反射來做。

3、簡單框架程序的步驟:

        1)右擊項目File命名一個配置文件如:config.properties,然後寫入配置信息。如鍵值對:className=java.util.ArrayList,等號右邊的配置鍵,右邊是值。

        2)代碼實現,加載此文件:

                ①將文件讀取到讀取流中,要寫出配置文件的絕對路徑。

                    如:InputStream is=new FileInputStream(“配置文件”);

                ②用Properties類的load()方法將流中的數據存入集合。

                ③關閉流:關閉的是讀取流,因爲流中的數據已經加載進內存。

        3)通過getProperty()方法獲取className,即配置的值,也就是某個類名。

        4)用反射的方式,創建對象newInstance()。

        5)執行程序主體功能

 

二、類加載器

1、簡述:類加載器是將.class的文件加載進內存,也可將普通文件中的信息加載進內存。

2、文件的加載問題:

        1)eclipse會將源程序中的所有.java文件編譯成.class文件,然後放到classPath指定的目錄中去。並且會將非.java文件原封不動的複製到.class指定的目錄中去。在運行的時候,執行的是.class文件。

        2)將配置文件放到.class文件目錄中一同打包,類加載器就會一同加載。

3、資源文件的加載:是使用類加載器。

        1)由類加載器ClassLoader來加載進內存,即用getClassLoader()方法獲取類加載器,然後用類加載器的getResourceAsStream(String name)方法,將配置文件(資源文件)加載進內存。利用類加載器來加載配置文件,需把配置文件放置的包名一起寫上。這種方式只有讀取功能。

       2)Class類也提供getResourceAsStream方法來加載資源文件,其實它內部就是調用了ClassLoader的方法。這時,配置文件是相對類文件的當前目錄的,也就是說用這種方法,配置文件前面可以省略包名。

       如:類名.class.getResourceAsStream(“資源文件名”)

4、配置文件的路徑問題:

        1)用絕對路徑,通過getRealPath()方法運算出來具體的目錄,而不是內部編碼出來的。

        一般先得到用戶自定義的總目錄,在加上自己內部的路徑。可以通過getRealPath()方法獲取文件路徑。對配置文件修改是需要要儲存到配置文件中,那麼就要得到它的絕對路徑才行,因此,配置文件要放到程序的內部。

        2)name的路徑問題:

                ①如果配置文件和classPath目錄沒關係,就必須寫上絕對路徑,

                ②如果配置文件和classPath目錄有關係,即在classPath目錄中或在其子目錄中(一般是資源文件夾resource),那麼就得寫相對路徑,因爲它自己瞭解自己屬於哪個包,是相對於當前包而言的。

示例:

[java] view plaincopy
  1. package cn.itheima.demo;  
  2.   
  3. import java.io.InputStream;  
  4. import java.util.Collection;  
  5. import java.util.Properties;  
  6.   
  7. public class OutlineDemo {  
  8.     public static void main(String[] args) throws Exception{  
  9.         //應該先直接用ArrayList和HashSet,然後才引入從配置文件讀,  
  10.         Properties props = new Properties();  
  11.         //先演示相對路徑的問題  
  12.         //InputStream ips = new FileInputStream("config.properties");  
  13.         /*一個類加載器能加載.class文件,那它當然也能加載classpath環境下的其他文件,既然它有如此能力,它沒有理由不順帶提供這樣一個方法。 
  14.          * 它也只能加載classpath環境下的那些文件。注意:直接使用類加載器時,不能以/打頭。*/  
  15.         //InputStream ips = ReflectTest2.class.getClassLoader().getResourceAsStream("cn/itheima/demo/config.properties");  
  16.         //Class提供了一個便利方法,用加載當前類的那個類加載器去加載相同包目錄下的文件  
  17.         //InputStream ips = ReflectTest2.class.getResourceAsStream("config.properties");  
  18.         InputStream ips = OutlineDemo.class.getResourceAsStream("/cn/itheima/demo/config.properties");  
  19.         props.load(ips);  
  20.         ips.close();  
  21.   
  22.         String className = props.getProperty("className");  
  23.         Class clazz = Class.forName(className);  
  24.   
  25.         Collection collection = (Collection)clazz.newInstance();  
  26.         HashCodeTest hct1=new HashCodeTest(1,2);  
  27.         HashCodeTest hct2=new HashCodeTest(3,4);  
  28.         HashCodeTest hct3=new HashCodeTest(1,2);  
  29.           
  30.         collection.add(hct1);  
  31.         collection.add(hct2);  
  32.         collection.add(hct3);  
  33.         collection.add(hct1);  
  34.           
  35.         //hct1.setX(5);  
  36.         //collection.remove(hct1);  
  37.         System.out.println(collection.size());  
  38.         }  
  39. }  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章