Java反射基礎

Introduction

內容源自對慕課網視頻Java反射教程的筆記
慕課網教程-反射——Java高級開發必須懂的

Class類的使用

  1. 類是對象,類是java.lang.Class的對象
  2. 如何表示這個對象
public class ClassDemo1 {
    // Foo的實例對象的表示
    Foo foo = new Foo();
    // 萬事萬物皆對象,那麼Foo這個類怎麼作爲對象表示出來
}

class Foo {}

我們進入Class的源代碼,可以看到一個私有的構造函數(這個構造函數可能因爲JDK的版本不同而不同)

/*
 * Private constructor. Only the Java Virtual Machine creates Class objects.
 * This constructor is not used and prevents the default constructor being
 * generated.
 */
private Class(ClassLoader loader) {
    // Initialize final field for classLoader.  The initialization value of non-null
    // prevents future JIT optimizations from assuming this final field is null.
    classLoader = loader;
}

說得很明確了只有JVM才能訪問。

要想把Foo這個類作爲對象表示出來有三種方式,內容基本都在註釋裏面了

package com.nevercome;

/**
 * @author: sun
 * @date: 2019/4/6
 */
public class ClassDemo1 {

    public static void main(String[] args) {
        // Foo的實例對象的表示
//        Foo foo = new Foo("1");
        Foo foo = new Foo();
        // 萬事萬物皆對象,Foo這個類怎麼作爲對象表示出來
        // 任何一個類都是Class的實例對象,但是我我們無法用構造函數的方式
        // 但其實有三種方式
        // 1. .class
        Class c1 = Foo.class; // 這說明所有的類,都有一個爲class的靜態變量
        // 2. 已知該類的對象 getClass()
        Class c2 = foo.getClass();
        // 來梳理一下概念
        // 根據官網的定義: c1,c2 表示了Foo類的類類型(class type)
        // 類類型指的就是類自己,它是Class類的一個實例,如Foo類的類類型就是c1
        // c1是它作爲Class類的實例(對象),我們稱之爲該類(這裏是Foo)的類類型


        // c1 和 c2都是Foo的類類型,一個類作爲Class類的實例只可能有一個實例對象
        System.out.println(c1 == c2); // true
        Class c3 = null;
        try {
            c3 = Class.forName("com.nevercome.Foo");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        System.out.println(c2 == c3); // true

        // 我們還可以通過類的類類型來創建該類的對象
        // 這要求該類必須有無參的構造函數(隱式和顯示皆可)
        // 不然會拋出NoSuchMethodException
        try {
            Foo foo1 = (Foo)c1.newInstance();
            Foo foo2 = (Foo)c2.newInstance();
            foo.print();
            foo1.print();
            foo2.print();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

    }

}

class Foo {

//    public Foo(String name) {
//
//    }

    public void print() {
        System.out.println("foo");
    }
}

動態加載類 類的反射

上述說的第三種方式Class.forName(“類的權限定名”),不僅代表了類的類類型,還代表了動態加載類。編譯時刻加載類是靜態加載類,運行時刻加載類是動態加載類。

看一下下面這個Office類:

public class Office {
	public static void mian(String[] args) {
		if("Word".equals(args[0])) {
			Word w = new Word();
			w.start();
		}
		if("Excel".equals(args[0])) {
			Excel e = new Excel();
			e.start();
		}
	}
}

使用javac命令編譯這個Office類,毫無疑問,它會報錯。因爲我們沒有Word和Excel類。那麼如果你有了Word類呢,當然還是會報錯,因爲你沒有Excel。仔細想一下這個問題,這是否和你的真實需求相背離了呢?我們希望有Word類,Word的功能就可以使用,而不是因爲Excel缺失了而所有的Office全家桶都無法使用了。這就是靜態加載類(new 都是靜態加載),編譯時刻要求加載所需的全部類的缺陷。我們希望只有在我們需要使用他們的時候,他們才被加載,Class.forName()可以完成這個任務。

public class OfficeBetter {
	public static void main(String[] args) {
		try {

			// 動態加載類 運行時加載
			Class c = Class.forName(args[0]);
			// 通過類類型創建對象
			// 強制類型轉換? Word 還是 Excel
			// 沒錯!需要接口或者繼承
			OfficeAble oa = (OfficeAble) c.newInstance();
			oa.start();

		} catch(Exception e) {
			e.printStackTrace();
		}
	}
}
public interface OfficeAble {
    void start();
}
public class Excel implements OfficeAble {
	public void start() {
		System.out.println("excel start");
	}
}
public class Word implements OfficeAble {
	public void start() {
		System.out.println("word start");
	}
}

這樣子我們就實現了功能上的彼此獨立與動態擴展。

獲取方法信息 方法的反射

基本數據類型、void關鍵字都可以獲取類的類類型

public class ClassDemo2 {
    public static void main(String[] args) {
        Class c1 = int.class; // int數據類型 的類類型
        Class c2 = String.class; // String類的 類類型
        Class c3 = double.class; // double數據類型 的類類型
        Class c4 = Double.class; // Double類 的類類型
        Class c5 = void.class; // void關鍵字的 類類型

        // 只要是可以在類內被定義的,就都可以獲取到它的類類型
//        Class c6 = package.class; // 會報錯

        System.out.println(c1.getName()); // int
        System.out.println(c2.getName());  // java.lang.String
        System.out.println(c2.getSimpleName()); // String
        System.out.println(c3.getName()); // double
        System.out.println(c4.getName()); // java.lang.Double
        System.out.println(c5.getName()); // void
    }
}
public class ClassUtils {

    /**
     * 打印類的信息:包括類的成員函數 成員變量
     * @param obj 該對象所屬類的信息
     */
    public static void printClassInfo(Object obj) {
        // 1. 要獲取類的信息 首先要獲取類的類類型
        // 這裏我們傳入的是一個Object參數 那麼我可以使用obj.getClass()方法

        // Object是所有對象的父類 那麼這裏傳遞的是哪個子類 獲取到的就是那個子類的類類型
        // 其底層實現使用了Native方法來實現...Native方法是Java聲明底層用C語言去實現的 目前我們不關心它的底層原理
        Class c = obj.getClass();
        System.out.println("類的全限定名是:" + c.getName());
        // 2. 要獲取成員函數...而方法是Method類的對象
        /*
         * Method類,方法對象
         * 一個成員方法就是一個Method對象
         * getMethods() 獲取的是所有public的方法,包括從父類繼承而來
         * getDeclaredMethods() 獲取的是該類自己聲明的方法,不包括符類,不限定訪問權限
         */
        Method[] methods = c.getMethods();
//        c.getDeclaredMethods();
        for(Method method : methods) {
            // 1. 獲取方法的返回類型
            Class returnType = method.getReturnType(); // 返回的是類類型
            System.out.print(returnType.getName() + " ");
            // 2. 獲取方法的名稱
            System.out.print(method.getName() + "(");
            // 3. 獲取參數列表(類型)
            Class[] paramTypes = method.getParameterTypes(); // 返回的是參數列表的類類型
            for(int i=0; i<paramTypes.length; i++) {
                if(i == paramTypes.length - 1) {
                    System.out.print(paramTypes[i].getName());
                } else {
                    System.out.print(paramTypes[i].getName() + ", ");
                }
            }
            System.out.println(")");
        }
    }
}

獲取構造函數與成員變量信息 構造函數與成員變量的反射

上面說到了獲取方法信息,接着來看構造函數與成員變量。

/**
 * 打印成員變量的信息
 * @param obj
 */
public static void printFieldInfo(Object obj) {
    /*
     * 成員變量也是對象
     * 是java.lang.reflect.Field的對象
     * getFields() 獲取的是public 包括父類
     * getDeclaredFields() 所有自己聲明的
     */
    Class c = obj.getClass();
    System.out.println("類的成員變量:");
    Field[] fields = c.getFields();
    Field[] fields1 = c.getDeclaredFields();
    for(Field  field : fields1) {
        Class fieldType = field.getType(); // 成員變量類型的類類型
        String typeName = fieldType.getName();
        String filedName = field.getName(); // 成員變量的名字 如int a 即爲a
        System.out.println(fieldType + " " + filedName);
    }
}

和方法的獲取基本是一致的。

同樣的,構造函數也是基本一致的

/**
 * 打印構造函數的信息
 * @param obj
 */
public static void printContructInfo(Object obj) {
    Class c = obj.getClass();
    /*
     * 構造函數也是對象
     * java.lang.Constructor 封裝了構造函數的信息
     * getConstructors() public
     * getDeclaredConstructors() 自己聲明的
     */
//   Constructor[] constructors = c.getConstructors();
    Constructor[] constructors = c.getDeclaredConstructors();
    System.out.println("類的全限定名是:" + c.getName());
    System.out.println("類的自己聲明的構造函數:");
    for(Constructor constructor : constructors) {
        // 獲取構造函數名
        System.out.print(constructor.getName() + "(");
        // 參數列表
        Class[] paramTypes = constructor.getParameterTypes();
        for (int i = 0; i < paramTypes.length; i++) {
            if (i == paramTypes.length - 1) {
                System.out.print(paramTypes[i].getName());
            } else {
                System.out.print(paramTypes[i].getName() + ", ");
            }
        }
        System.out.println(")");
    }
}

當然Class還有很多其它的API…諸如獲取到父類…包名等待,要記住的一點是要使用Class的接口就必須要先獲取到類類型。

方法的反射

  1. 獲取某一個方法 getMethod(名稱, 參數列表)
  2. 方法反射的操作 method.invoke(對象, 參數列表)
public class MethodDemo1 {

    public static void main(String[] args) {
        // 要獲取到方法 首先要得到類的類類型
        A a = new A();
        Class c  = a.getClass();

        // 獲取什麼樣的方法
        // print(int,int)
        // getMethod 共有方法
        // getDeclaredMethod 聲明的方法
        try {
            // ...參數類型
//            Method method = c.getMethod("print", new Class[]{int.class, int.class});
            Method m = c.getMethod("print", int.class, int.class);
            a.print(1, 2);
            try {
                // 有返回值則獲取返回值 沒有則爲null
                Object o = m.invoke(a, 1, 2);
                System.out.println(o);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }

        try {
             Method m = c.getMethod("print", String.class, String.class);
             m.invoke(a, "hello", "java");
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

        try {
            Method m = c.getMethod("print");
            m.invoke(a);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

    }


}

class A {

    public void print() {
        System.out.println("Hello World");
    }

    public void print(int a, int b) {
        System.out.println(a+b);
    }

    public void print(String a, String b) {
        System.out.println(a.toUpperCase() + " " + b.toUpperCase());
    }

}

通過反射來認識集合泛型的本質

集合泛型是設計來規定輸入的

public class ClassMethod4 {
    public static void main(String[] args) {
        ArrayList list = new ArrayList();

        ArrayList<String> list1 = new ArrayList<>();

        list1.add("hello");
//        list1.add(1); // 當然會報錯 集合泛型規定了只能放入String
        // 但是
        Class c = list.getClass();
        Class c1 = list1.getClass();
        System.out.println(c == c1); // true

        /*
         * 反射的操作都是編譯之後的操作
         * c1 == c2 說明編譯之後集合的泛型是去泛型化的
         * Java中集合的泛型是防止錯誤輸入的 繞過編譯 泛型就不起作用了
         * 我們可以用方法的泛型來繞過編譯
        */

        try {
            Method m = c.getMethod("add", Object.class);
            m.invoke(list1, 100);
            System.out.println(list1.size()); // 2
            System.out.println(list1); // [hello, 100]
            // 那麼這樣子在獲取數據的時候就造成了困難
            // 我們很難用foreach和for來遍歷集合
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

    }
}

ReflectonUtils

來看一個Reflection的工具類,裏面有一些最基本的用法

代碼出自JeeSite

獲取對象的DeclaredMethod

/**
 * 循環向上轉型, 獲取對象的DeclaredMethod,並強制設置爲可訪問.
 * 如向上轉型到Object仍無法找到, 返回null.
 * 只匹配函數名。
 *
 * 用於方法需要被多次調用的情況. 先使用本函數先取得Method,然後調用Method.invoke(Object obj, Object... args)
 */
public static Method getAccessibleMethodByName(final Object obj, final String methodName) {
    Validate.notNull(obj, "object can't be null");
    Validate.notBlank(methodName, "methodName can't be blank");
    for (Class<?> searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) {
        Method[] methods = searchType.getDeclaredMethods();
        for (Method method : methods) {
            if (method.getName().equals(methodName)) {
                makeAccessible(method);
                return method;
            }
        }
    }
    return null;
}

/**
 * 循環向上轉型, 獲取對象的DeclaredMethod,並強制設置爲可訪問.
 * 如向上轉型到Object仍無法找到, 返回null.
 * 只匹配函數名。
 *
 * 用於方法需要被多次調用的情況. 先使用本函數先取得Method,然後調用Method.invoke(Object obj, Object... args)
 */
public static Method getAccessibleMethodByName(final Object obj, final String methodName) {
    Validate.notNull(obj, "object can't be null");
    Validate.notBlank(methodName, "methodName can't be blank");

    for (Class<?> searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) {
            Method[] methods = searchType.getDeclaredMethods();
            for (Method method : methods) {
                if (method.getName().equals(methodName)) {
                    makeAccessible(method);
                    return method;
            }
        }
    }
    return null;
}

其中的Validate出自org.apache.commons.lang3,方法的意思很好理解,getMethods和getDeclaredMethod一個只能獲取共有方法,一個只能是自己聲明的。那麼我們通過getSuperclass獲取父類,繼續調用父類的getDeclaredMethod來找到方法,並最後返回method。

其中的makeAccessible

/**
 * 改變private/protected的方法爲public,儘量不調用實際改動的語句,避免JDK的SecurityManager抱怨。
 */
public static void makeAccessible(Method method) {
    if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers()))
            && !method.isAccessible()) {
            method.setAccessible(true);
    }
}

對應的調用方式

/**
 * 直接調用對象方法, 無視private/protected修飾符.
 * 用於一次性調用的情況,否則應使用getAccessibleMethod()函數獲得Method後反覆調用.
 * 同時匹配方法名+參數類型,
 */
public static Object invokeMethod(final Object obj, final String methodName, final Class<?>[] parameterTypes, final Object[] args) {
    Method method = getAccessibleMethod(obj, methodName, parameterTypes);
    if (method == null) {
        throw new IllegalArgumentException("Could not find method [" + methodName + "] on target [" + obj + "]");
    }
    try {
        return method.invoke(obj, args);
    } catch (Exception e) {
        throw convertReflectionExceptionToUnchecked(e);
    }
}

/**
 * 直接調用對象方法, 無視private/protected修飾符,
 * 用於一次性調用的情況,否則應使用getAccessibleMethodByName()函數獲得Method後反覆調用. 
 * 只匹配函數名,如果有多個同名函數調用第一個。
 */
public static Object invokeMethodByName(final Object obj, final String methodName, final Object[] args){
    Method method = getAccessibleMethodByName(obj, methodName);
    if (method == null) {
        throw new IllegalArgumentException("Could not find method [" + methodName + "] on target [" + obj + "]");
    }

    try {
        return method.invoke(obj, args);
    } catch (Exception e) {
        throw convertReflectionExceptionToUnchecked(e);
    }
}

獲取對象的DeclaredFeild

/**
 * 循環向上轉型, 獲取對象的DeclaredField, 並強制設置爲可訪問.
 *
 * 如向上轉型到Object仍無法找到, 返回null.
 */
public static Field getAccessibleField(final Object obj, final String fieldName) {
    Validate.notNull(obj, "object can't be null");
    Validate.notBlank(fieldName, "fieldName can't be blank");
    for (Class<?> superClass = obj.getClass(); superClass != Object.class; superClass=superClass.getSuperclass()) {
        try {
            Field field = superClass.getDeclaredField(fieldName);
            makeAccessible(field);
            return field;
        } catch (NoSuchFieldException e) {
            // Field不在當前類定義,繼續向上轉型
            continue;
        }
    }
    return null;
}

/**
 * 改變private/protected的成員變量爲public,儘量不調用實際改動的語句,避免JDK的SecurityManager抱怨。
 */
public static void makeAccessible(Field field) {
    if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers()) || Modifier
                .isFinal(field.getModifiers())) && !field.isAccessible()) {
        field.setAccessible(true);
    }
}

對應的讀取對象屬性值的方法

/**
 * 直接讀取對象屬性值, 無視private/protected修飾符, 不經過getter函數.
 */
public static Object getFieldValue(final Object obj, final String fieldName) {
    Field field = getAccessibleField(obj, fieldName);

    if (field == null) {
        throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + obj + "]");
    }

    Object result = null;
    try {
        result = field.get(obj);
    } catch (IllegalAccessException e) {
        logger.error("不可能拋出的異常{}", e.getMessage());
    }
    return result;
}

/**
 * 直接設置對象屬性值, 無視private/protected修飾符, 不經過setter函數.
 */
public static void setFieldValue(final Object obj, final String fieldName, final Object value) {
    Field field = getAccessibleField(obj, fieldName);

    if (field == null) {
        throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + obj + "]");
    }
    try {
        field.set(obj, value);
    } catch (IllegalAccessException e) {
        logger.error("不可能拋出的異常:{}", e.getMessage());
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章