2017-07-17 3 views
3

次のコードスニペットでは、ヘッダーに示されているエラーが発生します。 Number型の場合、私は演算子 '+'が正常であることを期待していました。制限付きジェネリック型の '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' 
     } 
    } 

誰かが手がかりを与えることができれば感謝します。

+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実装があります。コンパイラはどのように対処するのか知っていましたか?

したがって、コンパイラががNumberであると推測すると、操作に有効なオペコード(ボクシング、アンボックス、算術)を判断するには不十分です。

後にあなたにコードを少し変更することをお勧め:

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

そして今+オペレータが整数の和オペコードで実現することができますが、和の結果はInteger、ないTだろうコンパイラの観点からTInteger以外のものになる可能性があるため、このコードは無効になります。

私は、これらの基本的な実装の詳細を忘れるほどコードを一般的にする方法はないと考えています。

--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ディレクトリにあるツール)を使用して、これらの点を証明できます。あなたがそれらを必要と思っている場合は、より洗練された例を提供することができますが、あなた自身で試して、何が起こっているかをよく知ることができます:-)

+0

2番目のコードスニペットについては、私が理解しているところでは、コンパイラは型パラメータTを、私の場合はInteger型であるbounded型に置き換え、 'MathOperationV1 { ]になるようにここで型削除を行います。public Integer add(Integer a、整数b){ リターンa + b; } }「私はここで何を誤解していますか? –

+0

@PhoebeLiこれは短いコメントで答えるのが難しいので、私は詳細をつけて私の答えを豊かにしました。私は自分自身を説明することができたと思いますが、そうでなければ、もっと質問してください。私は何らかの議論を明確にするために全力を尽くします。 –

+1

ここに多くの多くのありがとう!私はあなたに10以上のアップフォースを与えることができたらいいと思う! :)それは私にははるかに明確です、私はあなたが次のステップとして提案したようにデコンパイラでいくつかの実験を行うつもりだと思います:-) –

1

自動(un)ボクシングは、元の同等のものに変換できるタイプでのみ機能します。加算は、数値プリミティブ型+文字列に対してのみ定義されます。すなわち、int、long、short、char、double、float、byte。 Numberにはプリミティブなものはありませんので、アンボックスすることはできません。そのため、追加できません。

+0

あなたの答えによると、コードをに変更しましたが、まだ動作していません。 –

+0

まず、数値クラスはfinalクラスなので、サブクラスを作成することはできません。第2に、型消去とは、コンパイル時にジェネリック型のみがチェックされ、実行時に情報が失われることを意味します。したがって、JVMは、アンボックスすることができない "Object"をプリミティブ型に渡すことを防ぐことはできません。つまり、あなたがしたいことはできません。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); 

これは単純にコンパイルされません。

あなたは一般的に直接追加を行うことはできません。あなたが入力に操作を適用することができます似てBiFunctionBinaryOperator、または、必要:再び

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

コンバイナラムダの実装を提供できますか?あなたはそれを解決せずに問題をただ動かすと思う。 –

+0

確かに: '(a、b) - > a + b'。これを文脈で使用すると、たとえば'BinaryOperator plusInt =(a、b) - > a + b;'、 'Integer'が推論されます。 –

関連する問題