case class
とclass
の違いを見つけるためにGoogleで検索しました。誰もがあなたがクラスのパターンマッチングをしたいときは、ユースケースクラスを使用すると述べています。それ以外の場合はクラスを使用し、等号やハッシュコードの上書きなどの追加の特典についても言及します。しかし、これらがクラスではなくケースクラスを使用する必要がある唯一の理由ですか?
私はScalaにこの機能のためのいくつかの非常に重要な理由があるはずだと思います。説明は何ですか?Scalaのケースクラスについてさらに学ぶためのリソースはありますか?
ケースクラスは、コンストラクタの引数にのみ依存する、単純で不変のデータ保持オブジェクトと見なすことができます。
この機能概念は私達にそれを可能にします
Node(1, Leaf(2), None))
)を使う継承と組み合わせて、ケースクラスは 代数データ型 を模倣するために使用されます。
オブジェクトが内部でステートフル計算を実行したり、他の種類の複雑な動作を示す場合、それは普通のクラスであるべきです。
技術的には、クラスとケースクラスの間に違いはありません - たとえコンパイラがケースクラスを使用するときにいくつかのものを最適化したとしてもです。しかし、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を持っているので、ハッシュマップやセットでも使うことができます。
(あなたはすでに最後のものを除くすべてを述べました)。
それらが通常のクラスとの唯一の違いです。
ケースクラスはProduct
のインスタンスでもあるため、これらのメソッドを継承することに言及した人はいません。
def productElement(n: Int): Any
def productArity: Int
def productIterator: Iterator[Any]
ここでproductArity
はクラスパラメータの数を返し、productElement(i)
はiを返します。番目 productIterator
はそれらを反復処理することを可能にします。
ケースクラスにval
コンストラクタパラメータがあるとは誰も述べていませんが、これは通常のクラスのデフォルトでもあります(これは Scalaの設計では矛盾していると思います )。ダリオは、彼らが「不変」であることに気づいたところにそのようなことを暗示しました。
ケースクラスの場合、各コンストラクタの引数の前にvar
を付けることで、デフォルトを上書きすることができます。ただし、ケースクラスを変更可能にすると、それらのequals
メソッドおよびhashCode
メソッドが時変になります。
sepp2kでは、ケースクラスが自動的にequals
メソッドおよびhashCode
メソッドを自動的に生成することを既に説明しました。
また、ケースクラスがobject
メソッドとapply
メソッドを含む、クラスと同じ名前のunapply
というコンパニオンを自動的に作成することに言及した人はいません。 apply
メソッドはnew
を前に付けずにインスタンスを構築することを可能にします。 unapply
抽出メソッドは、他の人が言及したパターンマッチングを有効にします。
また、コンパイラは、ケースクラスに対するmatch
-case
パターンマッチングの速度を最適化します[2]。
[1] ケースクラスはクールです
[2] 格クラスと抽出、pg 15 。
Scalaのcaseクラスの構成要素は、定型句を削除するのに便利だと見なすこともできます。
ケースクラスを構築するとき、Scalaはあなたに以下を与えます。
apply
メソッドを実装しています。新しいキーワードを使用しなくてもよいという糖衣構文上の利点が得られます。クラスは不変であるため、アクセサはクラスの変数(またはプロパティ)だけでミューテータはありません(変数を変更する機能はありません)。コンストラクターのパラメーターは、公開読み取り専用フィールドとして自動的に使用可能になります。 Java Bean構成よりもはるかに使いやすいです。
hashCode
、equals
、およびtoString
の各メソッドがあり、equals
メソッドはオブジェクトを構造的に比較します。オブジェクトを複製できるようにcopy
メソッドが生成されます(一部のフィールドにはメソッドに新しい値が提供されます)。前述の最大の利点は、ケースクラスでパターンマッチングができるということです。これは、ケースクラスを分解してそのフィールドを抽出できるunapply
メソッドを取得したためです。
基本的に、Scalaからcaseクラス(またはクラスが引数を取らない場合はcaseオブジェクト)を作成するときに得るものは、factoryおよび)としての目的を果たすシングルトンオブジェクトです。エクストラクター。
class
とcase 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
クラス:
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
ケースクラスとは何かについての究極の理解を得るために:
次のケースクラス定義を仮定しましょう。
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
scala.Equals
と同等であることに匹敵するように==
トレイトを実装する:def canEqual(x$1: Object): Boolean
override def equals(x$1: Object): Boolean
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);
unupply
:case <synthetic> def unapply(x$0: Foo): Option
<synthetic> private def readResolve(): Object = 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を作成するための機能を返します。
したがって、格クラスは単なる構文糖です。
クラスと異なり、ケースクラスは単にデータを保持するために使用されます。
ケースクラスはデータ中心のアプリケーションに柔軟に対応します。つまり、ケースクラスでデータフィールドを定義し、コンパニオンオブジェクトでビジネスロジックを定義できます。このようにして、データをビジネスロジックから分離しています。
コピー方法では、必要なプロパティの一部または全部をソースから継承し、必要に応じてそれらを変更できます。
ケースクラスのコンパニオンオブジェクトに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を使用できます。
ケースクラスは、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の追加メソッドのためにケースクラスを書くことを避けている人もいます。
case classes
の主要な機能の一部を以下にリストします
new
キーワードなしでケースクラスをインスタンス化できます。scalaドキュメントから取得したscala fiddleのscalaコードのサンプル。