2017-05-29 130 views
3

我有一個通用的方法接受類類型和該類的字段進行更新。 對於前:Java泛型 - 類型鑄造問題

class A { 
    private int a; 
    private int b; 
} 

class B { 
private int c; 
private int d; 
} 

在運行時,如果我們通過類類型爲「的A.class」和fieldstoBeUpdated爲「B」,什麼是訪問特定類領域的getter/setter方法的最佳途徑,以便我們可以修改這些字段。

public <T> void find(T clazz, List<String> fieldsToBeUpdated) { 
List<T> collectionList = findAll((Class<T>) clazz); 
collectionList.parallelStream().forEach(p -> { 
     if (clazz instanceof A) { 
      fieldsToBeUpdated.parallelStream().forEach(classFieldName -> { 
       switch(classFieldName) { 
       case "a":((A)p).setA(10); 
       break; 
       case "b":((A)p).setB(20); 
       break; 
       } 
      }); 
     } 

     if (clazz instanceof B) { 
      fieldsToBeUpdated.parallelStream().forEach(classFieldName -> { 
       switch(classFieldName) { 
       case "c":((B)p).setC(30); 
       break; 
       case "d":((B)p).setD(40); 
       break; 
       } 
      }); 
     } 
    }); 
} 

我已經寫上面的代碼來實現相同。

但問題是我有30個這樣的類作爲參數傳遞給這個通用方法,並且該類的字段列表要更新/修改。

它不是正確的實現來編寫30個這樣的if語句來檢查類的類型,然後將類型轉換爲該類的對象。

有沒有更好的方法來實現?

在此先感謝。

+0

作爲一個側面說明,這看起來並不像一個良好的使用parallelStream'的'。你沒有那麼多的字段需要更新,所以性能可能會更差,而你的'forEach' lambda [修改共享狀態時沒有任何線程安全性](https://docs.oracle.com/javase/tutorial/必需/併發/ memconsist.html)。 – Radiodef

回答

0

使用@Mena給出的建議,我想出了一個使用反射API的解決方案。

我通過fieldsToBeUpdated參數的列表,並在每一次迭代中(作爲參數傳遞)我檢查如果該字段是存在於對象使用下面的片線的迭代:

這將返回字段對象如果存在,否則爲空。

null != clazz.getDeclaredField(field) 

下面是對整個執行邏輯:

public <T> void find(Class clazz, List<String> fieldsToBeUpdated) { 
    List<T> collectionList = db.findAll((Class<T>) clazz); 
    if (CollectionUtils.isNotEmpty(collectionList)) { 
     collectionList.stream().forEach(p -> { 
      fieldsToBeUpdated.stream().forEach(field -> { 
      Date date = null; 
       try { 
       if (null != clazz.getDeclaredField(field)) { 
         Field f = clazz.getDeclaredField(field); 
         f.setAccessible(true); 
         date = (Date) f.get(p); 
        } 
       } catch (NoSuchFieldException|SecurityException|IllegalArgumentException|IllegalAccessException e) { 
        e.printStackTrace(); 
       } 
      }); 
     }); 
    } 
} 
4

您的AB類似乎都提供了set/getCreatedTimeset/getUpdatedTime方法。

如果其他28個左右的類提供了這些(就像你的問題所暗示的那樣),那麼只需要有一個以這些方法爲特徵的通用接口,以供所有類實現。

然後,您可以將方法的通用類型綁定到該接口,並放棄所有instanceof語句和隨後的顯式轉換。

唯一的缺點是如果在List中有一個字段名稱與傳遞給該方法的具體類不相關。

如果要強制執行此操作,可以對對象使用反射來發現字段是否按名稱存在。然後,您可以輕鬆處理任何遺漏的警告字段(或您認爲適用的任何機制)。

注意

正如thijs-steel提到的,如果你的「時間」的方法共享相同的實現,你可以有你的30個班擴展了一個公共抽象父,只有實現了「時間」的方法。

或者,你因爲你清楚地使用Java 8

實例可以使用default方法

interface I { 
    // assuming parameters and return types here 
    public void setCreatedTime(ZonedDateTime z); 
    public void setUpdatedTime(ZonedDateTime z); 
    public ZonedDateTime getCreatedTime(); 
    public ZonedDateTime getUpdatedTime(); 
} 

// A, B etc. all implement I 

public <T extends I> void find(T object, List<String> fieldsToBeUpdated) { 
    fieldsToBeUpdated 
    .parallelStream() 
    .forEach(
     field -> { 
      switch(field) { 
       case "a": { 
        try { 
         object.getClass().getDeclaredField("a"); 
         // we're good 
         object.setCreatedTime(...); 
        } 
        catch (NoSuchFieldException e) { 
         // TODO something 
        } 
        break; 
       } 
       // ... 
      } 
     }); 
} 

更新

如果你的類不共享同一領域的所有,你可能想要完全改變整個方法。

您可能想要使用繼承並在每個類中都有自己的find方法實現,而不是具有「一個通用方法適用所有」邏輯實現範例。

這將允許在每個實現中使用較小的switch語句,並在default的情況下執行錯誤處理。

你也仍然還是有find方法以推廣行爲的T extends Findable(其中Findable宣佈了「時間」的方法現在find方法),並簡單地調用指定的T對象find

甚至在FindableTimed之間分開關注,並讓你的類同時實現。

+0

而不是一個接口,實際的類繼承可能會更好 –

+0

@ThijsSteel確實如果「時間」方法有一個共同的實現。 – Mena

+0

這30個班都沒有類似的領域。如示例中所述,這些類將具有不同的字段。 –

0

我想提取接口第一:

public interface TimeManipulator { 
    public void setCreatedTime(long createdTime); 
    public long getCreatedTime(); 
    public void setUpdatedTime(long createdTime); 
    public long getUpdatedTime(); 
} 

並將其應用到的類:

class A implements TimeManipulator { 
    ... 
} 

class B implements TimeManipulator { 
    ... 
} 

然後,所有你需要做的就是將T綁定到這個接口:

public <T extends TimeManipulator> void find(T p, List<String> fieldsToBeUpdated) { 
    fieldsToBeUpdated.parallelStream().forEach(field -> { 
     switch(field) { 
     case "a": 
     case "c": 
      p.setCreatedTime(TimeUtils.toGMT(p.getCreatedTime(), ZoneOffset.of("+05:30"))); 
      break; 
     case "b": 
     case "d":p.setUpdatedTime(TimeUtils.toGMT(p.getUpdatedTime(), ZoneOffset.of("+05:30"))); 
     break; 
     } 
    }); 
}