Scala和Java Generics - 提取和返回嵌套类型

DK_*_*DK_ 9 java generics scala stanford-nlp scala-java-interop

Java泛型可以根据表达式的返回类型推断泛型类型参数的类型.考虑以下:

public static <T> T uncheckedCast(Object o) {
    return (T)o;
}
Run Code Online (Sandbox Code Playgroud)

我们可以称之为:

Map<Baz,Bog> bazbogMap = new HashMap<Baz,Bog>();
String foo = uncheckedCast(bazbogMap);
Run Code Online (Sandbox Code Playgroud)

这将编译但RuntimeException在调用时抛出一个,因为它会尝试将其转换Map为a String但是失败.但重点是Java <T>根据呼叫站点的预期结果类型推断出值.

我们也可以在Scala中执行以下操作:

def uncheckedCast[T](o: AnyRef): T = o.asInstanceOf[T]
Run Code Online (Sandbox Code Playgroud)

到现在为止还挺好.

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!"
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,在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])
Run Code Online (Sandbox Code Playgroud)

这两个调用put()Vtype参数指定了一个允许它们工作的值,但是一旦V需要从K参数的继承中提取参数,事情就会崩溃.Java代码没有这样的问题.

让Scala不要抱怨的唯一方法是明确定义类型:

kv.get[String, StringKey](classOf[StringKey])
Run Code Online (Sandbox Code Playgroud)

(对于像上面那样简单和做作的东西,它稍微好一点(违反DRY),但是对于更复杂的东西,比如斯坦福核心NLP API,你需要做的事情如下:

doc.get[java.util.Map[Integer, CorefChain], CorefChainAnnotation](classOf[CorefChainAnnotation])
Run Code Online (Sandbox Code Playgroud)

其中包括手动查找嵌套类型以便添加它们,这非常痛苦.)

问题:

有没有办法让这个工作,而不必如此冗长地指定类型参数?更重要的是,为什么这是一个问题开始?当Scala不是时,Java如何能够推断出类型参数?

编辑1:删除了大部分演示Stanford Core NLP问题的代码,并将其替换为Scala/Java泛型的差异的一般示例,这些示例解释了该问题.

Ste*_*eve 3

我无法确切地告诉您为什么 Scala 无法推断出 Java 类型参数,但我至少可以向您展示如何在 Java 中声明类型参数,以便 Scala可以推断它们:

@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);
}
Run Code Online (Sandbox Code Playgroud)

我认为这里的关键点是您根本不需要该类型参数 - 通配符类型参数将为您提供相同的功能。然后,这可以在 Scala 中正常工作,而无需指定任何类型参数:K? extends Key<V>

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

scala> kv.get(classOf[StringKey])
res1: String = BAM!
Run Code Online (Sandbox Code Playgroud)

编辑:

这是我能做的最好的解决方法。它需要更高级的类型和演员阵容。不确定我能否解释为什么可以编译,但其他版本却不能。;-)

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!
Run Code Online (Sandbox Code Playgroud)