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

发布于 2024-10-21 13:38:58 字数 33 浏览 2 评论 0原文

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

Is there any difference between case object and object in scala?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

兔小萌 2024-10-28 13:38:58

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

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$

Here's one difference - case objects extend the Serializable trait, so they can be serialized. Regular objects cannot by default:

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$
南薇 2024-10-28 13:38:58

Case 类与常规类的不同之处在于它们具有:

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

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

Case classes differ from regular classes in that they get:

  1. pattern matching support
  2. default implementations of equals and hashCode
  3. default implementations of serialization
  4. a prettier default implementation of toString, and
  5. the small amount of functionality that they get from automatically inheriting from scala.Product.

Pattern matching, equals and hashCode don't matter much for singletons (unless you do something really degenerate), so you're pretty much just getting serialization, a nice toString, and some methods you probably won't ever use.

山川志 2024-10-28 13:38:58
scala> object foo

定义对象foo

scala> case object foocase

定义对象foocase

序列化差异:

scala> foo.asInstanceOf[Serializable]

java.lang.ClassCastException: foo$ 无法转换为 scala.Serialized
... 43 已删除

scala> foocase.asInstanceOf[Serializable]

res1:可序列化= foocase

toString 区别:

scala> foo

res2: foo.type = foo$@7bf0bac8

scala> foocase

res3: foocase.type = foocase

scala> object foo

defined object foo

scala> case object foocase

defined object foocase

Serialization difference:

scala> foo.asInstanceOf[Serializable]

java.lang.ClassCastException: foo$ cannot be cast to scala.Serializable
... 43 elided

scala> foocase.asInstanceOf[Serializable]

res1: Serializable = foocase

toString difference:

scala> foo

res2: foo.type = foo$@7bf0bac8

scala> foocase

res3: foocase.type = foocase

多情癖 2024-10-28 13:38:58

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

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$,其中代码中定义的所有方法最终都作为实例方法。当您实现 Serialized 时,事情会变得更有趣:

//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$() {
   }
}

编译器不会将自己限制为简单地创建“实例”(非静态)类 Serialized,它会添加一个 >writeReplace 方法。 writeReplacewriteObject/readObject 的替代方案;它的作用是,当具有此方法的 Serialized 类被序列化时,它会序列化不同的对象。然后,在反序列化时,该代理对象的 readResolve 方法在反序列化后就会被调用。这里,ModuleSerializedProxy 实例使用携带 Class[SerializedObject] 的字段进行序列化,因此它知道需要解析什么对象。该类的 readResolve 方法仅返回 SerializedObject - 因为它是具有无参数构造函数的单例,所以 scala object 在结构上始终等于其自身不同的VM实例和不同的运行,通过这种方式,每个VM实例仅创建该类的单个实例的属性被保留。值得注意的是,这里存在一个安全漏洞:SerializedObject$ 中没有添加 readObject 方法,这意味着攻击者可以恶意准备与标准 Java 序列化相匹配的二进制文件SerializedObject$ 的格式和“单例”的单独实例将被创建。

现在,让我们转向 case 对象

//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 方法。仅当您决定将它与不是单例对象的 Product0 实例进行比较时,canEqual 才相关,toString 使我们免于实现单个简单对象方法,当 case 对象用作枚举常量而不实现任何行为时,该方法非常有用。最后,正如人们可能怀疑的那样,hashCode 返回一个常量,因此它对于所有 VM 实例都是相同的。如果序列化一些有缺陷的哈希映射实现,这一点很重要,但标准 java 和 scala 哈希映射都会明智地在反序列化时重新哈希所有内容,所以这应该不重要。请注意,equals 未被覆盖,因此它仍然是引用相等,并且安全漏洞仍然存在。这里有一个巨大的警告:如果一个 case 对象从 Object 之外的某个超类型继承 equals/toString,则不会生成相应的方法,并且而是使用继承的定义。

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

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

