【问题标题】:Difference between case object and object案例对象和对象之间的区别
【发布时间】:2011-07-13 08:20:43
【问题描述】:

case对象和scala中的对象有区别吗?

【问题讨论】:

  • 他有一个观点 - 不必为了能够在其上进行模式匹配而拥有一个 case 对象。我认为这在上一个问题中没有得到解决......
  • 我认为模式匹配行为会有所不同,但案例对象和普通对象在模式匹配 AFAIK 中的行为方式相同。很难找到有关案例对象的任何信息,所以我期待有人启发我们。
  • 没有必要使用case进行模式匹配,它只是糖。自己实现unapply 就可以了。
  • 接受的答案只是没有回答问题,正如上面的 cmets 中所讨论的那样。为时已晚,无法有所作为,但应该注意。
  • 编辑已接受的答案还为时不晚。修改将被审核,如果相关,将被接受。

标签: scala


【解决方案1】:

这里有一个区别 - case 对象扩展了 Serializable 特征,因此它们可以被序列化。常规对象默认不能:

scala> object A
defined module A

scala> case object B
defined module B

scala> import java.io._
import java.io._    

scala> val bos = new ByteArrayOutputStream                                            
bos: java.io.ByteArrayOutputStream =  

scala> val oos = new ObjectOutputStream(bos)                                          
oos: java.io.ObjectOutputStream = java.io.ObjectOutputStream@e7da60                   

scala> oos.writeObject(B)

scala> oos.writeObject(A)
java.io.NotSerializableException: A$

【讨论】:

  • 我认为case对象可以序列化是与常规对象最大的区别,尤其是actor之间的网络通信
  • 添加 extends Serializable 应该做同样的伎俩。
【解决方案2】:

案例类与常规类的不同之处在于:

  1. 模式匹配支持
  2. equals 和 hashCode 的默认实现
  3. 序列化的默认实现
  4. toString 的更漂亮的默认实现,以及
  5. 他们从scala.Product 自动继承的少量功能。

模式匹配、equals 和 hashCode 对于单例来说并不重要(除非你做了一些真正退化的事情),所以你几乎只是得到序列化,一个很好的toString,还有一些你可能永远不会的方法使用。

【讨论】:

  • 这个答案的第 3 点和第 4 点是案例对象和对象之间的正确区别。第 1 点和第 2 点对于单例对象无关紧要。并且单例对象总是具有 arity 0 的产品,所以第 5 点也无关紧要。
  • 这篇文章延续了object 与单例相同的神话。它不是。相反,它正是它所说的,一个对象,即一个声明和实例化。如果在包范围内定义,这会将object 限制为单个实例,这实际上使其成为单例,但前提是在该范围内定义。如果在类中定义,您可以拥有与类本身一样多的实例(它是延迟实例化的,因此不一定是 1-1)。并且这些内部对象可以很好地用作哈希键,使默认的 equals/hashCode 非常明智。
  • 问题是关于case object 不是类,为什么这是正确答案?
  • 这没有回答问题。这个答案涉及case class 和class 之间的区别。问题是关于case object 和object 之间的区别。
  • @C4stor 答案并没有这么说。对象不是类。鉴于案例类在幕后做了相当多的魔术,考虑到 Scala 的各种边缘案例和复杂性,没有理由简单地假设标准 Scala 对象和案例对象之间的唯一区别是由我们所知道的差异来解释的标准类和案例类之间。这个答案甚至没有解决问题的措辞。
【解决方案3】:
scala> object foo

定义的对象 foo

scala> case object foocase

定义对象 foocase

序列化区别:

scala> foo.asInstanceOf[Serializable]

java.lang.ClassCastException: foo$ 不能转换为 scala.Serializable
... 43 省略

scala> foocase.asInstanceOf[Serializable]

res1: 可序列化 = foocase

toString 的区别:

scala> foo

res2: foo.type = foo$@7bf0bac8

