Scala: массивы и стирание типов

Я хотел бы написать перегруженные функции следующим образом:

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)) = > тип

пока все хорошо. Но проблема в том, что то же самое не работает для массивов:

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

теперь компилятор жалуется:

двойное определение: метод f: [T](T: Array [T])блок и метод f: [T](t: T)блок в строке 14 имеют тот же тип после стирание: (t: java.ленг.Объект)Блок

Я думаю, что сигнатура второй функции после стирания типа должна быть (A: Array[Object])Unit not (t: Object)Unit, поэтому они не должны сталкиваться друг с другом. Я что-то упускаю?

и если я делаю что-то неправильно, каков будет правильный способ написать f, чтобы правильный был вызван в соответствии с типом аргумента?

4 ответов


это никогда не проблема в 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] и так далее. Эти массивы являются (или могут быть) массивами примитивных типов. Они не могут быть отлиты в Array[Object] или Array[anything else] на уровне JVM. Существует ровно один супертип 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.


я думаю, что сигнатура второй функции после стирания типа должна быть (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 (ther нет array raw type). И таким образом, подпись f после стирания действительно def f[T](a: Object). [я был неправ] на самом деле после проверки спецификации java, кажется, что я был совершенно неправ здесь. Спецификация говорит

стирание массива типа T[] равно |T / []

здесь |T| - стирание T. Таким образом, массивы действительно обрабатываются специально, но особенность заключается в том, что в то время как параметры типа действительно удаляются, тип помечается как массив T вместо просто T. Это значит, что Array[Int] есть, после стирания все равно Array[Int]. Но!--14--> другое: T параметр типа для универсального метода f. Для того, чтобы иметь возможность лечить любые из массива в целом, у scala нет другого выбора, кроме поворота Array[T] на Object (и я полагаю, что Java делает то же самое, кстати). Это потому, что, как я сказал выше, нет такой вещи, как сырой тип Array, так и должно быть Object.

я попробую выразить это по-другому. Обычно при компиляции универсального метода с параметром типа MyGenericClass[T], сам факт того, что стертый тип MyGenericClass позволяет (на уровне JVM) передать любой экземпляр MyGenericClass, такие как MyGenericClass[Int] и MyGenericClass[Float], потому что они на самом деле все равно во время выполнения. Однако это не относится к массивам:Array[Int] является полностью несвязанным типом с Array[Float], и они не стираются до общего Array сырье тип. Их наименее распространенный тип -Object, и поэтому это то, что манипулируется под капотом, когда массивы обрабатываются обобщенно (everythime компилятор не может знать статически тип элементов).

обновление 2: ответ v6ak добавил полезный бит информации: Java не поддерживает примитивные типы в дженериках. Так Array[T], T обязательно (в Java, но не в Scala) подкласс Object и таким образом его стирания в Array[Object]полностью имеет смысл, в отличие от Scala, где T может быть примитивным типом Int, который определенно не sublclass из 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])

[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, вы можете добавить неявный параметр, который даст вам Манифест (scala 2.9.*) или TypeTag (scala 2.10), а затем вы можете получить всю необходимую информацию о типах, как в:

def f[T] (t: T) (неявный манифест: Манифест[T])

вы можете проверить, является ли M экземпляром массива и т. д.