淺析JAVA反射

hellohellolady_86404發表於2019-03-03

廢話不多說,直切進入正題。先來一張反射機制的圖-->

淺析JAVA反射

認識反射

  • 首先反射我們通過“反”來理解,既然有“反”就一定有“正”,在正常情況下,我們認為一定是先有類再有物件。如下:

    import java.util.Date;//先有類

    public class ReflectTest1 {
	public static void main(String[] args) {
		Date date = new Date();//後有物件
		System.out.println(date);
	}
    }複製程式碼
  •  所謂的“反”就是指利用物件找到此物件的出處。在Object類裡提供有一個方法:
       取得Class物件:public final Class<?> getClass()。
       範例:觀察“反”

    import java.util.Date;

    public class ReflectTest2 {
	public static void main(String[] args) {
		Date date = new Date();
		System.out.println(date.getClass());
	}
    }
    "class java.util.Date"
複製程式碼

         發現呼叫了getClas()方法後的輸出,就輸出了類的完整名稱,等於找到了物件的出處。

  • Class類物件例項化

       java.lang.Class是一個類,這個類是反射操作的源頭,即:所有的反射都要從類開始進行,而最關鍵的是這個類有三種例項化方法:

  1.呼叫Object類中的getClass()方法:

    import java.util.Date;

    public class ReflectTest3 {
	public static void main(String[] args) {
		Date date = new Date();
		Class<?> cls =  date.getClass();
		System.out.println(cls);
	}
    }
    "class java.util.Date"
複製程式碼

   2. 使用“類.class”取得:

    import java.util.Date;

    public class ReflectTest4 {
	public static void main(String[] args) {
		Class<?> cls =  Date.class;
		System.out.println(cls);
	}
    }
    "class java.util.Date"
複製程式碼

         之前是在例項化了Date物件之後取得的Class類物件,但是現在並沒有例項化物件的產生。這種方法用在Hibernate、Mybatis、Spring等。

  3. 呼叫Class類提供的一個方法--例項化Class物件(在java.lang.Object下public  static 

 Class<?> forName(String className) throws ClassNotFoundException):

    public class ReflectTest5 {
	public static void main(String[] args) throws ClassNotFoundException {
		Class<?> cls =  Class.forName("java.util.Date");
		System.out.println(cls);
	}
    }
    "class java.util.Date"
複製程式碼

          此時可以不使用import語句匯入一個明確的類,而類的名稱是採用字串的形式進行描述的。

反射例項化物件

         當拿到一個類的時候,肯定要直接使用關鍵字new 進行物件例項化操作這屬於我們習慣性的做法。如果拿到Class類物件,那麼就可以做到利用反射來例項化物件操作:

public  T newInstance() throws InstantiationException,IllegalAccessException 

範例: 利用反射來例項化物件

    /*正常例項化*/
    class Book{
	public Book(){
		System.out.println("*** Book的構造方法 ***");
	}
	@Override
	public String toString() {
		return "--- 這是Book方法 ---";
	}
    }
    public class ReflectTest6 {
	public static void main(String[] args) throws ClassNotFoundException {
		Book b = new Book();
		System.out.println(b);
	}
    }
    "*** Book的構造方法 ***
     --- 這是Book方法 ---"複製程式碼
    package com.jkx.lzh.test;    
    /*反射例項化*/
    class Book{
	public Book(){
	    System.out.println("*** Book的構造方法 ***");
	}
	@Override
	public String toString() {
	    return "--- 這是Book方法 ---";
	}
    }
    public class ReflectTest7 {
	public static void main(String[] args) throws InstantiationException, 
            IllegalAccessException, ClassNotFoundException{
		Class<?> cls = Class.forName("com.jkx.lzh.test.Book");
		Object obj = cls.newInstance();
	}
    }
    "*** Book的構造方法 ***"複製程式碼

     class Book{
	public Book(){
	    System.out.println("*** Book的構造方法 ***");
	}
	@Override
	public String toString() {
	    return "--- 這是Book方法 ---";
	}
    }
    public class ReflectTest8 {
	public static void main(String[] args) throws InstantiationException, 
            IllegalAccessException, ClassNotFoundException{
	    Class<?> cls = Class.forName("com.jkx.lzh.test.Book");
	    Object obj = cls.newInstance();
	    Book b = (Book) obj;
	    System.out.println(b);
	}
    }
"*** Book的構造方法 ***
     --- 這是Book方法 ---"複製程式碼

        有了反射之後,以後進行物件化例項化的操作不在只是單獨的依靠關鍵字new完成了,反射也同樣可以,但是這並不表示new就被完全取代了。

        這樣有人就會說:“下面兩行程式碼相當於:Book b = new  Book();這樣不是更復雜了,麼“。

    Class<?> cls = Class.forName("com.jkx.lzh.test.Book");     
    Object obj = cls.newInstance();