scala> foocase

res3: foocase.type = foocase

【讨论】:

    【解决方案4】:

    与case class 和class 类似,我们只是在没有任何表示附加状态信息的字段时使用case object 而不是case class。

    【讨论】:

      【解决方案5】:

      case 对象隐含地带有方法 toString、equals 和 hashCode 的实现,但简单对象没有。 case 对象可以序列化,而简单对象则不能,这使得 case 对象在 Akka-Remote 中作为消息非常有用。 在 object 关键字之前添加 case 关键字使对象可序列化。

      【讨论】:

        【解决方案6】:

        一个巨大的死灵,但它是谷歌官方教程之外的这个问题的最高结果,与往常一样,它对细节非常模糊。以下是一些简单的对象:

        object StandardObject
        
        object SerializableObject extends Serializable
        
        case object CaseObject
        

        现在,让我们在已编译的 .class 文件上使用 IntelliJ '将 Scala 反编译为 Java' 的非常有用的功能:

        //decompiled from StandardObject$.class
        public final class StandardObject$ {
           public static final StandardObject$ MODULE$ = new StandardObject$();
        
           private StandardObject$() {
           }
        }
        
        //decompiled from StandardObject.class
        import scala.reflect.ScalaSignature;
        
        @ScalaSignature(<byte array string elided>)
        
        public final class StandardObject {
        }
        

        如您所见,一个非常简单的单例模式,除了这个问题范围之外的原因,两个类被生成:静态StandardObject(如果object define any) 和实际的单例实例StandardObject$,其中代码中定义的所有方法最终都作为实例方法。实现 Serializable 后事情会变得更有趣:

        //decompiled from SerializableObject.class
        import scala.reflect.ScalaSignature;
        
        @ScalaSignature(<byte array string elided>)
        public final class SerializableObject {
        }
        
                //decompiled from SerializableObject$.class
        import java.io.Serializable;
        import scala.runtime.ModuleSerializationProxy;
        
        public final class SerializableObject$ implements Serializable {
           public static final SerializableObject$ MODULE$ = new SerializableObject$();
        
           private Object writeReplace() {
              return new ModuleSerializationProxy(SerializableObject$.class);
           }
        
           private SerializableObject$() {
           }
        }
        

        编译器并不局限于简单地创建“实例”(非静态)类Serializable,它添加了一个writeReplace 方法。 writeReplace 是writeObject/readObject 的替代品;它的作用是在序列化具有此方法的Serializable 类时序列化不同的对象。然后在反序列化时,该代理对象的 readResolve 方法在反序列化后被调用。这里,ModuleSerializableProxy 实例被序列化为带有Class[SerializableObject] 的字段,因此它知道需要解析哪个对象。该类的 readResolve 方法只返回 SerializableObject - 因为它是一个带有无参数构造函数的单例,scala object 在不同的 VM 实例和不同的运行之间总是在结构上等于自身,并且通过这种方式,属性每保留一个 VM 实例,只会创建该类的一个实例。需要注意的是这里有一个安全漏洞:没有readObject 方法被添加到SerializableObject$,这意味着攻击者可以恶意准备一个二进制文件,它与SerializableObject$ 的标准Java 序列化格式和一个单独的实例相匹配。将创建“单例”。

        现在,让我们转到case object:

        //decompiled from CaseObject.class
        import scala.collection.Iterator;
        import scala.reflect.ScalaSignature;
        
        @ScalaSignature(<byte array string elided>)
        public final class CaseObject {
           public static String toString() {
              return CaseObject$.MODULE$.toString();
           }
        
           public static int hashCode() {
              return CaseObject$.MODULE$.hashCode();
           }
        
           public static boolean canEqual(final Object x$1) {
              return CaseObject$.MODULE$.canEqual(var0);
           }
        
           public static Iterator productIterator() {
              return CaseObject$.MODULE$.productIterator();
           }
        
           public static Object productElement(final int x$1) {
              return CaseObject$.MODULE$.productElement(var0);
           }
        
           public static int productArity() {
              return CaseObject$.MODULE$.productArity();
           }
        
           public static String productPrefix() {
              return CaseObject$.MODULE$.productPrefix();
           }
        
           public static Iterator productElementNames() {
              return CaseObject$.MODULE$.productElementNames();
           }
        
           public static String productElementName(final int n) {
              return CaseObject$.MODULE$.productElementName(var0);
           }
        }
        
                //decompiled from CaseObject$.class
        import java.io.Serializable;
        import scala.Product;
        import scala.collection.Iterator;
        import scala.runtime.ModuleSerializationProxy;
        import scala.runtime.Statics;
        import scala.runtime.ScalaRunTime.;
        
        public final class CaseObject$ implements Product, Serializable {
           public static final CaseObject$ MODULE$ = new CaseObject$();
        
           static {
              Product.$init$(MODULE$);
           }
        
           public String productElementName(final int n) {
              return Product.productElementName$(this, n);
           }
        
           public Iterator productElementNames() {
              return Product.productElementNames$(this);
           }
        
           public String productPrefix() {
              return "CaseObject";
           }
        
           public int productArity() {
              return 0;
           }
        
           public Object productElement(final int x$1) {
              Object var2 = Statics.ioobe(x$1);
              return var2;
           }
        
           public Iterator productIterator() {
              return .MODULE$.typedProductIterator(this);
           }
        
           public boolean canEqual(final Object x$1) {
              return x$1 instanceof CaseObject$;
           }
        
           public int hashCode() {
              return 847823535;
           }
        
           public String toString() {
              return "CaseObject";
           }
        
           private Object writeReplace() {
              return new ModuleSerializationProxy(CaseObject$.class);
           }
        
           private CaseObject$() {
           }
        }
        

        还有很多事情要做,因为CaseObject$ 现在也实现了Product0,带有它的迭代器和访问器方法。我不知道此功能的用例,它可能是为了与case class 保持一致,这始终是其领域的产物。这里主要的实际区别是我们免费获得canEqual、hashCode 和toString 方法。 canEqual 仅在您决定将其与不是单例对象的 Product0 实例进行比较时才相关,toString 使我们免于实现单个简单方法,这在将 case 对象用作没有任何枚举常量的情况下很有用行为实施。最后,正如人们可能怀疑的那样,hashCode 返回一个常量,因此对于所有 VM 实例都是相同的。如果一个人序列化一些有缺陷的哈希映射实现,这很重要,但是标准的 java 和 scala 哈希映射都明智地在反序列化时重新哈希所有内容,所以这无关紧要。请注意,equals 没有被覆盖,所以它仍然是引用相等,并且安全漏洞仍然存在。这里有个很大的警告:如果一个 case 对象从 Object 以外的某个超类型继承 equals/toString,则不会生成相应的方法,而是使用继承的定义。

        TL;DR:在实践中唯一重要的区别是 toString 返回对象的非限定名称。

        不过,我必须在此声明:除了字节码中的实际内容之外,我不能保证编译器不会专门处理 case 对象。除了实现unapply之外,在模式匹配案例类时肯定会这样做。

        【讨论】:

          【解决方案7】:

          我们以前知道对象和“案例类”。但是“案例对象”是两者的混合体,即它是类似于对象的单例,并且在案例类中具有很多样板。唯一的区别是样板文件是针对对象而不是类完成的。

          case 对象不会附带以下对象:

          应用、取消应用方法。 这里没有复制方法,因为这是一个单例。 没有结构相等比较的方法。 也没有构造函数。

          【讨论】:

            猜你喜欢
            • 2015-11-11
            • 2014-04-08
            • 2010-11-15
            • 2012-01-12
            • 1970-01-01
            • 2011-09-25
            • 2013-03-29
            • 1970-01-01
            相关资源
            最近更新 更多