2011-07-22 71 views
31

我有一個Java對象obj具有屬性obj.attr1obj.attr2等屬性都可能通過額外的間接級別訪問:obj.getAttr1()obj.getAttr2(),如果不公開。Java的內省:對象映射

挑戰:我想一個函數,一個對象,並返回一個Map<String, Object>,其中所述鍵是字符串"attr1""attr2"等和值對應的對象obj.attr1obj.attr2。 我想象中的功能將與東西被調用像

  • toMap(obj),如果必要的話
  • toMap(obj, "attr1", "attr3")(其中attr1attr3obj的屬性的子集),
  • 或許toMap(obj, "getAttr1", "getAttr3")

我對Java的內省了解不多:你如何在Java中做到這一點?

現在,我有一個專門的toMap()實施,我關心每個對象的類型,這太過分了樣板。


注意:對於那些誰知道Python的,我想是這樣obj.__dict__。或子集變體的dict((attr, obj.__getattribute__(attr)) for attr in attr_list)

+0

它是否必須遞歸? – biziclop

+0

@biziclop:不,沒有遞歸 – Radim

回答

22

您可以對此使用JavaBeans內省。閱讀上的java.beans.Introspector類:

public static Map<String, Object> introspect(Object obj) throws Exception { 
    Map<String, Object> result = new HashMap<String, Object>(); 
    BeanInfo info = Introspector.getBeanInfo(obj.getClass()); 
    for (PropertyDescriptor pd : info.getPropertyDescriptors()) { 
     Method reader = pd.getReadMethod(); 
     if (reader != null) 
      result.put(pd.getName(), reader.invoke(obj)); 
    } 
    return result; 
} 

重要的提醒:我只有getter方法代碼的交易;它不會找到裸地。有關領域,請參閱高咖啡因的答案。 :-)(你也許會希望將兩種方法結合起來。)

+0

Introspector vs只是反射之間的區別是什麼? –

+0

@Amir:內省使用反射來完成它的工作,但是它的工作水平高於反思。反射查找Java級別的字段和方法;內省查找JavaBeans級別的屬性和事件。 –

+0

所有的答案都很好。我會接受這個,因爲它在我的最終解決方案中證明了對我最有用(涉及'BeanInfo'和'java.io.Serializable')。謝謝大家。 – Radim

5

這裏是要做到這一點真的容易方式。

使用Jackson JSON LIB對象轉換爲JSON。

然後讀取JSON並將其轉換爲一個地圖。

該地圖將包含你想要的一切。

這裏是4襯墊

ObjectMapper om = new ObjectMapper(); 
StringWriter sw = new StringWriter(); 
om.writeValue(object, sw); 
Map<String, Object> map = om.readValue(sw.toString(), Map.class); 

當然額外的勝利是,這是遞歸的,如果它需要

+1

當你可以使用直接的JavaBeans自省時,這太過迂迴。 –

+1

不確定哪一個是更少的代碼。 –

+0

我喜歡這種方式,因爲我不必關心任何關於反射或對象的實際結構。 –

14

這裏有一個粗略的估計將創建地圖的地圖,希望足以讓你指向正確的方向:

public Map<String, Object> getMap(Object o) { 
    Map<String, Object> result = new HashMap<String, Object>(); 
    Field[] declaredFields = o.getClass().getDeclaredFields(); 
    for (Field field : declaredFields) { 
     result.put(field.getName(), field.get(o)); 
    } 
    return result; 
} 
+3

+1我認爲在你的解決方案和我的解決方案之間,OP應該讓它失控。 (你只處理字段;我只處理getter方法。:-P) –

+0

Mine處理所有內容:) –

+0

如果遍歷所有字段,聲明和繼承都可能更好。 – biziclop

44

使用Apache共享BeanUtils的:http://commons.apache.org/beanutils/

是JavaBean地圖的實現,它使用內省獲取和放置性質在bean:

Map<Object, Object> introspected = new org.apache.commons.beanutils.BeanMap(object); 

注:儘管API返回Map<Object, Object>(自1.9.0),鍵值實際的類在返回的映射爲java.lang.String

+3

+1確定這是官方最少量的代碼。 –

+1

看來這個解決方案只有一個處理'put'方法到目前爲止:) – Andrey

+0

這創建了'Map ',而不是'Map vvondra

35

另一種方法是用戶JacksonObjectMapperconvertValue例如:

ObjectMapper m = new ObjectMapper(); 
Map<String,Object> mappedObject = m.convertValue(myObject,Map.class); 
+0

這個答案succintly和熟練地解決原來的問題。不錯的工作! –

+0

什麼是Android的gradle依賴關係? –

+0

沒有檢查所有的方式,但應該是類似於 'compile'c​​om.fasterxml.jackson:jackson-parent:2.7-1'' (當然來自[maven](http:// search。 maven.org/#artifactdetails|com.fasterxml.jackson|jackson-parent|2.7-1|pom)) – Endeios

2

這些都不適用於嵌套屬性,對象映射器除了必須在映射中設置所有要在映射中看到的字段的所有值之外,還有一個公平的工作,即使這樣您也無法在ObjectMapper中輕鬆地避免/忽略對象自己的@Json註釋,基本上可以跳過一些屬性。所以不幸的是,你必須做下面的事情,這只是一個草案,只是給出一個想法。

/* 
    * returns fields that have getter/setters including nested fields as 
    * field0, objA.field1, objA.objB.field2, ... 
    * to take care of recursive duplicates, 
    * simply use a set<Class> to track which classes 
    * have already been traversed 
    */ 
    public static void getBeanUtilsNestedFields(String prefix, 
      Class clazz, List<String> nestedFieldNames) throws Exception { 
     PropertyDescriptor[] descriptors = BeanUtils.getPropertyDescriptors(clazz); 
     for(PropertyDescriptor descr : descriptors){ 
      // if you want values, use: descr.getValue(attributeName) 
      if(descr.getPropertyType().getName().equals("java.lang.Class")){ 
       continue; 
      } 
      // a primitive, a CharSequence(String), Number, Date, URI, URL, Locale, Class, or corresponding array 
      // or add more like UUID or other types 
      if(!BeanUtils.isSimpleProperty(descr.getPropertyType())){ 
       Field collectionfield = clazz.getDeclaredField(descr.getName()); 
       if(collectionfield.getGenericType() instanceof ParameterizedType){ 
        ParameterizedType integerListType = (ParameterizedType) collectionfield.getGenericType(); 
        Class<?> actualClazz = (Class<?>) integerListType.getActualTypeArguments()[0]; 
        getBeanUtilsNestedFields(descr.getName(), actualClazz, nestedFieldNames); 
       } 
       else{ // or a complex custom type to get nested fields 
        getBeanUtilsNestedFields(descr.getName(), descr.getPropertyType(), nestedFieldNames); 
       } 
      } 
      else{ 
       nestedFieldNames.add(prefix.concat(".").concat(descr.getDisplayName())); 
      } 
     } 
    }