如何在基类型对象的集合中测试一组类型的存在?

时间:2010-10-22 09:08:21

标签: scala

我需要将传入对象的类型与一组预定义的对象相交。

原始方法是扫描每个预定义类型的传入集合:

trait Field
class Field1 extends Field
class Field2 extends Field
class Field3 extends Field
...

class FieldManager(shownFields:Iterable[Field]) {
  var hiddenFields = new ArrayBuffer[Field]
  var found = false
  for (sf <- shownFields) {
    if (sf.isInstanceOf[Field1]) {
      found = true
      break
    }
  if (!found)
    hiddenFields+=new Field1
  for (sf <- shownFields) {
    if (sf.isInstanceOf[Field2]) {
      found = true
      break
    }
  if (!found)
    hiddenFields+=new Field2
  for (sf <- shownFields) {
    if (sf.isInstanceOf[Field3]) {
      found = true
      break
    }
  if (!found)
    hiddenFields+=new Field3
  ...
}
哇,这对Scala来说太冗长了!应该有一个更短的方式。像C ++中的函数模板一样:

class FieldManager {
  vector<Field*> hiddenFields, shownFields;
  template<class T>
  void fillHiddenType() {
    FOR_EACH(Field *, field, shownFields) {
      if (dynamic_cast<T*>(field))
        return
      hiddenFields.push_back(new T)
    }
  }
  void fillHidden() {
    fillHiddenType<Field1>();
    fillHiddenType<Field2>();
    fillHiddenType<Field3>();
    ...
  }
};

在这个C ++示例中,我们只提到要扫描一次的每种类型。

好的,Scala也适用于其类型参数,让我们试试:

def fillHiddenType[T] {
  for (sf <- shownFields)
    if (sf.isInstanceOf[T])
      return
  hiddenFields+=new T  
}

但编译器不喜欢创建T实例:class type required but T found。尝试将实例作为参数传递,下一个问题显示为warning: abstract type T in type is unchecked since it is eliminated by erasureisInstanceOf[]始终为真!类型T被删除得如此之好,即使[T&lt;:Field]也不允许将它与我们收藏的字段中的类型进行比较,这样就完全删除了。

问题是: 如何以紧凑的方式测试集合中给定类型的对象的存在?

更新 以下工作代码在Scala 2.7.7上进行了测试。两个答案都很有用。谢谢你们,伙计们!

//Scala 2.7.7 misses toSet members
implicit def toSet[T](i:Iterable[T]): Set[T] = {
    val rv = new HashSet[T]
    rv ++= i
    rv
}

def filterOut[T](all:Iterable[T], toRemove:Set[T]) = {
    all.filter(x => ! toRemove.contains(x))
}

def filterOutByType[T <: AnyRef](all:Iterable[T], toFilter:Set[T]):Iterable[T] = {
    def toClass(x:AnyRef) = x.getClass.asInstanceOf[Class[T]]
    val allTypes = all map toClass
    val extraTypes = toSet(filterOut(allTypes, toFilter map toClass))
    all.filter(extraTypes contains toClass(_))
}

2 个答案:

答案 0 :(得分:6)

找到具有给定类型的集合的第一个成员是微不足道的:

shownFields.find(_.isInstanceOf[Field1])

但是仍然会返回Option[Field]的实例,而不是Option[Field1] - 您想要强类型的实例。 collect方法将在这里提供帮助:

showFields.collect{case x : Field1 => x}

哪会返回Iterable[Field1],然后您可以使用headOption选择可迭代的第一个元素Option[Field1]Some[Field1]如果存在,或{{ 1}}否则:

None

为了提高效率,而不是计算列表中的所有Field1,我也会通过showFields.collect{case x : Field1 => x}.headOption 方法使其变得懒惰:

view

然后提供默认值(如果找不到),请使用选项提供的showFields.view.collect{case x : Field1 => x}.headOption 方法:

getOrElse

<强>更新

我刚读回了这个问题,如果你希望hiddenFields包含showFields中没有成员的每个Field子类型的新实例。

查找showFields.view.collect{case x : Field1 => x}.headOption getOrElse (new Field1) 中包含的所有类型:

Iterable

(将其转换为设定强制唯一值)

如果您有一组感兴趣的字段:

val shownFieldTypes = showFields.map(_.getClass).toSet

你可以减去找到缺失的那些:

val allFieldTypes = Set(classOf[Field1], classOf[Field2], ...)

这里的问题是你会被使用newInstance困住,反射并不总是令人满意的......所以:

val hiddenFieldTypes = allFieldTypes -- shownFieldTypes

这种方法的优点在于,如果您愿意,可以使用构造函数参数初始化您的原型

答案 1 :(得分:4)

您可以维护一组Field的已知子类,如下所示:

val allFieldClasses = Set[Class[_ <: Field]](
  classOf[Field1], 
  classOf[Field2],
  classOf[Field3],
  ...)

然后创建隐藏字段只是过滤掉这个集合中显示的字段类并构建其余字段的实例:

def createHiddenFields(shownFields: Iterable[Field]):Set[Field] = {
  val toClass = (_:Field).getClass.asInstanceOf[Class[Field]]
  (allFieldTypes -- (shownFields map toClass)) map (_.newInstance)
}

不幸的是,当你添加allFieldClasses的新子类时,你必须记住让Field保持最新 - 编译器不会警告你你的集合不完整 - 但我认为没办法。