我需要将传入对象的类型与一组预定义的对象相交.
原始方法是扫描每个预定义类型的传入集合:
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
...
}
Run Code Online (Sandbox Code Playgroud)
哇,这对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>();
...
}
};
Run Code Online (Sandbox Code Playgroud)
在这个C++示例中,我们只提到要扫描一次的每种类型.
好的,Scala也适用于它的类型参数,让我们尝试:
def fillHiddenType[T] {
for (sf <- shownFields)
if (sf.isInstanceOf[T])
return
hiddenFields+=new T
}
Run Code Online (Sandbox Code Playgroud)
但编译器不喜欢创建T实例:class type required but T found.尝试将实例作为参数传递,然后出现下一个问题warning: abstract type T in type is unchecked since it is eliminated by erasure,isInstanceOf[]并且始终为真!类型T是如此好,即使[T <:Field]不允许将它与我们的集合showsFields中的类型进行比较,也会完全删除.
问题是:如何以紧凑的方式测试集合中给定类型的对象的存在?
更新 以下工作代码在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(_))
}
Run Code Online (Sandbox Code Playgroud)
找到具有给定类型的集合的第一个成员是微不足道的:
shownFields.find(_.isInstanceOf[Field1])
Run Code Online (Sandbox Code Playgroud)
但是仍然会返回一个实例Option[Field],而不是Option[Field1]- 你想要强类型的实例.collect方法有助于:
showFields.collect{case x : Field1 => x}
Run Code Online (Sandbox Code Playgroud)
这将返回一个Iterable[Field1],然后您可以使用headOption选择iterable的第一个元素作为Option[Field1],Some[Field1]如果存在,或者None否则:
showFields.collect{case x : Field1 => x}.headOption
Run Code Online (Sandbox Code Playgroud)
为了提高效率,而不是计算列表中的所有Field1,我也会通过以下view方法使其变得懒惰:
showFields.view.collect{case x : Field1 => x}.headOption
Run Code Online (Sandbox Code Playgroud)
然后提供默认值,如果找不到,请使用getOrElseOptions提供的方法:
showFields.view.collect{case x : Field1 => x}.headOption getOrElse (new Field1)
Run Code Online (Sandbox Code Playgroud)
更新
我只是回过头来看看这个问题,如果你希望hiddenFields包含showFields中没有成员的每个Field子类型的新实例.
要查找以下所示的所有类型Iterable:
val shownFieldTypes = showFields.map(_.getClass).toSet
Run Code Online (Sandbox Code Playgroud)
(将其转换为设置强制唯一值)
如果你有一组你感兴趣的字段:
val allFieldTypes = Set(classOf[Field1], classOf[Field2], ...)
Run Code Online (Sandbox Code Playgroud)
你可以减去找到缺少的:
val hiddenFieldTypes = allFieldTypes -- shownFieldTypes
Run Code Online (Sandbox Code Playgroud)
这里的问题是你会被使用newInstance困住,并且反射并不总是令人满意的......所以:
val protoHiddenFields = Set(new Field1, new Field2, ...)
val allFieldTypes = protoHiddenFields.map(_.getClass)
val hiddenFieldTypes = allFieldTypes -- shownFieldTypes
val hiddenFields = protohiddenFields.filter(hiddenFieldTypes contains _.getClass)
Run Code Online (Sandbox Code Playgroud)
这种方法的优点在于,如果您愿意,可以使用构造函数参数初始化原型
| 归档时间: |
|
| 查看次数: |
322 次 |
| 最近记录: |