Scalaのケースクラスとクラスの違いは何ですか?


439

Googleで検索して、a case classとaの違いを見つけましたclass。クラスでパターンマッチングを実行する場合は、ケースクラスを使用すると誰もが述べています。それ以外の場合は、クラスを使用し、equalsやハッシュコードのオーバーライドなどの追加の特典について言及します。しかし、これらがクラスの代わりにケースクラスを使用するべき唯一の理由ですか?

Scalaのこの機能には、いくつかの非常に重要な理由があると思います。説明とは何ですか、またはScalaケースクラスの詳細を学ぶためのリソースはありますか?

回答:


393

Caseクラスは、コンストラクタの引数にのみ依存するプレーンで不変のデータ保持オブジェクトと見なすことができます

この機能概念により、

  • コンパクトな初期化構文を使用する(Node(1, Leaf(2), None))
  • パターンマッチングを使用して分解する
  • 暗黙的に定義された等価比較がある

継承と組み合わせて、代数的データ型を模倣するためにケースクラスが使用されます。

オブジェクトが内部でステートフルな計算を実行するか、他の種類の複雑な動作を示す場合、オブジェクトは通常のクラスである必要があります。


11
@テジャ:ある意味で。ADTは、パラメーター化された列挙型であり、非常に強力でタイプセーフです。
ダリオ

8
シールされたケースクラスは、代数的データ型を模倣するために使用されます。それ以外の場合、サブクラスの数は制限されません。
Thomas Jung、

6
@Thomas:正しく読み上げられ、封印された抽象クラスから派生したケースクラスは閉じた代数データ型を模倣しますが、ADTは他の方法で開いています。
ダリオ

2
@Dario ...そして、タイプはそれ以外の場合はオープンで、ADTではありません。:-)
Thomas Jung、

1
@トーマス:うん、それは単なる存在です;)
ダリオ

165

技術的には、クラスとケースクラスの間に違いはありません。たとえ、コンパイラがケースクラスを使用するときにいくつかのことを最適化しても、です。ただし、ケースクラスは、代数的データ型を実装する特定のパターンのボイラープレートを廃止するために使用されます

そのようなタイプの非常に単純な例はツリーです。たとえば、バイナリツリーは次のように実装できます。

sealed abstract class Tree
case class Node(left: Tree, right: Tree) extends Tree
case class Leaf[A](value: A) extends Tree
case object EmptyLeaf extends Tree

これにより、次のことが可能になります。

// DSL-like assignment:
val treeA = Node(EmptyLeaf, Leaf(5))
val treeB = Node(Node(Leaf(2), Leaf(3)), Leaf(5))

// On Scala 2.8, modification through cloning:
val treeC = treeA.copy(left = treeB.left)

// Pretty printing:
println("Tree A: "+treeA)
println("Tree B: "+treeB)
println("Tree C: "+treeC)

// Comparison:
println("Tree A == Tree B: %s" format (treeA == treeB).toString)
println("Tree B == Tree C: %s" format (treeB == treeC).toString)

// Pattern matching:
treeA match {
  case Node(EmptyLeaf, right) => println("Can be reduced to "+right)
  case Node(left, EmptyLeaf) => println("Can be reduced to "+left)
  case _ => println(treeA+" cannot be reduced")
}

// Pattern matches can be safely done, because the compiler warns about
// non-exaustive matches:
def checkTree(t: Tree) = t match {
  case Node(EmptyLeaf, Node(left, right)) =>
  // case Node(EmptyLeaf, Leaf(el)) =>
  case Node(Node(left, right), EmptyLeaf) =>
  case Node(Leaf(el), EmptyLeaf) =>
  case Node(Node(l1, r1), Node(l2, r2)) =>
  case Node(Leaf(e1), Leaf(e2)) =>
  case Node(Node(left, right), Leaf(el)) =>
  case Node(Leaf(el), Node(left, right)) =>
  // case Node(EmptyLeaf, EmptyLeaf) =>
  case Leaf(el) =>
  case EmptyLeaf =>
}

ツリーは、同じパターンで(パターンマッチによって)構築および分解されます。これは、正確にそれらが印刷される方法(スペースを除く)でもあります。

また、有効で安定したhashCodeを持っているため、ハッシュマップまたはセットで使用することもできます。


