scala中的case对象和对象之间有什么区别吗?
答案 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)
案例类与常规类的不同之处在于:
equals
和hashCode
toString
和scala.Product
自动继承的少量功能。 模式匹配,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
方法。 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
实例进行比较时才有意义,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 class
和class
类似,当没有任何字段代表其他状态信息时,我们只使用case object
而不是case class
。
答案 6 :(得分:0)
我们以前知道对象和“案例类”。但是“案例对象”是两者的混合体,即它是一个与对象相似的单例对象,并且在案例类中具有很多样板。唯一的区别是样板是为对象而不是类完成的。
案例对象将不包含以下对象:
应用,取消应用方法。 这里没有复制方法,因为这是一个单例。 没有用于结构相等比较的方法。 也没有构造函数。