複製程式碼

         PS: 在任何的開發之中,new是造成耦合的最大元凶。一切的耦合都起源於new。

         範例:觀察工廠模式-->

    interface Fruit{
	void eat();
    }
    class Apple implements Fruit{
	@Override
	public void eat() {
	    System.out.println("* 吃蘋果 *");
	}
    }
    class Factory{
	public static Fruit getInstance(String className){
	    if("apple" == className){
		return new Apple();
	    }
	    return null;
	}
    }
    public class TestFactory {
	public static void main(String[] args) {
	    Fruit f = Factory.getInstance("apple");
	    f.eat();
	}
    }
    "* 吃蘋果 *"
複製程式碼

         程式碼如上,但是此時,如果我們要增加一個Fruit介面子類“orange”,就意味著我們就要修改工廠模式的方法。

    class Orange implements Fruit{
	@Override
	public void eat() {
		System.out.println("* 吃橘子 *");
	}
    }
    class Factory{
	public static Fruit getInstance(String className){
	    if("apple" == className){
		return new Apple();
	    }else if("orange" == className){
		return new Orange();	
            }
	    return null;
	}
    }複製程式碼

          由此可見,每增加一個Fruit介面子類,就要修改工廠類,那麼如果隨時需要增加子類呢?

          因為現在工廠類都是new關鍵字直接例項化的,所以new就造成了所有問題的關鍵點。要想解決這一問題,就只能依靠反射完成。

修改工廠模式的方法如下:

    package com.jkx.lzh.test;

    interface Fruit{
	void eat();
    }
    class Apple implements Fruit{
	@Override
	public void eat() {
	    System.out.println("* 吃蘋果 *");
	}
    }
    class Orange implements Fruit{
	@Override
	public void eat() {
	    System.out.println("* 吃橘子 *");
	}
    }
    class Factory{
	public static Fruit getInstance(String className){
	    Fruit f = null;
	    try {
	        f = (Fruit) Class.forName(className).newInstance();
	    } catch (Exception e) {
		e.printStackTrace();
	    }
	    return f;
	}
    }
    public class TestFactory {
	public static void main(String[] args) {
	    Fruit f = Factory.getInstance("com.jkx.lzh.test.Apple");
	    f.eat();
	}
    }複製程式碼

        此時的程式就真正的完成了解耦合的目的,而且可擴充套件性非常強。

使用反射呼叫構造

        在之前所編寫的程式碼中,我們都預設呼叫了類中的無參構造方法。可是類中也有可能不提供無參構造方法。

範例:觀察當前程式的問題

Book.java

    package com.jkx.lzh.po;
    
    public class Book {
	private String title;
	private double price;
	public Book(String title,double price){
	    this.title = title;
	    this.price = price;
	}
	public String toString(String title,double price) {
	    return "書名:"+ this.title + "價格: " + this.price ;
	}
    }複製程式碼

ReflectTest9.java

    package com.jkx.lzh.test;


    public class ReflectTest9 {
	public static void main(String[] args) throws InstantiationException, 
            IllegalAccessException, ClassNotFoundException{
		Class<?> cls = Class.forName("com.jkx.lzh.po.Book");
		Object obj = cls.newInstance();
		System.out.println(obj);
	}
    }複製程式碼

以上程式碼由於Book沒有無參構造方法(<init>()),所以丟擲以下異常:

Exception in thread "main" java.lang.InstantiationException: com.jkx.lzh.po.Book
	at java.lang.Class.newInstance(Class.java:427)
	at com.jkx.lzh.test.ReflectTest1.main(ReflectTest1.java:7)
Caused by: java.lang.NoSuchMethodException: com.jkx.lzh.po.Book.<init>()
	at java.lang.Class.getConstructor0(Class.java:3082)
	at java.lang.Class.newInstance(Class.java:412)
	... 1 more複製程式碼

那麼怎麼解決這個問題呢?

在Class類中提供了這麼一個構造方法可以取得構造:

  1. 取得全部構造方法:public Constructor<?>[] getConstructors() throws SecurityException; 
  2. 取得一個指定引數順序的構造:public Constructor<T>(Class<?> ... ParameterTypes) throws NoSuchMethodException,SecurityException;

以上兩個方法返回都是“java.lang.reflect.Constructor”類的物件。在這個類中我們提供一個明確傳遞有參構造內容例項化物件方法:

public T newInstance(Object... initargs) throws                             InstantiationException,IllegalAccessException,IllegalAccessException,IllegalArgumentException,InvocationTargetException;

範例:明確呼叫類中的構造方法