71
  • ケースクラスはパターンマッチングが可能
  • Caseクラスは自動的にハッシュコードと等号を定義します
  • Caseクラスは、コンストラクター引数のゲッターメソッドを自動的に定義します。

(あなたはすでに最後のものを除いてすべてを述べました)。

これらが通常のクラスとの唯一の違いです。


13
コンストラクター引数で「var」が指定されていない限り、ケースクラスのセッターは生成されません。その場合、通常のクラスと同じゲッター/セッターが生成されます。
Mitch Blevins、2010

1
@ミッチ:本当、私の悪い。今修正されました。
sepp2k、2010

あなたは2つの違いを省きました、私の答えを見てください。
シェルビームーアIII

@MitchBlevins、通常のクラス、getter / setterの生成が常にあるとは限りません
シェルビームーアIII

ケースクラスは、適用されないメソッドを定義するため、パターンマッチングを行うことができます。
幸せな拷問2016年

30

ケースクラスもインスタンスでProductあり、したがってこれらのメソッドを継承するとは誰も言及していません。

def productElement(n: Int): Any
def productArity: Int
def productIterator: Iterator[Any]

ここで、productArityはクラスパラメータの数をproductElement(i)返し、i 番目のパラメータを返し、productIteratorそれらの反復を許可します。


2
ただし、Product1、Product2などのインスタンスではありません。
Jean-Philippe Pellet

27

ケースクラスにvalコンストラクターパラメーターがあることについては誰も言及していませんが、これは通常のクラスのデフォルトでもあります(これはScalaの設計に矛盾があると思います)。ダリオはそれらが「不変」であると彼が指摘したところでそのようなことを暗示しました。

varcaseクラスの各コンストラクタ引数を前に付けることで、デフォルトをオーバーライドできることに注意してください。ただし、ケースクラスを変更可能にすると、それらのメソッドequalshashCodeメソッドが時変になります。[1]

sepp2kは、ケースクラスが自動的に生成しequalshashCodeメソッドを生成することをすでに説明しました。

また、caseクラスobjectは、クラスapplyunapplyメソッドを含むクラスと同じ名前のコンパニオンを自動的に作成することについて言及していません。このapplyメソッドを使用すると、前にを付けずにインスタンスを作成できますnewunapply抽出方法は、他のものが挙げことパターンマッチングを可能にします。

また、コンパイラーは速度を最適化しますmatch- caseケースクラスのパターンマッチング[2]。

[1] ケースクラスはクールです

[2] Case Classes and Extractors、pg 15


12

Scalaのケースクラスコンストラクトは、ボイラープレートを削除するための便利な手段としても見なされます。

ケースクラスを構築するとき、Scalaは次のことを提供します。

  • クラスとそのコンパニオンオブジェクトを作成します
  • そのコンパニオンオブジェクトapplyは、ファクトリメソッドとして使用できるメソッドを実装します。新しいキーワードを使用する必要がないという構文上の利点があります。

クラスは不変であるため、クラスの変数(またはプロパティ)であるアクセサーを取得しますが、ミューテーター(変数を変更する機能)は取得しません。コンストラクターのパラメーターは、パブリックの読み取り専用フィールドとして自動的に使用できます。Java Beanコンストラクトよりもはるかに使いやすくなっています。

  • デフォルトでは、、、およびメソッドも取得しhashCode、メソッドはオブジェクトを構造的に比較します。この方法は、(いくつかのフィールドは、メソッドに提供される新しい値を持つ)オブジェクトのクローンを作成することができるように生成されます。equalstoStringequalscopy

前述の最大の利点は、ケースクラスでパターンマッチを実行できることです。これはunapply、ケースクラスを分解してフィールドを抽出できるメソッドを取得するためです。


本質的に、ケースクラス(または、クラスが引数を取らない場合はケースオブジェクト)を作成するときにScalaから取得するものは、ファクトリーおよびエクストラクターとしての役割を果たすシングルトンオブジェクトです。


なぜ不変オブジェクトのコピーが必要なのですか?
パウロEbermann

@PaŭloEbermann copyメソッドがフィールドを変更できるため:val x = y.copy(foo="newValue")
Thilo

8

人々がすでに言ったことを除いて、との間にいくつかのより基本的な違いがclassありますcase class

