一般的な算術の考え方の実装には根本的な問題があります。この問題は、数学的に言えば、これがうまくいかなければならないのではなく、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は、異なるプリミティブ型に対して異なる算術演算子(すなわち、オペコード)を有する。したがって、整数の和演算子は、倍精度演算子とはまったく異なるオペコードです(たとえば、iaddとdaddを参照してください)。それについて考えると、結局のところ、整数演算と浮動小数点演算は全く違います。また、異なるタイプは異なるサイズなどを有する(例えば、laddを参照)。また、BigInteger
とBigDecimal
については、Number
も考えていますが、オートボクシングのサポートがないため、直接対処するオペコードはありません。おそらく他のライブラリにあるような数十もの他のNumber
実装があります。コンパイラはどのように対処するのか知っていましたか?
したがって、コンパイラががNumber
であると推測すると、操作に有効なオペコード(ボクシング、アンボックス、算術)を判断するには不十分です。
後にあなたにコードを少し変更することをお勧め:
class MathOperationV1<T extends Integer> {
public T add(T a, T b) {
return a + b;
}
}
そして今+
オペレータが整数の和オペコードで実現することができますが、和の結果はInteger
、ないT
だろうコンパイラの観点からT
はInteger
以外のものになる可能性があるため、このコードは無効になります。
私は、これらの基本的な実装の詳細を忘れるほどコードを一般的にする方法はないと考えています。
--Edit--
上記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 decompiler(javapというJDKのbinディレクトリにあるツール)を使用して、これらの点を証明できます。あなたがそれらを必要と思っている場合は、より洗練された例を提供することができますが、あなた自身で試して、何が起こっているかをよく知ることができます:-)
質問に関連する言語タグを追加してください。 –
これはJavaですね。 – lilezek
私はちょうど思い出させるために、タイトルを更新しました! –