2010-09-07 2 views
4

им нового в Скале и побежал в следующую проблему:фильтр коллекции Scala по типу

Я хочу, чтобы получить подколлекцию существующей коллекции, которая содержит только элементы определенного типа. Следующие работы:

class C(val name : String) 
class D(name : String) extends C(name) { } 

val collection = Set[C](new C("C1"),new D("D1"),new C("C2"),new D("D2")) 
collection.collect{case d : D => d}.size must be === 2 // works 

Но когда я пытаюсь расширить классы коллекций с помощью метода «onlyInstancesOf [Type]» это не работает. Во-первых моя реализация:

object Collection { 
    implicit def extendScalaCollection[E](coll : Traversable[E]) = new CollectionExtension[E](coll) 
} 

class CollectionExtension[E](coll : Traversable[E]) { 

    def onlyInstancesOf[SpecialE <: E] : Traversable[SpecialE] = { 
     coll.collect({case special : SpecialE => special}).asInstanceOf[Traversable[SpecialE]] 
    } 
} 

Так что, когда я использую это расширение и выполнение:

collection.onlyInstancesOf[D].size must be === 2 

Я получаю сообщение об ошибке, что .size возвращенной 4, а не 2. Кроме того, я проверил, результат на самом деле содержит C1 и C2, хотя это не должно.

Когда я делаю:

collection.onlyInstancesOf[D].foreach(e => println(e.name)) 

я получаю исключение:

java.lang.ClassCastException: CollectionsSpec$$anonfun$1$C$1 cannot be cast to CollectionsSpec$$anonfun$1$D$1 

Так, очевидно, полученный набор по-прежнему содержит элементы, которые должны были отфильтрованы.

Я не понимаю, почему это происходит, может ли кто-нибудь объяснить это?

Edit: Scala: Scala код бегуна версия 2.8.0.final

+2

Не генерирует ли этот код предупреждение? – mkneissl

+0

Вы правы, но, как оказалось, предупреждение не показывалось во время тестирования, потому что файл был скомпилирован некоторое время назад и не был перекомпилирован:/ – Ragmaanir

+0

Возможный дубликат [Тип коллекции Scala для фильтра] (http: // stackoverflow. ком/вопросы/2218558/Scala-коллекция-типа для-фильтра) – thSoft

ответ

10

Обратите внимание на предупреждения компилятора и добавьте -unchecked свои SCALA параметры командной строки.

M:\>scala -unchecked 
Welcome to Scala version 2.8.0.final (Java HotSpot(TM) Client VM, Java 1.6.0_21) 
. 
Type in expressions to have them evaluated. 
Type :help for more information. 

scala> class CollectionExtension[E](coll : Traversable[E]) { 
    | 
    |  def onlyInstancesOf[SpecialE <: E] : Traversable[SpecialE] = { 
    |   coll.collect({case special : SpecialE => special}).asInstanceOf[Traversable[SpecialE]] 
    |  } 
    | } 
<console>:8: warning: abstract type SpecialE in type pattern SpecialE is unchecked since it is eliminated by erasure 
       coll.collect({case special : SpecialE => special}).asInstanceOf[Traversable[SpecialE]] 
              ^
defined class CollectionExtension 

предупреждение означает, что лучший компилятор может сделать, это эквивалентно:

coll.collect({case special : AnyRef => special}).asInstanceOf[Traversable[_]] 

Для более подробного объяснения типа стирания, и способы, которыми Вы можете работать вокруг него с манифестами, см:

https://stackoverflow.com/questions/tagged/type-erasure+scala

4

Scala работает на JVM, которая, к сожалению, стирает параметры во время выполнения типа: http://en.wikipedia.org/wiki/Generics_in_Java#Type_erasure. В первом примере вы указываете тип в не стертой позиции, и поэтому код выполнения может выполнять сравнение. Во втором примере тип SpecialE стирается, и, следовательно, код вернет все.

Вы можете использовать манифестов SCALA, чтобы восстановить часть информации, потерянной типа стирания:

import scala.reflect.ClassManifest 
class CollectionsExtension[E <: AnyRef](coll : Traversable[E]) { 
    def onlyInstancesOf[SpecialE <: E](implicit m : Manifest[SpecialE]) : Traversable[SpecialE] = { 
    coll.collect({case e if (ClassManifest.singleType(e) <:< m) => e}).asInstanceOf[Traversable[SpecialE]] 
    } 
} 
3

Как предупреждение сказать:

<console>:14: warning: abstract type SpecialE in type pattern SpecialE is unchecked since it is eliminated by erasure 
       coll.collect({case special : SpecialE => special}).asInstanceOf[Traversable[SpecialE]] 

Давайте посмотрим реализацию collect:

def collect[B, That](pf: PartialFunction[A, B])(implicit bf: CanBuildFrom[Repr, B, That]): That = { 
    val b = bf(repr) 
    for (x <- this) if (pf.isDefinedAt(x)) b += pf(x) 
    b.result 
} 

Обратите внимание, что здесь нет шаблона. Это принципиальное отличие - когда вы пишете «collection.collect{case d : D => d}», компилятор точно знает, о каком типе вы говорите: D.

С другой стороны, когда вы пишете coll.collect({case special : SpecialE => special}), компилятор не знает, какой тип SpecialE, потому что SpecialE - это просто параметр типа. Поэтому он не может генерировать код, который знает, что такое SpecialE, и во время выполнения нет SpecialE - байт-код просто использует java.lang.Object.

6

Как указывали другие, манифестации могут спасти вас. Вот пример того, как, ограничиваясь не-примитивы, и при условии, что мы не хотим, чтобы хранить манифесты в нашей коллекции, но вместо того, чтобы использовать отражение на месте, чтобы понять вещи:

class CollectionExtension[E <: AnyRef](coll : Traversable[E]) { 
    def onlyInstancesOf[SpecialE <: E](implicit mf : Manifest[SpecialE]) : Traversable[SpecialE] = { 
    coll.collect({ 
     case special if mf.erasure.isAssignableFrom(special.getClass) => special 
    }).asInstanceOf[Traversable[SpecialE]] 
    } 
} 

и здесь в действие:

scala> val ce = new CollectionExtension(List(Some(1),Some(5),"This","Fox")) 
ce: CollectionExtension[java.lang.Object] = [email protected]7 

scala> val opts = ce.onlyInstancesOf[Some[_]] 
opts: Traversable[Some[_]] = List(Some(1), Some(5)) 

scala> val strings = ce.onlyInstancesOf[String] 
strings: Traversable[String] = List(This, Fox)