1. Case Class明示的なは必要ありませんがnew、クラスはnew

val classInst = new MyClass(...)  // For classes
val classInst = MyClass(..)       // For case class

2.デフォルトでは、コンストラクタのパラメータはでプライベートですがclass、パブリックではcase class

// For class
class MyClass(x:Int) { }
val classInst = new MyClass(10)

classInst.x   // FAILURE : can't access

// For caseClass
case class MyClass(x:Int) { }
val classInst = MyClass(10)

classInst.x   // SUCCESS

3. case class値で比較する

// case Class
class MyClass(x:Int) { }

val classInst = new MyClass(10)
val classInst2 = new MyClass(10)

classInst == classInst2 // FALSE

// For Case Class
case class MyClass(x:Int) { }

val classInst = MyClass(10)
val classInst2 = MyClass(10)

classInst == classInst2 // TRUE

6

Scalaのドキュメントによると:

ケースクラスは、通常のクラスです。

  • デフォルトでは不変
  • パターンマッチングにより分解可能
  • 参照ではなく構造的等価性で比較
  • インスタンス化して操作するための簡潔な

caseキーワードのもう1つの機能は、Javaの使い慣れたtoString、equals、hashCodeメソッドなど、いくつかのメソッドをコンパイラーが自動的に生成することです。


5

クラス:

scala> class Animal(name:String)
defined class Animal

scala> val an1 = new Animal("Padddington")
an1: Animal = Animal@748860cc

scala> an1.name
<console>:14: error: value name is not a member of Animal
       an1.name
           ^

しかし、同じコードを使用するが、ケースクラスを使用する場合:

scala> case class Animal(name:String)
defined class Animal

scala> val an2 = new Animal("Paddington")
an2: Animal = Animal(Paddington)

scala> an2.name
res12: String = Paddington


scala> an2 == Animal("fred")
res14: Boolean = false

scala> an2 == Animal("Paddington")
res15: Boolean = true

個人クラス:

scala> case class Person(first:String,last:String,age:Int)
defined class Person

scala> val harry = new Person("Harry","Potter",30)
harry: Person = Person(Harry,Potter,30)

scala> harry
res16: Person = Person(Harry,Potter,30)
scala> harry.first = "Saily"
<console>:14: error: reassignment to val
       harry.first = "Saily"
                   ^
scala>val saily =  harry.copy(first="Saily")
res17: Person = Person(Saily,Potter,30)

scala> harry.copy(age = harry.age+1)
res18: Person = Person(Harry,Potter,31)

パターンマッチング:

scala> harry match {
     | case Person("Harry",_,age) => println(age)
     | case _ => println("no match")
     | }
30

scala> res17 match {
     | case Person("Harry",_,age) => println(age)
     | case _ => println("no match")
     | }
no match

オブジェクト:シングルトン:

scala> case class Person(first :String,last:String,age:Int)
defined class Person

scala> object Fred extends Person("Fred","Jones",22)
defined object Fred

5

ケースクラスとは何かを最終的に理解するには:

次のケースクラス定義を想定します。

case class Foo(foo:String, bar: Int)

ターミナルで以下を実行します。

$ scalac -print src/main/scala/Foo.scala

Scala 2.12.8は以下を出力します:

