web-dev-qa-db-ja.com

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

case classclassの違いを見つけるためにGoogleで検索しました。誰もがあなたがクラスのパターンマッチングをしたいときは、ユースケースクラスを使用すると述べています。それ以外の場合はクラスを使用し、等号やハッシュコードの上書きなどの追加の特典についても言及します。しかし、これらがクラスではなくケースクラスを使用する必要がある唯一の理由ですか?

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

409
Teja Kantamneni

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

この機能概念は私達にそれを可能にします

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

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

オブジェクトが内部でステートフル計算を実行したり、他の種類の複雑な動作を示す場合、それは普通のクラスであるべきです。

360
Dario

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

そのようなタイプの非常に単純な例は木です。たとえば、二分木は次のように実装できます。

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を持っているので、ハッシュマップやセットでも使うことができます。

154
  • ケースクラスはパターンマッチ可能
  • ケースクラスは自動的にハッシュコードと等しいを定義する
  • ケースクラスはコンストラクタ引数のためのgetterメソッドを自動的に定義します。

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

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

62
sepp2k

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

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

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

25

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

ケースクラスの場合、各コンストラクタの引数の前にvarを付けることで、デフォルトを上書きすることができます。ただし、ケースクラスを変更可能にすると、それらのequalsメソッドおよびhashCodeメソッドが時変になります。

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

また、ケースクラスがobjectメソッドとapplyメソッドを含む、クラスと同じ名前のunapplyというコンパニオンを自動的に作成することに言及した人はいません。 applyメソッドはnewを前に付けずにインスタンスを構築することを可能にします。 unapply抽出メソッドは、他の人が言及したパターンマッチングを有効にします。

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

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

[2] 格クラスと抽出、pg 15

24

Scalaのcaseクラスの構成要素は、定型句を削除するのに便利だと見なすこともできます。

ケースクラスを構築するとき、Scalaはあなたに以下を与えます。

  • それはその仲間オブジェクトと同様にクラスを作成する
  • そのコンパニオンオブジェクトは、ファクトリメソッドとして使用できるapplyメソッドを実装しています。新しいキーワードを使用しなくてもよいという糖衣構文上の利点が得られます。

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

  • また、デフォルトでhashCodeequals、およびtoStringの各メソッドがあり、equalsメソッドはオブジェクトを構造的に比較します。オブジェクトを複製できるようにcopyメソッドが生成されます(一部のフィールドにはメソッドに新しい値が提供されます)。

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


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

9
Faktor 10

classcase classの間には、人々がすでに言ったこととは別に、いくつかの基本的な違いがあります。

1 .Case Classは明示的なnewを必要としませんが、classは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
DeepakKg

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

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

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

caseキーワードのもう1つの機能は、Javaでよく知られているtoString、equals、hashCodeなど、いくつかのメソッドが自動的に生成されることです。

5
user2989087

クラス:

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
           ^

しかし、同じコードを使うがcaseクラスを使うと

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
3
user1668782

ケースクラスとは何かについての究極の理解を得るために:

次のケースクラス定義を仮定しましょう。

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とcompanion-object 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
  • make caseクラスのインスタンスがscala.Equalsと同等であることに匹敵するように==トレイトを実装する:
def canEqual(x$1: Object): Boolean
override def equals(x$1: Object): Boolean
  • equals-hashcode規約に従うためにJava.lang.Object.hashCodeをオーバーライドします。
override <synthetic> def hashCode(): Int
  • Java.lang.Object.toStringをオーバーライドする:
override def toString(): String
  • newキーワードによるインスタンス化のためのコンストラクター:
def <init>(foo: String, bar: Int): Foo 

オブジェクトFoo: - applyキーワードなしのインスタンス生成のためのメソッドnew

case <synthetic> def apply(foo: String, bar: Int): Foo = new Foo(foo, bar);
  • パターンマッチングでケースクラスFooを使用するための抽出メソッドunupply
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

objectからのtupledは、2つの要素からなるTupleを適用して新しいFooを作成するための機能を返します。

したがって、格クラスは単なる構文糖です。

3
Matthew I.

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

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

コピー方法では、必要なプロパティの一部または全部をソースから継承し、必要に応じてそれらを変更できます。

2
Reddeiah Pidugu

ケースクラスのコンパニオンオブジェクトにtupled防御型があり、その型が次のようになっていることについて誰も言及していません:

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

私が見つけることができる唯一のユースケースは、あなたがTupleからケースクラスを構築する必要があるときです。

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

オブジェクトを直接作成することで、tupledを使わずに同じことを行うことができますが、データセットがarity 20のTuple(20要素のtuple)のリストとして表現されている場合は、tupledを使用できます。

2
Slow Harry

ケースクラスは、match/caseステートメントで使用できるクラスです。

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

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

「パターンマッチングによる再帰的な分解メカニズム」という曖昧なフレーズは、単に「caseで機能する」という意味です。 (確かに、matchが後に続くインスタンスは、caseに続くインスタンスと比較され(突き合わされて)、Scalaはそれらを両方とも分解し、それらが何であるのかを再帰的に分解しなければなりません。)

どんなケースクラスが役に立ちますか? 代数データ型に関するウィキペディアの記事 は2つの良い古典的な例、リストと木を与えます。代数データ型のサポート(それらを比較する方法を知ることを含む)は、現代のあらゆる機能言語にとって必須です。

どのケースクラスでないに便利ではないのですか?いくつかのオブジェクトは状態を持っています、connection.setConnectTimeout(connectTimeout)のようなコードはケースクラスのためではありません。

そして今、あなたは読むことができます Scalaのツアー:ケースクラス

私は全体的にすべての答えがクラスとケースクラスについての意味論的説明を与えていると思います。これは非常に関連性があるかもしれませんが、scalaのすべての初心者はケースクラスを作成したときに何が起こるかを知っているはずです。私は this answerと書いています。これはケースクラスを簡単に説明しています。

すべてのプログラマは、事前に構築された関数を使用している場合は、比較的少ないコードを書いていることを知っておく必要があります。これにより、最も最適化されたコードを書くことができます。そのため、事前に作成された関数は慎重に使用してください。

開発者の中には、クラスファイルを逆アセンブルすることでわかる20の追加メソッドのためにケースクラスを書くことを避けている人もいます。

してください あなたがケースクラス内のすべてのメソッドをチェックしたい場合はこのリンクを参照してください

0
arglee
  • ケースクラスは、applyメソッドとunapplyメソッドでコンパニオンオブジェクトを定義します。
  • ケースクラスはSerializableを拡張する
  • ケースクラスはequals hashCodeとcopyメソッドを定義します
  • コンストラクターのすべての属性はval(構文糖)です。
0
tictactoki

case classesの主要な機能の一部を以下にリストします

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

scalaドキュメントから取得したscala fiddleのscalaコードのサンプル。

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

0
Krishnadas PC