用于从集合中检索特定类型项的通用强类型 Scala 方法
Generic strongly-typed scala method to retrieve item of a particular type from a collection
假设我有这个 class:
class Example {
val list = List(new Apple(), new Orange(), Banana());
def getIfPresent[T <: Fruit] : Option[T] = list.collectFirst { case x : T => x }
}
你这样使用它:
val example = new Example();
match example.getIfPresent[Apple] {
case Some(apple) => apple.someAppleSpecificMethod();
case None => println("No apple");
}
当然,由于类型擦除,这在 JVM 中不起作用。 getIfPresent
仅匹配 collectFirst
部分函数中的类型 Fruit
,而不是调用中指定的实际类型。
我试图了解类型标签和 class 标签,但真的不知道如何实现上述方法。我看到的例子试图做非常不同的事情。我如何使用 TypeTag 或其他我不知道的机制来实现我想要的方法?
编辑:下面 m-z 的回答是完整的解决方案,但这是我的示例代码的样子:
class Example {
val list = List(new Apple(), new Orange(), Banana());
def getIfPresent[T <: Fruit : ClassTag] : Option[T] = list.collectFirst { case x : T => x }
}
只需添加 : ClassTag
!
您可以在一定程度上使用 ClassTag
来做到这一点。
import scala.reflect.ClassTag
// Modify to apply whatever type bounds you find necessary
// Requires Scala ~2.11.5 or greater (not sure of the exact version, but 2.11.1 does not work, and 2.11.5 does)
def findFirst[A : ClassTag](list: List[Any]): Option[A] =
list collectFirst { case a: A => a }
val l = List(1, "a", false, List(1, 2, 3), List("a", "b"))
scala> findFirst[Boolean](l)
res22: Option[Boolean] = Some(false)
scala> findFirst[Long](l)
res23: Option[Long] = None
但是 ClassTag
有一些注意事项,因为它只会匹配 class,而不匹配类型:
scala> findFirst[List[String]](l)
res24: Option[List[String]] = Some(List(1, 2, 3)) // No!
您可以使用 TypeTag
来解决这个问题,但它不适用于 List[Any]
。这是一个可能的(有点丑陋的)技巧:
import scala.reflect.runtime.universe.{typeOf, TypeTag}
case class Tagged[A : TypeTag](a: A) {
def tpe = typeOf[A]
}
implicit class AnyTagged[A : TypeTag](a: A) {
def tag = Tagged(a)
}
def findFirst[A : TypeTag](list: List[Tagged[_]]): Option[A] =
list collectFirst { case tag @ Tagged(a) if(tag.tpe =:= typeOf[A]) => a.asInstanceOf[A] }
我能想到的保持每个元素的 TypeTag
的唯一方法就是用包装器class 字面地保持它。所以我必须像这样构建列表:
val l = List(1.tag, "a".tag, false.tag, List(1, 2, 3).tag, List("a", "b").tag)
但有效:
scala> findFirst[List[String]](l)
res26: Option[List[String]] = Some(List(a, b))
可能有一种更优雅的方式来构建这样一个具有 TypeTag
s 的列表。
为了好玩,您还可以尝试使用 HList
和 select
对 shapeless 进行此操作。不同之处在于 returning Option[A]
,select
将 return A
(您想要的类型),但如果 HList
包含否 A
,它不会编译。
import shapeless._
val l = 1 :: "a" :: false :: List(1, 2, 3) :: List("a", "b") :: HNil
scala> l.select[Boolean]
res0: Boolean = false
scala> l.select[Boolean]
res1: Boolean = false
scala> l.select[List[String]]
res2: List[String] = List(a, b)
scala> l.select[Long]
<console>:12: error: Implicit not found: shapeless.Ops.Selector[shapeless.::[Int,shapeless.::[String,shapeless.::[Boolean,shapeless.::[List[Int],shapeless.::[List[String],shapeless.HNil]]]]], Long]. You requested an element of type Long, but there is none in the HList shapeless.::[Int,shapeless.::[String,shapeless.::[Boolean,shapeless.::[List[Int],shapeless.::[List[String],shapeless.HNil]]]]].
l.select[Long]
^
假设我有这个 class:
class Example {
val list = List(new Apple(), new Orange(), Banana());
def getIfPresent[T <: Fruit] : Option[T] = list.collectFirst { case x : T => x }
}
你这样使用它:
val example = new Example();
match example.getIfPresent[Apple] {
case Some(apple) => apple.someAppleSpecificMethod();
case None => println("No apple");
}
当然,由于类型擦除,这在 JVM 中不起作用。 getIfPresent
仅匹配 collectFirst
部分函数中的类型 Fruit
,而不是调用中指定的实际类型。
我试图了解类型标签和 class 标签,但真的不知道如何实现上述方法。我看到的例子试图做非常不同的事情。我如何使用 TypeTag 或其他我不知道的机制来实现我想要的方法?
编辑:下面 m-z 的回答是完整的解决方案,但这是我的示例代码的样子:
class Example {
val list = List(new Apple(), new Orange(), Banana());
def getIfPresent[T <: Fruit : ClassTag] : Option[T] = list.collectFirst { case x : T => x }
}
只需添加 : ClassTag
!
您可以在一定程度上使用 ClassTag
来做到这一点。
import scala.reflect.ClassTag
// Modify to apply whatever type bounds you find necessary
// Requires Scala ~2.11.5 or greater (not sure of the exact version, but 2.11.1 does not work, and 2.11.5 does)
def findFirst[A : ClassTag](list: List[Any]): Option[A] =
list collectFirst { case a: A => a }
val l = List(1, "a", false, List(1, 2, 3), List("a", "b"))
scala> findFirst[Boolean](l)
res22: Option[Boolean] = Some(false)
scala> findFirst[Long](l)
res23: Option[Long] = None
但是 ClassTag
有一些注意事项,因为它只会匹配 class,而不匹配类型:
scala> findFirst[List[String]](l)
res24: Option[List[String]] = Some(List(1, 2, 3)) // No!
您可以使用 TypeTag
来解决这个问题,但它不适用于 List[Any]
。这是一个可能的(有点丑陋的)技巧:
import scala.reflect.runtime.universe.{typeOf, TypeTag}
case class Tagged[A : TypeTag](a: A) {
def tpe = typeOf[A]
}
implicit class AnyTagged[A : TypeTag](a: A) {
def tag = Tagged(a)
}
def findFirst[A : TypeTag](list: List[Tagged[_]]): Option[A] =
list collectFirst { case tag @ Tagged(a) if(tag.tpe =:= typeOf[A]) => a.asInstanceOf[A] }
我能想到的保持每个元素的 TypeTag
的唯一方法就是用包装器class 字面地保持它。所以我必须像这样构建列表:
val l = List(1.tag, "a".tag, false.tag, List(1, 2, 3).tag, List("a", "b").tag)
但有效:
scala> findFirst[List[String]](l)
res26: Option[List[String]] = Some(List(a, b))
可能有一种更优雅的方式来构建这样一个具有 TypeTag
s 的列表。
为了好玩,您还可以尝试使用 HList
和 select
对 shapeless 进行此操作。不同之处在于 returning Option[A]
,select
将 return A
(您想要的类型),但如果 HList
包含否 A
,它不会编译。
import shapeless._
val l = 1 :: "a" :: false :: List(1, 2, 3) :: List("a", "b") :: HNil
scala> l.select[Boolean]
res0: Boolean = false
scala> l.select[Boolean]
res1: Boolean = false
scala> l.select[List[String]]
res2: List[String] = List(a, b)
scala> l.select[Long]
<console>:12: error: Implicit not found: shapeless.Ops.Selector[shapeless.::[Int,shapeless.::[String,shapeless.::[Boolean,shapeless.::[List[Int],shapeless.::[List[String],shapeless.HNil]]]]], Long]. You requested an element of type Long, but there is none in the HList shapeless.::[Int,shapeless.::[String,shapeless.::[Boolean,shapeless.::[List[Int],shapeless.::[List[String],shapeless.HNil]]]]].
l.select[Long]
^