从超类列表中提取给定子类的元素

Posted

技术标签:

【中文标题】从超类列表中提取给定子类的元素【英文标题】:Extract element of given subclass from list of superclass 【发布时间】:2021-09-10 05:42:47 【问题描述】:

鉴于此代码

class Animal
class Dog extends Animal
class Cat extends Animal
class Pitbull extends Dog

object MyClass 
    def main(args: Array[String]) 
        val animals: List[Animal] = List(new Dog(), new Cat(), new Pitbull(), new Dog(), new Cat())
        getElementOftype(animals, PitBull)
    
    
    def getElementOftype(list: List[Animal], givenType: ???): Animal = 
        
    

我想从这个 List 中提取 Pitbull 类型的第一个元素,我应该如何处理?

我试过但感觉不对

trait Identity
    def who: String


class Animal extends Identity
    override def who = "Animal"



class Dog extends Animal with Identity
    override def who = "Dog"



class Cat extends Animal with Identity
    override def who = "Cat"


class Pitbull extends Dog with Identity
    override def who = "Pitbull"



object MyClass 
    def main(args: Array[String]) 
        val animals: List[Animal] = List(new Dog(), new Cat(), new Pitbull(), new Dog(), new Cat())
        println(getElementOftype(animals, new Pitbull()))
    
    
    def getElementOftype(list: List[Animal], givenType: Animal): Option[Animal] = 
        list.collectFirstcase s if s.who == givenType.who => s
    

我想简单地将Pitbull 作为参数传递,而不是实例化一个空对象。

这个方法可行,但我认为这不是最好的方法。 必须有一种更大规模的方式来做到这一点。

【问题讨论】:

你尝试了什么,什么不起作用? @GaëlJ 我添加了我用作解决方法的代码。 一般来说,您应该避免出现这种情况。当绝对必要时 ClassTag 在类型擦除没有问题的情况下工作,否则您可能需要更高级的东西,例如 ShapelessTypeTag 或您自己的 typeclass. 【参考方案1】:

你可以这样做(未经测试,可能有一些错别字):

def getElementOftype[A <: Animal](list: List[Animal])(implicit ct: ClassTag[A]): Option[A] = 
  list.collectFirst  case s: A => s 

ClassTag 是匹配collectFirstA 类型所必需的。

【讨论】:

我应该使用getElementOftype(animals)(Pitbull)这个函数? getElementOftype[Pitbull](animals) 如果您愿意,也可以显式传递 Class 作为第二个参数,但这不是必需的。 谢谢,代码运行良好。我不得不使用scala.reflect,我只是想知道这是否会对性能产生任何影响,因为我已经被警告过。我对反射不熟悉,所以我无法自己确定 我不是专家,但 ClassTag 在编译时提供。 AFAIK 不应该有运行时成本。【参考方案2】:

当您写 Animal extends Identity 时,您的意思是 Animal IS an Identity,这是错误的,最好使用这种方法:

class Animal  self: Identity => 

这意味着当你想实例化一个 Animal 时,你需要给它一个 Identity,但你为什么要这样做呢? 您可以像这样简单地使用模式匹配来过滤所需的类型:

import scala.reflect.ClassTag
def getElementsOfType[A <: Animal : ClassTag](animals: List[Animal]): Option[A] = 
  animals.filter 
    case a: A => true
    case _ => false
  .map(_.asInstanceOf[A]).headOption

headOption 是因为你的 api 返回 Option[A] 作为结果,你可以让它返回 List[A] 并去掉 headOption,以防列表中有多个 Pitbull :)

【讨论】:

这行不通。了解类型擦除。 @sarveshseri 在线结果here! 因为第一个元素是 Dog。尝试将您的输入更改为val list: List[Animal] = List(new Cat())。 Scastie 本身正在给你abstract type pattern A is unchecked since it is eliminated by erasure 警告。另外,您为什么要使用 filter + map + headOption 组合,即使在找到所需元素之后也会遍历列表,而且效率低下。 哦,是的!我忘记了类标签,我进行了编辑。感谢您的有用评论@sarveshseri! 最好使用findcollectFirst 而不是filter + map + headOption 这个东西。

以上是关于从超类列表中提取给定子类的元素的主要内容,如果未能解决你的问题,请参考以下文章

从超类调用子类的方法

Python子类方法从超类方法继承装饰器

从超类调用子类方法

子类是不是从超类继承私有实例变量

从超类对象初始化子类实例

无法从超类访问 Django 模型的子类