【问题标题】:Generic object load function for scalascala的通用对象加载函数
【发布时间】:2011-01-26 21:00:40
【问题描述】:

我开始使用后端使用 Hibernate (JPA) 的 Scala 应用程序。为了加载一个对象,我使用了这行代码:

val addr = s.load(classOf[Address], addr_id).asInstanceOf[Address];

不用说,这有点痛苦。我写了一个看起来像这样的辅助类:

import org.hibernate.Session

class DataLoader(s: Session) {
  def loadAddress(id: Long): Address = {
    return s.load(classOf[Address], id).asInstanceOf[Address];
  }
  ...
}

所以,现在我可以这样做了:

val dl = new DataLoader(s)
val addr = dl loadAddress(addr_id)

问题来了: 如何编写一个通用的参数化方法,该方法可以使用相同的模式加载任何对象?即

val addr = dl load[Address](addr_id)

(或类似的东西。)

我是 Scala 新手,所以请原谅这里特别可怕的任何内容。

【问题讨论】:

    标签: generics scala manifest scala-2.8 parameterization


    【解决方案1】:

    这里是:

    import org.hibernate.Session
    class DataLoader(s: Session) {
      def load[A](id: Long)(implicit m: Manifest[A]): A = {
        return s.load(m.erasure, id).asInstanceOf[A];
      }
    }
    

    EDIT——或者,为了确保任何转换错误——由于休眠返回错误的对象——将在load内部发生,如下所示:

    import org.hibernate.Session
    class DataLoader(s: Session) {
      def load[A](id: Long)(implicit m: Manifest[A]): A = {
        return m.erasure.asInstanceOf[Class[A]].cast(s.load(m.erasure, id));
      }
    }
    

    在 Scala 2.8 上你也可以这样写:

    import org.hibernate.Session
    class DataLoader(s: Session) {
      def load[A : Manifest](id: Long): A = {
        return s.load(manifest[A].erasure, id).asInstanceOf[A];
      }
    }
    

    您也可以将它与隐式会话结合起来,正如Chris 所建议的那样:

    def load[A](id: Long)(implicit m: Manifest[A], s: org.hibernate.Session): A = {
      return s.load(m.erasure, id).asInstanceOf[A];
    }
    

    请注意,您不能将上下文视图符号 (A : Manifest) 与其他隐式参数结合使用。

    【讨论】:

    • @Daniel - 恐怕你的 asInstanceOf 演员表并不安全。考虑两个语句val a = load[Address](1)val b: Any = load[Address](2) 的情况。在第二种情况下,实际上并没有对 Address 类进行强制转换(它们已被删除),并且您相信 Hibernate 实际上会返回一个正确类型的实例(公平地说,它肯定会)。
    • @oxbow 我明白了。您的意思是Class.cast 会导致load 内部出现运行时错误,而不是留下以后可能造成麻烦的东西?
    • 是的——就是这样。这里唯一的“正确”转换是在load 方法之外并由编译器插入,因为引用a 的类型是地址。如果调用 load 方法并将其分配给 Any,则不会发生 CCE,您可能会发现某些东西会中断
    • @oxbow 好的,那么。我添加了一个更安全的版本,根据我所做的一些测试,它应该可以工作。 asInstanceOf 的位置特别令人惊讶。
    【解决方案2】:

    一种方法是利用java.lang.Class.cast 方法来执行以下操作:

    def load[A](clazz: Class[A], id: Long)(implicit s: Session) : A 
           = clazz.cast(s.load(clazz, id))
    

    那么用法如下:

    implicit val s = ...//get hibernate session
    val addr = load(classOf[Address], 1)
    

    它与你已经拥有的并没有太大的不同,但是为了访问class 实例,你需要将它传入。

    我非常有信心你不能安全地Manifests做你想做的事,因为他们不能在编译时提供你需要的参数化Class[Address]为了让演员工作(他们只能提供Class[_]擦除)。我没有看到任何其他机制可以从 Manifest 进行强制转换

    您当然可以使用asInstanceOf[A] 而不是cast,但这会在编译时被删除为isInstanceOf[Object],因此在编译时类型检查方面毫无用处(因此不建议使用)。

    【讨论】:

    • 有趣。这让事情变得更整洁了……但你刚刚还教了我一些新的 Scala 构造。欣赏。
    • 顺便说一句...asInstanceOf[A] 被删除了?这没有任何意义。此外,擦除发生在运行时,而不是编译时。另一方面,我从未想过使用Class[A] 来转换为A。有趣。
    • 我同意丹尼尔的观点,Manifest 没有理由不在这里工作。
    • @Daniel - 擦除发生在编译时。您可以编写转换为参数类型(如 A)的代码,但此类型在编译时被擦除到其上限,在本例中为 Object。因此,在运行时实际发生的转换是对对象的(无意义的)转换。我会听从你对 scala 的智慧,而不是 java :-)
    • @oxbow 我不确定我们是否同意这里编译时的含义。 load 返回的类型虽然在运行时是 Object,但在编译时肯定会在语法上进行检查。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多