案例对象和对象之间的区别

时间:2011-03-11 08:45:58

标签: scala

scala中的case对象和对象之间有什么区别吗?

7 个答案:

答案 0 :(得分:133)

这是一个区别 - 案例对象扩展了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$

答案 1 :(得分:105)

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

  1. 模式匹配支持
  2. equalshashCode
  3. 的默认实施
  4. 序列化的默认实现
  5. toString
  6. 的更漂亮的默认实现
  7. scala.Product自动继承的少量功能。
  8. 模式匹配,equals和hashCode对于单身人士来说并不重要(除非你做了一些真正退化的事情),所以你几乎只是获得序列化,一个很好的toString,以及你可能赢得的一些方法'永远使用。

答案 2 :(得分:32)

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:Serializable = foocase

toString difference:

scala> foo
  

res2:foo.type = foo $ @ 7bf0bac8

scala> foocase
  

res3:foocase.type = foocase

答案 3 :(得分:2)

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

答案 4 :(得分:2)

一个巨大的死灵,但这是谷歌官方教程之外的该问题的最高结果,它一如既往地对细节含糊不清。这是一些裸露的物体:

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(其中将包含静态转发器方法(如果对象定义了任何实例)和实际的单例实例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$() {
   }
}

编译器不仅限于简单地创建'instance'(非静态)类Serializable,还添加了writeReplace方法。 writeReplacewriteObject / 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始终是其领域的产物。这里主要的实际区别是我们免费获得hashCodetoStringcanEqual方法。 Product0仅在您决定将其与非单例对象的toString实例进行比较时才有意义,hashCode使我们免于实现单个简单方法,当案例对象为用作枚举常量,未实现任何行为。最后,您可能会怀疑,equals返回一个常量,因此对于所有VM实例都是相同的。如果序列化了一些有缺陷的哈希映射实现,这一点很重要,但是标准的Java和Scala哈希映射都会明智地在反序列化上重新哈希所有内容,因此这无关紧要。请注意,equals没有被覆盖,因此它仍然是引用相等,并且安全漏洞仍然存在。这里有一个很大的警告:如果案例对象从toString以外的某些超类型继承Object / toString,则不会生成相应的方法,而是使用继承的定义。

TL; DR:实践中唯一重要的区别是unapply返回对象的不合格名称。

但是,我必须在这里声明一下:我不能保证编译器除了字节码中实际包含的内容以外,不特别处理大小写对象。当patterm匹配案例类时,除了执行var adminEntries = Object.keys(environment.entry).filter(function(e) { return e !== 'app' }); environment.plugins.append( 'CommonsChunkVendor', new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', chunks: adminEntries, minChunks: (module) => { // this assumes your vendor imports exist in the node_modules directory return module.context && module.context.indexOf('node_modules') !== -1 } }) ) 之外,当然也可以这样做。

答案 5 :(得分:0)

case classclass类似,当没有任何字段代表其他状态信息时,我们只使用case object而不是case class

答案 6 :(得分:0)

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

案例对象将不包含以下对象:

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