ReflectTest9.java修改如下:

    import java.lang.reflect.Constructor;
    import java.lang.reflect.InvocationTargetException;

    public class ReflectTest9 {
	public static void main(String[] args) throws InstantiationException, 
            IllegalAccessException, ClassNotFoundException, NoSuchMethodException, 
            SecurityException, IllegalArgumentException, InvocationTargetException{
	        
                Class<?> cls = Class.forName("com.jkx.lzh.po.Book");
	        //public Book(String title,double price);Constructor傳的是有參構造的引數型別
	        Constructor<?> con = cls.getConstructor(String.class,double.class);
	        Object obj = con.newInstance("JAVA 反射,",82.22);
	        System.out.println(obj);
	}
    }
    "書名:JAVA 反射價格: 82.22"
複製程式碼

         以上給我一個警示:在我們的簡單java類的開發中不管提供多少構造方法,我們至少要保留無參構造方法。

反射呼叫方法

        類中普通方法只有在這個類例項化物件之後才能被呼叫。並且例項化方法有三種(new,conle,反射)

範例:定義一個類

    public class Book {
	private String title;
	public String getTitle() {
		return title;
	}
	public void setTitle(String title) {
		this.title = title;
	}
    }複製程式碼

這個類有無參構造方法,所以例項化物件的時候可以直接利用Class提供的newInstance方法

在Class類裡面提供有以下取得類中Method()的操作:

  1. 取得一個類中的全部方法:
    public Method[] getMethods() throws  SecurityException;

  2. 取得類中指定方法:
    public Method getMethod(String name,Class<?> ... ParameterTypes) throws NoSuchMethodException,SecurityException;

以上兩個操作返回的是“java.lang.reflect.Method”類物件,在這裡類裡面我們重點關注一個方法:呼叫方法-->

public Object invoke(Object obj,Object args) throws IllegalAccessException,IllegalArgumentException,InvocationTargetException;

範例:反射呼叫方法

    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;

    public class ReflectTest10 {
	public static void main(String[] args) throws InstantiationException, 
            IllegalAccessException, ClassNotFoundException, NoSuchMethodException, 
            SecurityException, IllegalArgumentException, InvocationTargetException{
		String fileName = "title";//要操作的成員
		Class<?> cls = Class.forName("com.jkx.lzh.po.Book");
		Object obj = cls.newInstance();//必須例項化物件
		Method setMet = cls.getMethod("set"+ inticap(fileName) , String.class);
		Method getMet = cls.getMethod("get"+ inticap(fileName));
		setMet.invoke(obj, "JAVA 反射");
		System.out.println(getMet.invoke(obj));
	}
	public static String inticap(String str){
		return str.substring(0,1).toUpperCase() + str.substring(1);
	}
    }
"JAVA 反射"複製程式碼

此時我們完全看不到操作型別,也就是說利用反射可以實現任意類的指定方法呼叫。

反射呼叫成員

  • 類中屬性一定要在本類例項化物件產生之後才可以分配記憶體空間。在Class類中提供了取得成員的方法:

    1. 取得全部成員:public Filed[] getDeclaredFileds() throws SecurityException;

    2. 取得指定成員:public Filed getDeclaredFiled(String name) throws NoSuchMethodException,SecurityException;

  • 返回的型別是“java.lang.reflect.Filed”類,在這個類裡面有兩個重要的方法:

     1. 取得屬性的內容:public Object getObject(Object obj) throws IllegalAccessException,IllegalArgumentException

     2. 設定屬性的內容:public void Object getObject(Object obj,Object values) throws IllegalAccessException,IllegalArgumentException

  • 在“java.lang.reflect.AccessibleObject”類下面(在JDK1.8之後修改):

      1. Executable下面繼承了Constructor,Method;

      2. Filed: 在這個類中提供有一個方法:public void setAccessible(boolean flag) throws SecurityException;設定是否取消封裝

範例:現在提供如下類 

    package com.jkx.lzh.po;

    public class Book {
        private String title;
    }複製程式碼

這個類中定義了一個私有屬性,按照我們原始的做法,此時它一定無法被外部所使用。

範例: 但是使用setAccessible取消封裝就可以使用了

import java.lang.reflect.Field;

public class ReflectTest11 {
	public static void main(String[] args) throws NoSuchFieldException, 
            SecurityException, ClassNotFoundException, 
            InstantiationException, IllegalAccessException {
		Class<?> cls = Class.forName("com.jkx.lzh.po.Book");
		Object obj = cls.newInstance();//必須例項化物件
		Field titleField = cls.getDeclaredField("title");
		titleField.setAccessible(true);
		titleField.set(obj, "JAVA 反射");//相當於:Book物件.title = "JAVA 反射";
		System.out.println(titleField.get(obj));//相當於:Book物件.title;
	}
} 
"JAVA 反射"複製程式碼

構造方法和普通方法同樣可以取消,只不過我們很少這樣去做,而且對於屬性的訪問還是建議使用settr、gettr


相關文章