搶先一步
VMware 提供培訓和認證,以加速您的進度。
了解更多在與客戶交談時,我經常聽到一個常見的誤解,認為所有關於泛型類型的資訊都會從 Java 類別檔案中刪除。 這完全是不正確的。 所有靜態泛型資訊都會被保留,只有關於個別實例的泛型資訊才會被刪除。 因此,如果我有一個類別 Foo 實作 List<String>,那麼我可以確定 Foo 實作了由 String 參數化的 List 介面(在執行期間)。 但是,如果我在執行期間實例化 ArrayList<String> 的實例,我無法取得該實例並確定其具體類型參數(我可以確定 ArrayList 需要類型參數)。 在本文中,我將向您展示一些可用的泛型元數據的實際用途,這些元數據簡化了策略介面和實作的建立,它們因處理的物件類型而異。
我在許多應用程式中看到的一種模式是使用某種類型的策略介面,其具體實作各自處理特定的輸入類型。 例如,考慮一個來自投資銀行界的簡單場景。 任何公開交易的公司都可以發布公司行為,這些行為會對其股票帶來實際變化。 其中一個關鍵範例是股息支付,它會向所有股東支付每股一定數量的現金、股票或財產。 在投資銀行內部,接收這些事件的通知並計算由此產生的權利非常重要,以便使交易帳簿保持最新的正確股票和現金價值。
作為一個具體的例子,考慮一下持有 1,200,000 股 IBM 股票的 BigBank。 IBM 決定發放每股 0.02 美元的股息。 因此,BigBank 需要收到股息行為的通知,並在適當的時間點更新其交易帳簿,以反映可用的額外 24,000 美元現金。
權利的計算會因執行的公司行為類型而異。 例如,合併最有可能導致一家公司股票的損失和另一家公司股票的收益。
如果我們考慮一下這在 Java 應用程式中可能如何表現,我們可以假設看到類似這樣的(大大簡化的)例子
public class CorporateActionEventProcessor {
public void onCorporateActionEvent(CorporateActionEvent event) {
// do we have any stock for this security?
// if so calculate our entitlements
}
}
關於事件的通知可能會通過來自外部合作夥伴的許多機制傳入,然後發送到此 CorporateActionEventProcessor 類別。 CorporateActionEvent 介面可以透過許多具體類別實現
public class DividendCorporateActionEvent implements CorporateActionEvent {
private PayoutType payoutType;
private BigDecimal ratioPerShare;
// ...
}
public class MergerCorporateActionEvent implements CorporateActionEvent {
private String currentIsin; // security we currently hold
private String newIsin; // security we get
private BigDecimal conversionRatio;
}
計算權利的過程可以由這樣的介面封裝
public interface EntitlementCalculator {
void calculateEntitlement(CorporateActionEvent event);
}
除了這個介面之外,我們很可能會看到許多類似這樣的實作
public class DividendEntitlementCalculator implements EntitlementCalculator {
public void calculateEntitlement(CorporateActionEvent event) {
if(event instanceof DividendCorporateActionEvent) {
DividendCorporateActionEvent dividendEvent = (DividendCorporateActionEvent)event;
// do some processing now
}
}
}
我們的 CorporateActionEventProcessor 可能會看起來像這樣
public class CorporateActionEventProcessor {
private Map<Class, EntitlementCalculator> entitlementCalculators = new HashMap<Class, EntitlementCalculator>();
public CorporateActionEventProcessor() {
this.entitlementCalculators.put(DividendCorporateActionEvent.class, new DividendEntitlementCalculator());
}
public void onCorporateActionEvent(CorporateActionEvent event) {
// do we have any stock for this security?
// if so calculate our entitlements
EntitlementCalculator entitlementCalculator = this.entitlementCalculators.get(event.getClass());
}
}
在這裡,您可以看到我們維護一個從 CorporateActionEvent 類型到 EntitlementCalculator 實作的 Map,我們使用它來找到每個 CorporateActionEvent 的正確 EntitlementCalculator。
回顧這個範例,第一個明顯的問題是 EntitlementCalculator.calculateEntitlement 的類型設定為僅接收 CorporateActionEvent,導致每個實作內部都有類型檢查和轉換。 我們可以使用泛型輕鬆解決這個問題
public interface EntitlementCalculator<E extends CorporateActionEvent> {
void calculateEntitlement(E event);
}
public class DividendEntitlementCalculator implements EntitlementCalculator<DividendCorporateActionEvent> {
public void calculateEntitlement(DividendCorporateActionEvent event) {
}
}
如您所見,我們引入了一個類型參數 E,它被綁定為擴展 CorporateActionEvent。 然後,我們定義 DividendEntitlementCalculator 實作 EntitlementCalculator<DividendCorporateActionEvent>,導致 E 在 DividendEntitlementCalculator 中適當地替換為 DividendCorporateActionEvent。 刪除類型檢查和轉換的需要。
CorporateActionEventProcessor 類別繼續按原樣工作,但是現在有一些重複並且也存在出錯的可能性。 在註冊特定的 EntitlementCalculator 時,我們仍然必須指定它處理的類型,即使這已經在類別定義中指定了。 鑑於此,可以註冊一個 EntitlementCalculator,用於它可能無法處理的類型
public CorporateActionEventProcessor() {
this.entitlementCalculators.put(MergerCorporateActionEvent.class, new DividendEntitlementCalculator());
}
幸運的是,通過從泛型介面聲明中提取參數類型並將其用作鍵類型,可以很容易地解決這個問題
public void registerEntitlementCalculator(EntitlementCalculator calculator) {
this.entitlementCalculators.put(extractTypeParameter(calculator.getClass()), calculator);
}
我們首先新增一個 registerEntitlementCalculator 方法,該方法委派給 extractTypeParameter 以尋找 EntitlementCalculator 類別的類型參數。
private Class extractTypeParameter(Class<? extends EntitlementCalculator> calculatorType) {
Type[] genericInterfaces = calculatorType.getGenericInterfaces();
// find the generic interface declaration for EntitlementCalculator<E>
ParameterizedType genericInterface = null;
for (Type t : genericInterfaces) {
if (t instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType)t;
if (EntitlementCalculator.class.equals(pt.getRawType())) {
genericInterface = pt;
break;
}
}
}
if(genericInterface == null) {
throw new IllegalArgumentException("Type '" + calculatorType
+ "' does not implement EntitlementCalculator<E>.");
}
return (Class)genericInterface.getActualTypeArguments()[0];
}
在這裡,我們首先通過呼叫 Class.getGenericInterfaces() 來取得表示 EntitlementCalculator 類型的泛型介面的 Type[] 。 此方法與 Class.getInterfaces() 大不相同,後者傳回 Class[]。 呼叫 DividendEntitlementCalculator.class.getInterfaces() 傳回一個表示 EntitlementCalculator 類型的 Class 實例。 呼叫 DividendEntitlementCalculator.class.getGenericInterfaces() 傳回一個表示 EntitlementCalculator 類型的 ParameterizedType 實例,該類型具有 DividendCorporateActionEvent 的類型參數。 在具有泛型和非泛型介面的類別上呼叫 getGenericInterfaces() 將傳回一個包含 Class 和 ParameterizedType 實例的陣列。
接下來,我們迭代 Type[] 並找到 "原始類型" 為 EntitlementCalculator 的 ParameterizedType 實例。 從此,我們可以使用 getTypeArguments() 提取 E 的類型引數,並傳回第一個陣列實例 - 我們知道在這個場景中它總是存在的。
呼叫程式碼可以簡單地根據需要傳入 EntitlementCalculator 實作
CorporateActionEventProcessor processor = createCorporateActionEventProcessor();
processor.registerEntitlementCalculator(new DividendEntitlementCalculator());
現在這是一個非常好的 API,並且可以使用類似 Spring 的東西進一步擴展,您可以使用 ListableBeanFactory.getBeansOfType() 來定位所有已配置的 EntitlementCalculator 實作,並自動將它們註冊到 CorporateActionEventProcessor。
你們中的一些人可能已經注意到的一個有趣的狀況是,完全有可能擁有像這樣的程式碼
EntitlementCalculator calculator = new DividendEntitlementCalculator();
calculator.calculateEntitlement(new MergerCorporateActionEvent());
此程式碼可以順利編譯,但我們知道 DividendEntitlementCalculator.calculateEntitlement 方法僅接受 DividendCorporateActionEvent 物件。 那麼為什麼可以編譯呢? 而且,既然它可以編譯,那麼在執行期間會發生什麼? 好吧,首先回答第二個問題 - Java 仍然通過在執行期間拋出 ClassCastException 來確保類型安全。 為什麼這有效以及回答這個範例實際上為什麼可以編譯的問題,我將很快撰寫另一篇文章...