11

次のようにオーバーロードされた関数を書きたいと思います。

case class A[T](t: T)
def f[T](t: T) = println("normal type")
def f[T](a: A[T]) = println("A type")

そして、結果は私が期待したとおりです:

f(5) => 通常型
f(A(5)) => A型

ここまでは順調ですね。しかし、問題は同じことが配列では機能しないことです:

def f[T](t: T) = println("normal type")
def f[T](a: Array[T]) = println("Array type")

今、コンパイラは不平を言います:

double 定義: メソッド f:[T](t: Array[T])Unit とメソッド f:[T](t: T)Unit 14 行目は、消去後に同じ型になります: (t: java.lang.Object)Unit

型消去後の 2 番目の関数のシグネチャは、(a: Array[Object])Unit ではなく (t: Object)Unit である必要があるため、互いに衝突しないようにする必要があると思います。ここで何が欠けていますか?

そして、私が何か間違ったことをしている場合、引数の型に応じて正しいものが呼び出されるように、f を記述する正しい方法は何でしょうか?

4

4 に答える 4

9

Java では、ジェネリックでプリミティブ型がサポートされていないため、これが問題になることはありません。したがって、次のコードは Java ではかなり合法です。

public static <T> void f(T t){out.println("normal type");}
public static <T> void f(T[] a){out.println("Array type");}

