簡體   English   中英

Scala:數組和類型擦除

[英]Scala: arrays and type erasure

我想編寫重載函數如下:

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 definition:方法f:[T](t:Array [T])單位和方法f:[T](t:T)第14行的單位在擦除后具有相同的類型:(t:java.lang.Object)Unit

我認為類型擦除后第二個函數的簽名應該是(a:Array [Object])Unit not(t:Object)Unit,所以它們不應該相互沖突。 我在這里錯過了什么?

如果我做錯了什么,那么編寫f的正確方法是什么,以便根據參數的類型調用正確的?

我認為類型擦除后第二個函數的簽名應該是(a:Array [Object])Unit not(t:Object)Unit,所以它們不應該相互沖突。 我在這里錯過了什么?

擦除精確意味着您丟失了有關泛型類的類型參數的任何信息,並且只獲取原始類型。 因此def f[T](a: Array[T])的簽名不能是def f[T](a: Array[Object])因為你仍然有一個類型參數( Object )。 根據經驗,你只需要刪除類型參數來獲得擦除類型,這將給我們def f[T](a: Array) 這適用於所有其他泛型類,但是數組在JVM上是特殊的, 特別是它們的擦除只是 Object (其中沒有 array原始類型)。 因此擦除后 f的簽名確實是 def f[T](a: Object) [更新,我錯了]實際上在檢查了java規范后,看來我在這里完全錯了。 規范說

數組類型T []的擦除是| T | []

哪里|T| T的擦除。 所以,確實對數組進行了特殊處理,但奇怪的是,當類型參數確實被刪除時,類型被標記為T的數組而不僅僅是T.這意味着在擦除之后, Array[Int]仍然是Array[Int] 但是Array[T]是不同的: T是泛型方法f的類型參數。 為了能夠一般地處理任何類型的數組,scala除了將Array[T]轉換為Object之外別無選擇(我認為Java的方式也是如此)。 這是因為正如我上面所說的那樣,沒有原始類型的Array ,所以它必須是Object

我會試着用另一種方式。 通常,在使用MyGenericClass[T]類型的參數編譯泛型方法時,擦除類型為MyGenericClass使得(在JVM級別)可以傳遞MyGenericClass任何實例化,例如MyGenericClass[Int]MyGenericClass[Float] ,因為它們在運行時實際上都是一樣的。 但是,對於數組不是這樣的: Array[Int]是與Array[Float]完全無關的類型,它們不會擦除到常見的Array原始類型。 它們最不常見的類型是Object ,因此當數組被一般地處理時(這是編譯器不能靜態地知道元素的類型),這是在引擎蓋下操作的。

更新2 :v6ak的回答添加了一些有用的信息:Java不支持泛型中的原始類型。 所以在Array[T]T必然(在Java中,但不在Scala中)是Object的子類,因此它對Array[Object]擦除完全有意義,不像在Scala中,例如T可以是原始類型Int ,絕對不是Object (又名AnyRef )。 為了與Java處於相同的情況,我們可以使用上限約束T ,並且果然,現在它編譯得很好:

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])

這在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代碼。 Java代碼不接受int[]作為數組,因為int不是java.lang.Object 因此,Java可以將這些方法參數類型擦除為ObjectObject[] (這意味着Ljava/lang/Object;[Ljava/lang/Object;在JVM上。)

另一方面,您的Scala代碼處理所有數組,包括Array[Int]Array[Float]Array[Char]Array[Boolean]等。 這些數組是(或可以是)基本類型的數組。 它們不能在JVM級別上轉換為Array[Object]Array[anything else] 只有一個超類型的Array[Int]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的高優先級增強。

[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是第二個參數列表的語法糖(implicit mf: Manifest[T])

不幸的是,我不知道為什么Array[T]會被刪除為Object而不是Array[Object]

要在scala中克服類型擦除,您可以添加一個隱式參數,它將為您提供Manifest (scala 2.9。*)或TypeTag (scala 2.10),然后您可以獲得有關類型的所有信息,如:

def f [T](t:T)(隱式清單:清單[T])

您可以檢查m是否是Array等的實例。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM