case对象和对象的区别

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

56743 次浏览

这里有一个区别——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类与常规类的不同之处在于:

  1. 模式匹配支持
  2. equalshashCode的默认实现
  3. 序列化的默认实现
  4. toString的一个更漂亮的默认实现,以及
  5. 它们通过自动继承scala.Product而获得的少量功能。

模式匹配、等号和hashCode对于单例来说并不重要(除非你做了一些真正退化的事情),所以你基本上只是得到了序列化、一个漂亮的toString和一些你可能永远不会使用的方法。

scala> object foo

定义对象foo

scala> case object foocase

定义对象foocase

序列化的区别:

scala> foo.asInstanceOf[Serializable]
< p > . lang。foo$不能强制转换到scala。可序列化的< br > ... 43省略< / p >
scala> foocase.asInstanceOf[Serializable]

res1: Serializable = foocase

toString的区别:

scala> foo

它:foo。Type = foo$@7bf0bac8

scala> foocase

res3: foocase。Type = foocase

它与case classclass类似,我们只是在没有任何字段表示额外的状态信息时使用case object而不是case class

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

我们以前知道对象和“case类”。但是“case object”是两者的混合,即它是一个类似于对象的单例,并且在case类中有很多样板。唯一的区别是样板文件是为对象而不是类编写的。

Case对象不会与以下对象一起出现:

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

一个巨大的死灵,但这是最高的结果在谷歌之外的官方教程,一如既往,是相当模糊的细节。下面是一些基本对象:

object StandardObject


object SerializableObject extends Serializable


case object CaseObject

现在,让我们使用IntelliJ在编译的.class文件上“将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$() {
}
}

编译器并不局限于简单地使“实例”(非静态)类Serializable,它添加了一个writeReplace方法。writeReplacewriteObject/readObject的替代;它所做的是,当具有此方法的Serializable类被序列化时,它会序列化writeReplace5。在反序列化时,一旦该代理对象被反序列化,就会调用它的readResolve方法。在这里,ModuleSerializableProxy实例被序列化为带有Class[SerializableObject]的字段,因此它知道需要解析什么对象。该类的readResolve方法只是返回writeReplace0 -因为它是一个具有无参数构造函数的单例,scala writeReplace1在不同的VM实例和不同的运行之间总是在结构上等于它自己,这样,每个VM实例只创建该类的单个实例的属性就被保留了。值得注意的是这里有一个安全漏洞:没有readObject方法被添加到writeReplace3中,这意味着攻击者可以恶意地为writeReplace3准备一个匹配标准Java序列化格式的二进制文件,并将创建一个单独的'singleton'实例。

现在,让我们移动到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保持一致,而case class始终是其字段的产物。这里主要的实际区别是我们免费获得了canEqualhashCodetoString方法。canEqual只在你决定将它与一个不是单例对象的Product0实例进行比较时才有意义,toString使我们不必实现一个简单的方法,这在case对象用作枚举常量而没有实现任何行为时非常有用。最后,正如人们可能会怀疑的那样,hashCode返回一个常量,因此对所有VM实例都是一样的。如果有人序列化了一些有缺陷的哈希映射实现,这很重要,但是标准java和scala哈希映射都明智地在反序列化上重新哈希了所有内容,所以这不重要。注意,Product00没有被覆盖,所以它仍然是引用相等,安全漏洞仍然存在。这里有一个很大的警告:如果case对象从Product03以外的超类型继承了Product00/toString,则不会生成相应的方法,而是使用继承的定义。

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

不过,我必须在这里声明:我不能保证编译器除了字节码中的实际内容外,不会特别处理case对象。当模式匹配case类时,它当然会这样做,除了它们实现unapply