...
case class Foo extends Object with Product with Serializable {

  <caseaccessor> <paramaccessor> private[this] val foo: String = _;

  <stable> <caseaccessor> <accessor> <paramaccessor> def foo(): String = Foo.this.foo;

  <caseaccessor> <paramaccessor> private[this] val bar: Int = _;

  <stable> <caseaccessor> <accessor> <paramaccessor> def bar(): Int = Foo.this.bar;

  <synthetic> def copy(foo: String, bar: Int): Foo = new Foo(foo, bar);

  <synthetic> def copy$default$1(): String = Foo.this.foo();

  <synthetic> def copy$default$2(): Int = Foo.this.bar();

  override <synthetic> def productPrefix(): String = "Foo";

  <synthetic> def productArity(): Int = 2;

  <synthetic> def productElement(x$1: Int): Object = {
    case <synthetic> val x1: Int = x$1;
        (x1: Int) match {
            case 0 => Foo.this.foo()
            case 1 => scala.Int.box(Foo.this.bar())
            case _ => throw new IndexOutOfBoundsException(scala.Int.box(x$1).toString())
        }
  };

  override <synthetic> def productIterator(): Iterator = scala.runtime.ScalaRunTime.typedProductIterator(Foo.this);

  <synthetic> def canEqual(x$1: Object): Boolean = x$1.$isInstanceOf[Foo]();

  override <synthetic> def hashCode(): Int = {
     <synthetic> var acc: Int = -889275714;
     acc = scala.runtime.Statics.mix(acc, scala.runtime.Statics.anyHash(Foo.this.foo()));
     acc = scala.runtime.Statics.mix(acc, Foo.this.bar());
     scala.runtime.Statics.finalizeHash(acc, 2)
  };

  override <synthetic> def toString(): String = scala.runtime.ScalaRunTime._toString(Foo.this);

  override <synthetic> def equals(x$1: Object): Boolean = Foo.this.eq(x$1).||({
      case <synthetic> val x1: Object = x$1;
        case5(){
          if (x1.$isInstanceOf[Foo]())
            matchEnd4(true)
          else
            case6()
        };
        case6(){
          matchEnd4(false)
        };
        matchEnd4(x: Boolean){
          x
        }
    }.&&({
      <synthetic> val Foo$1: Foo = x$1.$asInstanceOf[Foo]();
      Foo.this.foo().==(Foo$1.foo()).&&(Foo.this.bar().==(Foo$1.bar())).&&(Foo$1.canEqual(Foo.this))
  }));

  def <init>(foo: String, bar: Int): Foo = {
    Foo.this.foo = foo;
    Foo.this.bar = bar;
    Foo.super.<init>();
    Foo.super./*Product*/$init$();
    ()
  }
};

<synthetic> object Foo extends scala.runtime.AbstractFunction2 with Serializable {

  final override <synthetic> def toString(): String = "Foo";

  case <synthetic> def apply(foo: String, bar: Int): Foo = new Foo(foo, bar);

  case <synthetic> def unapply(x$0: Foo): Option =
     if (x$0.==(null))
        scala.None
     else
        new Some(new Tuple2(x$0.foo(), scala.Int.box(x$0.bar())));

  <synthetic> private def readResolve(): Object = Foo;

  case <synthetic> <bridge> <artifact> def apply(v1: Object, v2: Object): Object = Foo.this.apply(v1.$asInstanceOf[String](), scala.Int.unbox(v2));

  def <init>(): Foo.type = {
    Foo.super.<init>();
    ()
  }
}
...

ご覧のとおり、Scalaコンパイラは通常のクラスFooとコンパニオンオブジェクトを生成しますFoo

コンパイルされたクラスを見て、取得した内容についてコメントしましょう。

  • Fooクラスの内部状態、不変:
val foo: String
val bar: Int
  • ゲッター:
def foo(): String
def bar(): Int
  • コピー方法:
def copy(foo: String, bar: Int): Foo
def copy$default$1(): String
def copy$default$2(): Int
  • scala.Productトレイトの実装:
override def productPrefix(): String
def productArity(): Int
def productElement(x$1: Int): Object
override def productIterator(): Iterator
  • 実装scala.Equalsによって平等のための同等のメイクケースクラスのインスタンスのための形質を==
def canEqual(x$1: Object): Boolean
override def equals(x$1: Object): Boolean
  • java.lang.Object.hashCodeequals-hashcode規約に従うためのオーバーライド:
override <synthetic> def hashCode(): Int
  • オーバーライドjava.lang.Object.toString
override def toString(): String
  • newキーワードによるインスタンス化のコンストラクタ:
def <init>(foo: String, bar: Int): Foo 

Object Foo:- キーワードapplyなしでインスタンス化する方法new

case <synthetic> def apply(foo: String, bar: Int): Foo = new Foo(foo, bar);
  • unupplyパターンマッチングでケースクラスFooを使用するための抽出メソッド:
case <synthetic> def unapply(x$0: Foo): Option
  • もう1つのインスタンスを生成させないようにするために、オブジェクトを逆シリアル化からシングルトンとして保護するメソッド:
<synthetic> private def readResolve(): Object = Foo;
  • オブジェクトFooはscala.runtime.AbstractFunction2、このようなトリックを実行するために拡張されます。
scala> case class Foo(foo:String, bar: Int)
defined class Foo

