2017-07-17 181 views
3

下面的代碼片段拋出錯誤,如頭部所示,我沒有弄清楚爲什麼它不起作用,因爲T是數字類型,我希望運營商'+'罰款。運算符'+'不能應用於'T','T'用於有界泛型

class MathOperationV1<T extends Number> { 
     public T add(T a, T b) { 
      return a + b; // error: Operator '+' cannot be applied to 'T', 'T' 
     } 
    } 

如果有人能提供一些線索,thx!

+1

請將相關的語言標籤添加到您的問題。 –

+0

這是Java,不是嗎? – lilezek

+0

我剛更新了標題,提醒! –

回答

3

實現這種通用算術思想存在一個基本問題。這個問題並不在於如何在數學上說這應該起作用,而在於它應該如何編譯爲Java編譯器的字節碼。

在你的榜樣,你有這樣的:

class MathOperationV1<T extends Number> { 
     public T add(T a, T b) { 
      return a + b; // error: Operator '+' cannot be applied to 'T', 'T' 
     } 
} 

留裝箱和拆箱不談,問題是,編譯器不知道應該如何編譯+運營商。編譯器應使用+的多個重載版本中的哪一個?對於不同的基元類型,JVM具有不同的算術運算符(即操作碼);因此整數的和運算符與雙精度運算符完全不同(例如,請參閱iadddadd)。如果仔細考慮它,那完全有意義,因爲畢竟整數運算和浮點運算是完全不同。不同的類型也有不同的尺寸等(參見例如ladd)。另外請考慮BigIntegerBigDecimal,它們也擴展爲Number,但那些不支持自動裝箱,因此沒有操作碼直接處理它們。可能有其他幾十個其他類似於其他庫中的實現的其他Number實現。編譯器如何知道如何處理它們?

因此,當編譯器推斷TNumber時,這不足以確定哪些操作碼對操作有效(即裝箱,拆箱和算術)。

後來你建議更改代碼位:

class MathOperationV1<T extends Integer> { 
     public T add(T a, T b) { 
      return a + b; 
     } 
    } 

而且現在+運營商可以用一個整數總和碼來實現,但總和的結果將是一個Integer,而不是一個T ,但仍然會使此代碼無效,因爲從編譯器的角度來看,T可能是Integer以外的其他內容。

我相信沒有辦法讓代碼具有足夠的通用性,以至於您可以忘記這些底層實現細節。

- 編輯 -

爲了回答您的評論部分的問題考慮基於以上MathOperationV1<T extends Integer>最後一個定義以下情形。

你是正確的,當你說,編譯器會做類型擦除的類定義,並且如果考慮到這種類型擦除,就好像使用它似乎是

class MathOperationV1 { 
     public Integer add(Integer a, Integer b) { 
      return a + b; 
     } 
} 

它會被編譯子類Integer應該在這裏工作,但這不是真的,因爲它會使類型系統不健全。讓我試着證明這一點。

編譯器不僅可以擔心的聲明網站,它也有考慮在多個調用點會發生什麼,可能使用不同類型的論據T

例如,想象一下(爲了我的論點),有一個Integer的子類,我們將其稱爲SmallInt。假設我們上面的代碼編譯好了(這實際上是你的問題:爲什麼它不能編譯?)。

如果我們做了以下事情,會發生什麼?

MathOperationV1<SmallInt> op = new MathOperationV1<>(); 
SmallInt res = op.add(SmallInt.of(1), SmallInt.of(2)); 

正如你可以看到op.add()方法的結果預計將是一個SmallInt,而不是一個Integer。然而,我們上面的a + b的結果,從我們已刪除的類定義中,總是會返回Integer而不是SmallInt(因爲+使用JVM整數算術操作碼),因此這個結果是不合適的,對嗎?

您現在可能會想知道,但如果MathOperationV1的類型刪除總是返回Integer,那麼在呼叫站點的世界中它可能會有什麼其他的東西(如SmallInt)呢?

好,編譯器在這裏增加了一些額外的施法者add結果強制轉換爲SmallInt,但只是因爲它已經保證了操作無法返回任何東西比預期的類型等(這就是爲什麼你看到編譯器錯誤)。

換句話說,您的通話網站看起來像這樣擦除之後:

MathOperationV1 op = new MathOperationV1<>(); //with Integer type erasure 
SmallInt res = (SmallInt) op.add(SmallInt.of(1), SmallInt.of(2)); 

但是,如果你能保證add回報總是SmallInt(我們不能因操作問題描述了只會工作在我原來的答案)。

所以,你可以看到,你的類型擦除只是確保在於,根據分型的規則,你可以返回任何延伸的Integer,但一旦您的通話網站聲明瞭T一種說法,你應該在原始代碼中出現T以保持類型系統聲音時始終假設爲相同類型。

您實際上可以通過使用Java反編譯器(您的JDK bin目錄中名爲javap的工具)來證明這些觀點。我可以提供更好的例子,如果你認爲你需要它們,但你會自己嘗試一下,看看底下發生了什麼:-)

+0

對於第二個代碼片段,從我所瞭解的情況來看,編譯器會在這裏進行類型擦除,它將用有界類型替換類型參數T,在我的情況下是Integer,並且它變成類'MathOperationV1 {public Integer add(Integer a,整數b){a}返回a + b; } }'我在這裏誤解了什麼? –

+0

@PhoebeLi這是一個很難回答的簡短評論,所以我通過更多細節豐富了我的答案。我希望我能夠解釋我自己,但除此之外,可以隨時提出更多問題,我會盡我所能澄清任何論點。 –

+1

很多很多人在這裏感謝!我希望我能給你10個更多的讚揚! :)這對我來說更加清晰了,我想我會使用反編譯器做一些實驗,正如您在下一步中所建議的那樣:-) –

1

自動(非)拳擊只適用於可以轉換爲其原始等值的類型。加法僅針對數字基元類型和字符串進行定義。即:int,long,short,char,double,float,byte。數字沒有原始的等價物,所以不能拆箱,這就是爲什麼你不能添加它們。

+0

根據您的回答,我將代碼更改爲,但它仍然不起作用,我不太明白。 –

+0

首先,數字類是最終的,因此您將無法創建任何子類。其次,類型擦除意味着通用類型只在編譯時被檢查,在運行時丟失信息。因此,JVM將無法阻止您將無法拆箱的「對象」傳遞給原始類型。換句話說,你不能做你想做的事情@see https://docs.oracle.com/javase/tutorial/java/generics/erasure.html – xburgos

+0

最好你可以創建一個MathOperation接口,然後讓類實現每個數字類型的接口 – xburgos

1

+未定義爲Number。你可以通過編寫(沒有泛型)看到這個:

Number a = 1; 
Number b = 2; 
System.out.println(a + b); 

這根本就不會編譯。

你不能這樣做除了一般直接:你需要一個BiFunction,一個BinaryOperator,或類似的,這是能夠運行適用於輸入:再次

class MathOperationV1<T extends Number> { 
    private final BinaryOperator<T> combiner; 

    // Initialize combiner in constructor. 

    public T add(T a, T b) { 
     return combiner.apply(a, b); 
    } 
} 

但是,你不妨直接使用BinaryOperator<T>MathOperationV1不會在標準類之上添加任何內容(實際上,它提供的更少)。

+0

你能提供一個你的組合lambda的實現嗎?我認爲你只是在解決問題的時候解決問題。 –

+0

當然:'(a,b) - > a + b'。當你在上下文中使用它時,例如'BinaryOperator plusInt =(a,b) - > a + b;',則推斷出整數性。 –

相關問題