[英]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
原始類型)。
因此擦除后
[更新,我錯了]實際上在檢查了java規范后,看來我在這里完全錯了。 規范說 f
的簽名確實是
def f[T](a: Object)
。
數組類型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可以將這些方法參數類型擦除為Object
和Object[]
。 (這意味着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]
。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.