一方、Scala はすべての型のジェネリックをサポートしています。Scala 言語にはプリミティブがありませんが、結果のバイトコードでは Int、Float、Char、Boolean などの型にプリミティブが使用されます。これが Java コードと Scala コードの違いになります。は .ではないint[]ため、Java コードは配列として受け入れません。したがって、Java はこれらのメソッド パラメータ タイプをおよびに消去できます。(つまり、JVM 上で。)intjava.lang.ObjectObjectObject[]Ljava/lang/Object;[Ljava/lang/Object;

一方、Scala コードは 、 、 などを含むすべての配列をArray[Int]処理Array[Float]Array[Char]ますArray[Boolean]。これらの配列は、プリミティブ型の配列です (またはその可能性があります)。Array[Object]JVM レベルまたはJVM レベルでキャストすることはできませんArray[anything else]Array[Int]andのスーパータイプは 1 つだけです。Array[Char]それはjava.lang.Objectです。これは、より一般的なスーパータイプです。

これらのステートメントをサポートするために、一般的でないメソッド f を使用してコードを作成しました。

def f[T](t: T) = println("normal type")
def f[T <: AnyRef](a: Array[T]) = println("Array type")

このバリアントは、Java コードのように機能します。つまり、プリミティブの配列はサポートされていません。しかし、この小さな変更は、コンパイルするには十分です。一方、次のコードは型消去の理由でコンパイルできません。

def f[T](t: T) = println("normal type")
def f[T <: AnyVal](a: Array[T]) = println("Array type")

ジェネリック メソッドが生成されるため、@specialized を追加しても問題は解決しません。

def f[T](t: T) = println("normal type")
def f[@specialized T <: AnyVal](a: Array[T]) = println("Array type")

@specialized が問題を解決したことを願っていますが (場合によっては)、コンパイラは現時点ではサポートしていません。しかし、それが scalac の優先度の高い拡張になるとは思いません。

于 2013-01-16T13:02:24.090 に答える
8

型消去後の 2 番目の関数のシグネチャは、(a: Array[Object])Unit ではなく (t: Object)Unit である必要があるため、互いに衝突しないようにする必要があると思います。ここで何が欠けていますか?

消去とは正確には、ジェネリック クラスの型パラメーターに関する情報を失い、生の型のみを取得することを意味します。したがって、型パラメーター ( ) がまだあるため、の署名はdef f[T](a: Array[T])できません。経験則として、型パラメーターをドロップして消去型を取得するだけで済みます。これは他のすべての汎用クラスで機能しますが、配列は JVM では特別であり、特にその消去は単純です(生の型はありません)。したがって、消去後の署名は確かに. [更新、私が間違っていた]実際に Java の仕様を確認したところ、ここで完全に間違っていたようです。スペックは言うdef f[T](a: Array[Object])Objectdef f[T](a: Array)Objectarrayfdef f[T](a: Object)

配列型 T[] の消去は |T|[] です

|T|の消去はどこにありますかT。したがって、実際には配列は特別に扱われますが、型パラメーターが実際に削除されている間、型が単なる T ではなく T の配列としてマークされているという特異な点があります。これArray[Int]は、消去後もまだArray[Int]. しかしArray[T]、違います:Tはジェネリック メソッドの型パラメーターですf。あらゆる種類の配列を一般的に扱えるようにするために、scala は (ちなみに Java も同じことをしていると思います) に変更する以外に選択肢はありませArray[T]Object。これは、上で述べたように、生の型などというものは存在ArrayしないためObjectです。

別の言い方をしてみます。通常、 type のパラメーターを使用してジェネリック メソッドをコンパイルする場合MyGenericClass[T]、消去された型が であるという単なる事実MyGenericClassだけで (JVM レベルで) や などの のインスタンス化を渡すことができます。MyGenericClassこれらは実行時に実際にはすべて同じであるためです。ただし、これは配列には当てはまりません。は とはまったく関係のない型であり、一般的な生の型に消去されません。それらの最も一般的でない型はであり、これは、配列が一般的に扱われるときにフードの下で操作されるものです (コンパイラーが要素の型を静的に知ることができないたびに)。MyGenericClass[Int]MyGenericClass[Float]Array[Int]Array[Float]ArrayObject

更新 2 : v6ak の回答に役立つ情報が追加されました。Java はジェネリックでプリミティブ型をサポートしていません。したがって、 inArray[T]T必然的に (Java では、Scala ではなく) のサブクラスであり、したがって(別名)のサブクラスではないことは明らかなプリミティブ型であるScala とは異なり、Objectその消去は完全に理にかなっています。 . Java と同じ状況になるように、上限を指定して制約することができます。Array[Object]TIntObjectAnyRefT

def f[T](t: T) = println("normal type")
def f[T<:AnyRef](a: Array[T]) = println("Array type") // no conflict anymore

この問題を回避する方法として、一般的な解決策はダミー パラメータを追加することです。各呼び出しでダミー値を明示的に渡したくないのは確かなので、ダミーのデフォルト値を指定するか、コンパイラによって常に暗黙的に検出される暗黙的なパラメーターを使用することができます (たとえば、 でdummyImplicit見つかりますPredef)。

def f[T](a: Array[T], dummy: Int = 0)
// or:
def f[T](a: Array[T])(implicit dummy: DummyImplicit)
// or:
def f[T:ClassManifest](a: Array[T])
于 2013-01-16T09:51:30.580 に答える
3

Scalaで型消去を克服するには、マニフェスト(scala 2.9。*)またはTypeTag (scala 2.10)を提供する暗黙のパラメーターを追加すると、次のように型に関して必要なすべての情報を取得できます。

def f [T](t:T)(暗黙のマニフェスト:マニフェスト[T])

mが配列などのインスタンスであるかどうかを確認できます。

于 2013-01-16T09:43:57.737 に答える
3

[Scala 2.9] 解決策は、競合しないようにメソッドのシグネチャを自然に変更する暗黙の引数を使用することです。

case class A()

def f[T](t: T) = println("normal type")
def f[T : Manifest](a: Array[T]) = println("Array type")

f(A())        // normal type
f(Array(A())) // Array type

T : Manifestは、2 番目の引数 list のシンタックス シュガーです(implicit mf: Manifest[T])

残念ながら、なぜの代わりにArray[T]単に消去されるのかわかりません。ObjectArray[Object]

于 2013-01-16T09:41:01.903 に答える