天天看點

Java 反射 Java 反射

Java 反射

标簽 : Java基礎

動态語言

動态語言,是指程式在運作時可以改變其結構:新的函數可以被引進,已有的函數可以被删除等在結構上的變化。比如衆所周知的ECMAScript(JavaScript)便是一個動态語言。除此之外如Ruby、Python等也都屬于動态語言,而C、C++等語言則不屬于動态語言。(引自: 百度百科)
var execString = "alert(Math.floor(Math.random()*10));";
eval(execString);
           

Class

反射機制

  • 指的是可以于運作時加載,探知和使用編譯期間完全未知的類.
  • 程式在運作狀态中, 可以動态加載一個隻有名稱的類, 對于任意一個已經加載的類,都能夠知道這個類的所有屬性和方法; 對于任意一個對象,都能調用他的任意一個方法和屬性;
  • 加載完類之後, 在堆記憶體中會産生一個

    Class

    類型的對象(一個類隻有一個Class對象), 這個對象包含了完整的類的結構資訊,而且這個

    Class

    對象就像一面鏡子,透過這個鏡子看到類的結構,是以被稱之為:

    反射

    .
Instances of the class

Class

represent classes and interfaces in a running Java application. An enum is a kind of class and an annotation is a kind of interface. Every array also belongs to a class that is reflected as a

Class

object that is shared by all arrays with the same element type and number of dimensions(次元). The primitive Java types (

boolean

,

byte

,

char

,

short

,

int

,

long

,

float

, and

double

), and the keyword

void

are also represented as

Class

objects.
  • 每個類被加載進入記憶體之後,系統就會為該類生成一個對應的

    java.lang.Class

    對象,通過該

    Class

    對象就可以通路到JVM中的這個類.

Class對象的擷取

  • 對象的

    getClass()

    方法;
  • 類的

    .class

    (最安全/性能最好)屬性;
  • 運用

    Class.forName(String className)

    動态加載類,

    className

    需要是類的全限定名(最常用).

從Class中擷取資訊

Class

類提供了大量的執行個體方法來擷取該Class對象所對應的詳細資訊,Class類大緻包含如下方法,其中每個方法都包含多個重載版本,是以我們隻是做簡單的介紹,詳細請參考JDK文檔

  • 擷取類内資訊
擷取内容 方法簽名
構造器

Constructor<T> getConstructor(Class<?>... parameterTypes)

包含的方法

Method getMethod(String name, Class<?>... parameterTypes)

包含的屬性

Field getField(String name)

包含的

Annotation

<A extends Annotation> A getAnnotation(Class<A> annotationClass)

内部類

Class<?>[] getDeclaredClasses()

外部類

Class<?> getDeclaringClass()

所實作的接口

Class<?>[] getInterfaces()

修飾符

int getModifiers()

所在包

Package getPackage()

類名

String getName()

簡稱

String getSimpleName()

  • 一些判斷類本身資訊的方法
判斷内容 方法簽名
注解類型?

boolean isAnnotation()

使用了該

Annotation

修飾?

boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)

匿名類?

boolean isAnonymousClass()

數組?

boolean isArray()

枚舉?

boolean isEnum()

原始類型?

boolean isPrimitive()

接口?

boolean isInterface()

obj

是否是該

Class

的執行個體

boolean isInstance(Object obj)

  • 使用反射生成并操作對象:

    Method

    Constructor

    Field

    這些類都實作了

    java.lang.reflect.Member

    接口,程式可以通過

    Method

    對象來執行相應的方法,通過

    Constructor

    對象來調用對應的構造器建立執行個體,通過

    Filed

    對象直接通路和修改對象的成員變量值.

建立對象

通過反射來生成對象的方式有兩種:

  • 使用

    Class

    對象的

    newInstance()

    方法來建立該

    Class

    對象對應類的執行個體(這種方式要求該Class對象的對應類有預設構造器).
  • 先使用

    Class

    對象擷取指定的

    Constructor

    對象, 再調用Constructor對象的

    newInstance()

    方法來建立該Class對象對應類的執行個體(通過這種方式可以選擇指定的構造器來建立執行個體).