A huge necro, but it is the highest result for this question in Google outside official tutorial which, as always, is pretty vague about the details. Here are some bare bones objects:

object StandardObject

object SerializableObject extends Serializable

case object CaseObject

Now, lets use the very useful feature of IntelliJ 'decompile Scala to Java' on compiled .class files:

//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 {
}

As you can see, a pretty straightforward singleton pattern, except for reasons outside the scope of this question, two classes are generated: the static StandardObject (which would contain static forwarder methods should the object define any) and the actual singleton instance StandardObject$, where all methods defined in the code end up as instance methods. Things get more intresting when you implement 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$() {
   }
}

The compiler doesn't limit itself to simply making the 'instance' (non-static) class Serializable, it adds a writeReplace method. writeReplace is an alternative to writeObject/readObject; what it does, it serializes a different object whenether the Serializable class having this method is being serialized. On deserializention then, that proxy object's readResolve method is invoked once it is deserialized. Here, a ModuleSerializableProxy instance is serialized with a field carrying the Class[SerializableObject], so it knows what object needs to be resolved. The readResolve method of that class simply returns SerializableObject - as it is a singleton with a parameterless constructor, scala object is always structurally equal to itself between diffrent VM instances and different runs and, in this way, the property that only a single instance of that class is created per one VM instance is preserved. A thing of note is that there is a security hole here: no readObject method is added to SerializableObject$, meaning an attacker can maliciously prepare a binary file which matches standard Java serialization format for SerializableObject$ and a separate instance of the 'singleton' will be created.

Now, lets move to the 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$() {
   }
}

A lot more is going on, as CaseObject$ now implements also Product0, with its iterator and accessor methods. I am unaware of a use case for this feature, it is probably done for consistency with case class which is always a product of its fields. The main practical difference here is that we get canEqual, hashCode and toString methods for free. canEqual is relevant only if you decide to compare it with a Product0 instance which is not a singleton object, toString saves us from implementing a single simple method, which is useful when case objects are used as enumeration constants without any behaviour implemented. Finally, as one might suspect, hashCode returns a constant, so it is the same for all VM instances. This matters if one serializes some flawed hash map implementation, but both standard java and scala hash maps wisely rehash all contents on deserialization, so it shouldn't matter. Note that equals is not overriden, so it is still reference equality, and that the security hole is still there. A huge caveat here: if a case object inherit equals/toString from some supertype other than Object, the corresponding methods are not generated, and the inherited definitions are used instead.

TL;DR: the only difference that matters in practice is the toString returning the unqualified name of the object.

I must make a disclamer here, though: I cannot guarantee that the compiler doesn't treat case objects specially in addition to what is actually in the bytecode. It certainly does so when patterm matching case classes, aside from them implementing unapply.

倾其所爱 2024-10-28 13:38:58

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

It's similar with case class and class ,we just use case object instead of case class when there isn't any fields representing additional state information.

指尖上的星空 2024-10-28 13:38:58

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

case objects implicitly come with implementations of methods toString, equals, and hashCode, but simple objects don't.
case objects can be serialized while simple objects cannot, which makes case objects very useful as messages with Akka-Remote.
Adding the case keyword before object keyword makes the object serializable.

烏雲後面有陽光 2024-10-28 13:38:58

我们之前知道对象和“案例类”。但“案例对象”是两者的混合,即它是类似于对象的单例,并且具有大量样板,如案例类中一样。唯一的区别是样板是针对对象而不是类完成的。

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

Apply、Un-apply 方法。
这里没有复制方法,因为这是一个单例。
没有结构平等比较的方法。
也没有构造函数。

We know objects and "case class" before. But "case object" is a mix of both i.e it is a singleton similar to an object and with a lot of boilerplate as in a case class. The only difference is that the boilerplate is done for an object instead of a class.

case objects won't come with the below ones:

Apply, Un-apply methods.
here are no copy methods since this is a singleton.
No method for structural equality comparison.
No constructor as well.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文