scala> Foo.tupled
res1: ((String, Int)) => Foo = scala.Function2$$Lambda$224/1935637221@9ab310b

tupled fromオブジェクトは、2つの要素のタプルを適用して新しいFooを作成する関数を返します。

したがって、caseクラスは構文上の糖衣です。


4

クラスとは異なり、caseクラスはデータを保持するためだけに使用されます。

ケースクラスはデータ中心のアプリケーションに柔軟です。つまり、ケースクラスでデータフィールドを定義し、ビジネスオブジェクトをコンパニオンオブジェクトで定義できます。このようにして、データをビジネスロジックから分離します。

copyメソッドを使用すると、ソースから必要なプロパティの一部またはすべてを継承し、必要に応じてそれらを変更できます。


3

ケースクラスのコンパニオンオブジェクトがtupled次のタイプを持つ定義を持つことについては誰も述べていません。

case class Person(name: String, age: Int)
//Person.tupled is def tupled: ((String, Int)) => Person

私が見つけることができる唯一のユースケースは、タプルからケースクラスを構築する必要がある場合です、例:

val bobAsTuple = ("bob", 14)
val bob = (Person.apply _).tupled(bobAsTuple) //bob: Person = Person(bob,14)

オブジェクトを直接作成することで、タプルなしで同じことができますが、データセットがアリティ20のタプル(20要素のタプル)のリストとして表されている場合は、タプルを使用するのが最適です。


3

ケースクラスを一緒に使用することができるクラスですmatch/case声明。

def isIdentityFun(term: Term): Boolean = term match {
  case Fun(x, Var(y)) if x == y => true
  case _ => false
}

あなたはそれを見る case後に、2番目のパラメーターがVarであるFunクラスのインスタンスが続くます。これは非常に優れた強力な構文ですが、どのクラスのインスタンスでも機能しないため、caseクラスにはいくつかの制限があります。そしてこれらの制限が守られれば、ハッシュコードとイコールを自動的に定義することが可能です。

あいまいなフレーズ「パターンマッチングによる再帰的な分解メカニズム」は、単に「動作する」という意味caseです。(実際、の後に続くインスタンスは、の後に続くインスタンスmatchと比較(照合)されますcase。Scalaは、両方を分解し、それらが構成されているものを再帰的に分解する必要があります。)

ケースクラスが役に立ちますか?代数的データ型に関するWikipediaの記事 2良い古典例、リストやツリーを提供します。代数的データ型のサポート(それらを比較する方法を知ることを含む)は、現代の関数型言語では必須です。

どのようなケースのクラスです役に立たないのですか?一部のオブジェクトには状態があります。このようなコードconnection.setConnectTimeout(connectTimeout)は、ケースクラス用ではありません。

そして今、あなたはScalaのツアーを読むことができます:Case Classes


2

全体として、すべての回答がクラスとケースクラスに関する意味的な説明を提供していると思います。これは非常に関連性がありますが、Scalaのすべての初心者は、ケースクラスを作成したときに何が起こるかを知っている必要があります。私はこれを書きました回答ました。これはケースクラスを簡単に説明しています。

すべてのプログラマーは、事前に作成された関数を使用している場合、比較的少ないコードを作成していることを知っている必要があります。これにより、最適化されたコードを作成するためのパワーを与えることができますが、パワーには大きな責任があります。そのため、ビルド済みの関数を使用する際は十分に注意してください。

20のメソッドが追加されているため、一部の開発者はケースクラスの記述を避けています。これは、クラスファイルを分解することで確認できます。

ケースクラス内のすべてのメソッドを確認する場合は、このリンクを参照してください。


1
  • ケースクラスは、applyメソッドとunapplyメソッドでcompagnonオブジェクトを定義します
  • CaseクラスはSerializableを拡張します
  • ケースクラスは、等しいハッシュコードとコピーメソッドを定義します
  • コンストラクターのすべての属性はval(構文糖)

1

の主な機能の一部をcase classes以下に示します

  1. ケースクラスは不変です。
  2. newキーワードなしでケースクラスをインスタンス化できます。
  3. ケースクラスは値で比較できます

Scalaのドキュメントから抜粋した、ScalaフィドルのサンプルScalaコード。

https://scalafiddle.io/sf/34XEQyE/0

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.