通過第一種方式來建立對象比較常見, 像Spring這種架構都需要根據配置檔案(如

applicationContext.xml

)資訊來建立Java對象,從配置檔案中讀取的隻是某個類的全限定名字元串,程式需要根據該字元串來建立對應的執行個體,就必須使用預設的構造器來反射對象.

下面我們就模拟Spring實作一個簡單的對象池, 該對象池會根據檔案讀取key-value對, 然後建立這些對象, 并放入

Map

中.

  • 配置檔案
{
  "objects": [
    {
      "id": "id1",
      "class": "com.fq.domain.User"
    },
    {
      "id": "id2",
      "class": "com.fq.domain.Bean"
    }
  ]
}
           
  • ObjectPool
/**
 * Created by jifang on 15/12/31.
 */
public class ObjectPool {

    private Map<String, Object> pool;

    private ObjectPool(Map<String, Object> pool) {
        this.pool = pool;
    }

    private static Object getInstance(String className) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        return Class.forName(className).newInstance();
    }

    private static JSONArray getObjects(String config) throws IOException {
        Reader reader = new InputStreamReader(ClassLoader.getSystemResourceAsStream(config));
        return JSONObject.parseObject(CharStreams.toString(reader)).getJSONArray("objects");
    }

    // 根據指定的JSON配置檔案來初始化對象池
    public static ObjectPool init(String config) {
        try {
            JSONArray objects = getObjects(config);
            ObjectPool pool = new ObjectPool(new HashMap<String, Object>());
            if (objects != null && objects.size() != ) {
                for (int i = ; i < objects.size(); ++i) {
                    JSONObject object = objects.getJSONObject(i);
                    if (object == null || object.size() == ) {
                        continue;
                    }
                    String id = object.getString("id");
                    String className = object.getString("class");

                    pool.putObject(id, getInstance(className));
                }
            }
            return pool;
        } catch (IOException | ClassNotFoundException | InstantiationException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    public Object getObject(String id) {
        return pool.get(id);
    }

    public void putObject(String id, Object object) {
        pool.put(id, object);
    }

    public void clear() {
        pool.clear();
    }
}
           
  • Client
public class Client {

    @Test
    public void client() {
        ObjectPool pool = ObjectPool.init("config.json");
        User user = (User) pool.getObject("id1");
        System.out.println(user);

        Bean bean = (Bean) pool.getObject("id2");
        System.out.println(bean);
    }
}
           
  • User
public class User {

    private int id;
    private String name;
    private String password;

    public int getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}
           
  • Bean
public class Bean {
    private Boolean usefull;
    private BigDecimal rate;
    private String name;

    public Boolean getUsefull() {
        return usefull;
    }

    public void setUsefull(Boolean usefull) {
        this.usefull = usefull;
    }

    public BigDecimal getRate() {
        return rate;
    }

    public void setRate(BigDecimal rate) {
        this.rate = rate;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Bean{" +
                "usefull=" + usefull +
                ", rate=" + rate +
                ", name='" + name + '\'' +
                '}';
    }
}
           

注意: 需要在pom.xml中添加如下依賴:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.7</version>
</dependency>

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>18.0</version>
</dependency>
           

調用方法

當擷取到某個類對應的Class對象之後, 就可以通過該Class對象的

getMethod

來擷取一個Method數組或Method對象.每個Method對象對應一個方法,在獲得Method對象之後,就可以通過調用invoke方法來調用該Method對象對應的方法.

@CallerSensitive
public Object invoke(Object obj, Object... args)
    throws IllegalAccessException, IllegalArgumentException,
       InvocationTargetException
{
    ...
}
           

下面我們對上面的對象池加強:可以看到

Client

擷取到的對象的成員變量全都是預設值,既然我們已經使用了JSON這麼優秀的工具,我們又學習了動态調用對象的方法,那麼我們就通過配置檔案來給對象設定值(在對象建立時), 新的配置檔案形式如下:

{
  "objects": [
    {
      "id": "id1",
      "class": "com.fq.domain.User",
      "fields": [
        {
          "name": "id",
          "value": 
        },
        {
          "name": "name",
          "value": "feiqing"
        },
        {
          "name": "password",
          "value": "ICy5YqxZB1uWSwcVLSNLcA=="
        }
      ]
    },
    {
      "id": "id2",
      "class": "com.fq.domain.Bean",
      "fields": [
        {
          "name": "usefull",
          "value": true
        },
        {
          "name": "rate",
          "value": 
        },
        {
          "name": "name",
          "value": "bean-name"
        }
      ]
    },
    {
      "id": "id3",
      "class": "com.fq.domain.ComplexBean",
      "fields": [
        {
          "name": "name",
          "value": "complex-bean-name"
        },
        {
          "name": "refBean",
          "ref": "id2"
        }
      ]
    }
  ]
}
           

其中

fields

代表該

Bean

所包含的屬性,

name

為屬性名稱,

value

為屬性值(屬性類型為JSON支援的類型),

ref

代表引用一個對象(也就是屬性類型為

Object

,但是一定要引用一個已經存在了的對象)

/**
 * @author jifang
 * @since 15/12/31下午4:00
 */
public class ObjectPool {

    private Map<String, Object> pool;

    private ObjectPool(Map<String, Object> pool) {
        this.pool = pool;
    }

    private static JSONArray getObjects(String config) throws IOException {
        Reader reader = new InputStreamReader(ClassLoader.getSystemResourceAsStream(config));
        return JSONObject.parseObject(CharStreams.toString(reader)).getJSONArray("objects");
    }

    private static Object getInstance(String className, JSONArray fields)
            throws ClassNotFoundException, NoSuchMethodException,
            IllegalAccessException, InstantiationException, InvocationTargetException {

        // 配置的Class
        Class<?> clazz = Class.forName(className);
        // 目标Class的執行個體對象
        Object targetObject = clazz.newInstance();
        if (fields != null && fields.size() != ) {
            for (int i = ; i < fields.size(); ++i) {
                JSONObject field = fields.getJSONObject(i);
                // 需要設定的成員變量名
                String fieldName = field.getString("name");

                // 需要設定的成員變量的值
                Object fieldValue;
                if (field.containsKey("value")) {
                    fieldValue = field.get("value");
                } else if (field.containsKey("ref")) {
                    String refBeanId = field.getString("ref");
                    fieldValue = OBJECTPOOL.getObject(refBeanId);
                } else {
                    throw new RuntimeException("neither value nor ref");
                }

                String setterName = "set" +
                        fieldName.substring(, ).toUpperCase() +
                        fieldName.substring();
                // 需要設定的成員變量的setter方法
                Method setterMethod = clazz.getMethod(setterName, fieldValue.getClass());
                // 調用setter方法将值設定進去
                setterMethod.invoke(targetObject, fieldValue);
            }
        }

        return targetObject;
    }

    private static ObjectPool OBJECTPOOL;

    // 建立一個對象池的執行個體(保證是多線程安全的)
    private static void initSingletonPool() {
        if (OBJECTPOOL == null) {
            synchronized (ObjectPool.class) {
                if (OBJECTPOOL == null) {
                    OBJECTPOOL = new ObjectPool(new ConcurrentHashMap<String, Object>());
                }
            }
        }
    }

    // 根據指定的JSON配置檔案來初始化對象池
    public static ObjectPool init(String config) {
        // 初始化pool
        initSingletonPool();

        try {
            JSONArray objects = getObjects(config);
            for (int i = ; objects != null && i < objects.size(); ++i) {
                JSONObject object = objects.getJSONObject(i);
                if (object == null || object.size() == ) {
                    continue;
                }
                String id = object.getString("id");
                String className = object.getString("class");

                // 初始化bean并放入池中
                OBJECTPOOL.putObject(id, getInstance(className, object.getJSONArray("fields")));
            }
            return OBJECTPOOL;
        } catch (IOException | ClassNotFoundException |
                InstantiationException | IllegalAccessException |
                NoSuchMethodException | InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }

    public Object getObject(String id) {
        return pool.get(id);
    }

    public void putObject(String id, Object object) {
        pool.put(id, object);
    }

    public void clear() {
        pool.clear();
    }
}
           
  • Client
public class Client {

    @Test
    public void client() {
        ObjectPool pool = ObjectPool.init("config.json");
        User user = (User) pool.getObject("id1");
        System.out.println(user);

        Bean bean = (Bean) pool.getObject("id2");
        System.out.println(bean);

        ComplexBean complexBean = (ComplexBean) pool.getObject("id3");
        System.out.println(complexBean);
    }
}
           
  • ComplexBean
public class ComplexBean {

    private String name;

    private Bean refBean;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Bean getRefBean() {
        return refBean;
    }

    public void setRefBean(Bean refBean) {
        this.refBean = refBean;
    }

    @Override
    public String toString() {
        return "ComplexBean{" +
                "name='" + name + '\'' +
                ", refBean=" + refBean +
                '}';
    }
}
           

Spring架構就是通過這種方式将成員變量值以及依賴對象等都放在配置檔案中進行管理的,進而實作了較好地解耦(不過Spring是通過XML作為配置檔案).

通路成員變量

通過

Class

對象的的

getField()

方法可以擷取該類所包含的全部或指定的成員變量

Field

,

Filed

提供了如下兩組方法來讀取和設定成員變量值.

  • getXxx(Object obj)

    : 擷取obj對象的該成員變量的值, 此處的Xxx對應8中基本類型,如果該成員變量的類型是引用類型, 則取消get後面的Xxx;
  • setXxx(Object obj, Xxx val)

    : 将obj對象的該成員變量值設定成val值.此處的Xxx對應8種基本類型, 如果該成員類型是引用類型, 則取消set後面的Xxx;

注: getDeclaredXxx方法可以擷取所有的成員變量,無論private/public;

/**
 * @author jifang
 * @since 16/1/2下午1:00.
 */
public class Client {

    @Test
    public void client() throws NoSuchFieldException, IllegalAccessException {
        User user = new User();
        Field idFiled = User.class.getDeclaredField("id");
        setAccessible(idFiled);
        idFiled.setInt(user, );

        Field nameFiled = User.class.getDeclaredField("name");
        setAccessible(nameFiled);
        nameFiled.set(user, "feiqing");

        Field passwordField = User.class.getDeclaredField("password");
        setAccessible(passwordField);
        passwordField.set(user, "ICy5YqxZB1uWSwcVLSNLcA==");

        System.out.println(user);
    }

    private void setAccessible(AccessibleObject object) {
        object.setAccessible(true);
    }
}
           

使用反射擷取泛型資訊

為了通過反射操作泛型以迎合實際開發的需要, Java新增了

java.lang.reflect.ParameterizedType

java.lang.reflect.GenericArrayType

java.lang.reflect.TypeVariable

java.lang.reflect.WildcardType

幾種類型來代表不能歸一到Class類型但是又和原始類型同樣重要的類型.

類型 含義

ParameterizedType

一種參數化類型, 比如

Collection<String>

GenericArrayType

一種元素類型是參數化類型或者類型變量的數組類型

TypeVariable

各種類型變量的公共接口

WildcardType

一種通配符類型表達式, 如

?

? extends Number

? super Integer

其中, 我們可以使用

ParameterizedType

來擷取泛型資訊.

public class Client {

    private Map<String, Object> objectMap;

    public void test(Map<String, User> map, String string) {
    }

    public Map<User, Bean> test() {
        return null;
    }

    /**
     * 測試屬性類型
     *
     * @throws NoSuchFieldException
     */
    @Test
    public void testFieldType() throws NoSuchFieldException {
        Field field = Client.class.getDeclaredField("objectMap");
        Type gType = field.getGenericType();
        // 列印type與generic type的差別
        System.out.println(field.getType());
        System.out.println(gType);
        System.out.println("**************");
        if (gType instanceof ParameterizedType) {
            ParameterizedType pType = (ParameterizedType) gType;
            Type[] types = pType.getActualTypeArguments();
            for (Type type : types) {
                System.out.println(type.toString());
            }
        }
    }

    /**
     * 測試參數類型
     *
     * @throws NoSuchMethodException
     */
    @Test
    public void testParamType() throws NoSuchMethodException {
        Method testMethod = Client.class.getMethod("test", Map.class, String.class);
        Type[] parameterTypes = testMethod.getGenericParameterTypes();
        for (Type type : parameterTypes) {
            System.out.println("type -> " + type);
            if (type instanceof ParameterizedType) {
                Type[] actualTypes = ((ParameterizedType) type).getActualTypeArguments();
                for (Type actualType : actualTypes) {
                    System.out.println("\tactual type -> " + actualType);
                }
            }
        }
    }

    /**
     * 測試傳回值類型
     *
     * @throws NoSuchMethodException
     */
    @Test
    public void testReturnType() throws NoSuchMethodException {
        Method testMethod = Client.class.getMethod("test");
        Type returnType = testMethod.getGenericReturnType();
        System.out.println("return type -> " + returnType);

        if (returnType instanceof ParameterizedType) {
            Type[] actualTypes = ((ParameterizedType) returnType).getActualTypeArguments();
            for (Type actualType : actualTypes) {
                System.out.println("\tactual type -> " + actualType);
            }
        }
    }
}
           

使用反射擷取注解

使用反射擷取注解資訊的相關介紹, 請參看我的部落格Java注解實踐

反射性能測試

Method/Constructor/Field/Element

都繼承了

AccessibleObject

,

AccessibleObject

類中有一個

setAccessible

方法:

public void setAccessible(boolean flag) throws SecurityException {
    ...
}
           

該方法有兩個作用:

1. 啟用/禁用通路安全檢查開關:值為true,則訓示反射的對象在使用時取消Java語言通路檢查;值為false,則訓示應該實施Java語言的通路檢查;

2. 可以禁止安全檢查, 提高反射的運作效率.

/**
 * @author jifang
 * @since 15/12/31下午4:53.
 */
public class TestReflect {

    @Before
    public void testNoneReflect() {
        User user = new User();

        long start = System.currentTimeMillis();
        for (long i = ; i < Integer.MAX_VALUE; ++i) {
            user.getName();
        }
        long count = System.currentTimeMillis() - start;
        System.out.println("沒有反射, 共消耗 <" + count + "> 毫秒");
    }

    @Test
    public void testNotAccess() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        User user = new User();
        Method method = Class.forName("com.fq.domain.User").getMethod("getName");

        long start = System.currentTimeMillis();
        for (long i = ; i < Integer.MAX_VALUE; ++i) {
            method.invoke(user, null);
        }
        long count = System.currentTimeMillis() - start;
        System.out.println("沒有通路權限, 共消耗 <" + count + "> 毫秒");
    }

    @After
    public void testUseAccess() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        User user = new User();
        Method method = Class.forName("com.fq.domain.User").getMethod("getName");
        method.setAccessible(true);

        long start = System.currentTimeMillis();
        for (long i = ; i < Integer.MAX_VALUE; ++i) {
            method.invoke(user, null);
        }
        long count = System.currentTimeMillis() - start;
        System.out.println("有通路權限, 共消耗 <" + count + "> 毫秒");
    }
}
           

執行上面程式,在我的機器上可以看到使用反射會比直接調用慢

3000毫秒

,但是前提是該方法會執行20E+次(而且伺服器的性能也肯定比我的機器要高),是以在我們的實際開發中,其實是不用擔心反射機制帶來的性能消耗的,而且禁用通路權限檢查,也會有性能的提升.

附-機器配置資訊
Java 反射 Java 反射
擴充閱讀:
為什麼動态類型程式設計語言會如此流行?
動态語言和靜态語言的比較
反射是否真的會讓你的程式性能降低?