9

Java ジェネリックは、式の戻り値の型に基づいて、ジェネリック型パラメーターの型を推測できます。次の点を考慮してください。

public static <T> T uncheckedCast(Object o) {
    return (T)o;
}

次のように呼び出すことができます。

Map<Baz,Bog> bazbogMap = new HashMap<Baz,Bog>();
String foo = uncheckedCast(bazbogMap);

これはコンパイルされますが、呼び出されると a がスローされます。これは、を aRuntimeExceptionにキャストしようとして失敗するためです。しかし要点は、Javaがコールサイトで予想される結果の型に基づいて の値を推測したことです。MapString<T>

これは、Scala で次のように行うこともできます。

def uncheckedCast[T](o: AnyRef): T = o.asInstanceOf[T]

ここまでは順調ですね。

Java は、ネストされた定義から型引数を推測し、それらを返すこともできます (つまり、型を使用する前に、上記のように使用するために結果を実際に型に割り当てる必要はありません。Java はそれが既に何であるかを知っています)。

これを示す簡単なクラス:

import java.util.HashMap;
import java.util.Map;

public class KeysAndValues {
    public interface Key<T> {}
    public static class StringKey implements Key<String> {}

    private final Map<Class<?>, Object> lookup;

    public KeysAndValues() {
        lookup = new HashMap<Class<?>, Object>();
    }

    @SuppressWarnings("unchecked")
    public <V, K extends Key<V>> V put(Class<K> key, V value) {
        return (V) lookup.put(key, value);
    }

    @SuppressWarnings("unchecked")
    public <V, K extends Key<V>> V get(Class<K> key) {
        return (V) lookup.get(key);
    }

    public static void test() {
        KeysAndValues kv = new KeysAndValues();
        kv.put(StringKey.class, "BAM!"); // returns null
        kv.put(StringKey.class, "BOOM!"); // returns "BAM!"
        kv.get(StringKey.class); // returns "BOOM!"
    }
}

ただし、Scala では、このクラスが問題を引き起こします。REPL:

scala> val kv = new KeysAndValues
kv: KeysAndValues = KeysAndValues@13c2982e

scala> import KeysAndValues.StringKey
import KeysAndValues.StringKey

scala> kv.put(classOf[StringKey], "BAM!")
res0: java.lang.String = null

scala> kv.put(classOf[StringKey], "BOOM!")
res1: java.lang.String = BAM!

scala> kv.get(classOf[StringKey])
<console>:10: error: inferred type arguments [Nothing,KeysAndValues.StringKey] do not conform to method get's type parameter bounds [V,K <: KeysAndValues.Key[V]]
              kv.get(classOf[StringKey])

put()型パラメーターの値を指定するための 2 つの呼び出しは機能しますが、パラメーターの継承からパラメーターを抽出する必要があるVとすぐに、問題が発生します。Java コードにはそのような問題はありません。VK

Scala が文句を言わないようにする唯一の方法は、型を明示的に定義することです。

kv.get[String, StringKey](classOf[StringKey])

(上記のような単純で工夫されたものについては、わずかに問題ありません (DRY に違反します)。しかし、スタンフォード コア NLP API のような、より複雑なものについては、次のようなことを行う必要があります。

doc.get[java.util.Map[Integer, CorefChain], CorefChainAnnotation](classOf[CorefChainAnnotation])

ネストされたタイプを手動で検索して追加できるようにすることは、非常に面倒です。)

質問):

型パラメータをそれほど詳細に指定せずにこれを機能させる方法はありますか? さらに重要なことは、そもそもなぜこれが問題なのかということです。Scalaがそうでない場合、Javaはどのようにして型引数を推測できますか?

編集 1 : スタンフォード コア NLP の問題を示すコードのほとんどを削除し、問題を例示する Scala/Java ジェネリックの違いの一般的な例に置き換えました。

4

1 に答える 1

3

ScalaがJava型パラメーターを推測できない理由を正確に説明することはできませんが、少なくとも、Scala推測できるようにJavaで型パラメーターを宣言する方法を説明することはできます。

@SuppressWarnings("unchecked")
public <V> V put(Class<? extends Key<V>> key, V value) {
    return (V) lookup.put(key, value);
}

@SuppressWarnings("unchecked")
public <V> V get(Class<? extends Key<V>> key) {
    return (V) lookup.get(key);
}

ここで重要なのは、その型パラメーターはまったく必要ないということです。ワイルドカード型パラメーターを使用すると、同じ機能が得られます。これは、型パラメーターを指定しなくても、Scalaから問題なく機能します。K? extends Key<V>

scala> kv.put(classOf[StringKey], "BAM!")
res0: String = null

scala> kv.get(classOf[StringKey])
res1: String = BAM!

編集:

これが回避策のために私ができる最善のことです。種類の多いタイプとキャストが必要です。なぜこれがコンパイルされるのか説明できるかどうかはわかりませんが、他のバージョンでは説明できません。;-)

scala> import KeysAndValues._
import KeysAndValues._

scala> import scala.language.higherKinds
import scala.language.higherKinds

scala> def get[V, K[X] <: Key[X]](kv: KeysAndValues, key: Class[_ <: K[V]]) =
     |   kv.get[V, K[V]](key.asInstanceOf[Class[K[V]]])
get: [V, K[X] <: KeysAndValues.Key[X]](kv: KeysAndValues, key: Class[_ <: K[V]])V

scala> val kv = new KeysAndValues
kv: KeysAndValues = KeysAndValues@24c63dac

scala> kv.put(classOf[StringKey], "BAM!")
res0: String = null

scala> get(kv, classOf[StringKey])
res1: String = BAM!
于 2013-02-12T19:04:36.240 に答える