2014-05-03 91 views
5

我有一個Scala集合,其中包含不同子類型的對象。在Scala中,如何在運行時通過指定類型進行過濾?

abstract class Base 

class A extends Base 

class B extends Base 

val a1 = new A() 
val a2 = new A() 
val b = new B() 
val s = List(a1, a2, b) 

我想過濾掉所有A對象或B對象。如果我知道我想在編譯時過濾的對象,那麼我可以輕鬆地做到這一點。

s.filter(_.isInstanceOf[A]) // Give me all the As 
s.filter(_.isInstanceOf[B]) // Give me all the Bs 

我可以這樣做,如果我只知道在運行時過濾的對象類型嗎?我想寫一個這樣的函數。

def filterType(xs:List[Base], t) = xs.filter(_.isInstanceOf[t]) 

哪裏t指示我是否要A型或B的對象。

當然,我不能這樣寫,因爲類型擦除。有沒有一種慣用的斯卡拉方式來解決這個使用類型標籤?我一直在閱讀Scala類型標籤文檔和相關的StackOverflow帖子,但我無法弄清楚。

回答

6

這已經出現了幾次。重複,任何人?

scala> trait Base 
defined trait Base 

scala> case class A(i: Int) extends Base 
defined class A 

scala> case class B(i: Int) extends Base 
defined class B 

scala> val vs = List(A(1), B(2), A(3)) 
vs: List[Product with Serializable with Base] = List(A(1), B(2), A(3)) 

scala> def f[T: reflect.ClassTag](vs: List[Base]) = vs collect { case x: T => x } 
f: [T](vs: List[Base])(implicit evidence$1: scala.reflect.ClassTag[T])List[T] 

scala> f[A](vs) 
res0: List[A] = List(A(1), A(3)) 
+0

對你有好處,因爲我沒有意識到'match'可以利用'ClassTag',這真的很關鍵。 – wingedsubmariner

1

類型擦除將破壞類型參數中的任何信息,但對象仍然知道它們屬於哪個類。因此,我們無法過濾任意類型,但我們可以按類或接口/特徵進行過濾。 ClassTag優於TypeTag

import scala.reflect.ClassTag 

def filterType[T: ClassTag](xs: List[Base]) = xs.collect { 
    case x: T => x 
} 

,我們可以使用這樣的:

scala> filterType[B](s) 
res29: List[B] = List([email protected]) 

scala> filterType[Base](s) 
res30: List[Base] = List([email protected], [email protected], [email protected]) 

這種方法是在運行時安全的,如果類型T是不通用的。如果有class C[T] extends Base,我們無法安全地過濾C[String]

+0

看起來像@ wingedsubmariner,有翅膀,只是打敗了我,儘管模式匹配器消除了isInstance測試。 –

+0

你說得對,@ som-snytt。我清理了我的答案,不再使用'isInstance'和'asInstanceOf'。 – wingedsubmariner

+0

@wingedsubmariner這是一個很好的解決方案;我嘗試通過用'[_>:T]'替換'[Base]'來爲函數簽名添加類型邊界,想法是在編譯時捕獲原則上永遠不會成功的過濾操作(假設原始列表人口沒有受到破壞)。但編譯器不會拒絕它們。對此有何想法? – satyagraha

相關問題