답변:
케이스 클래스는 생성자 인수에만 의존하는 단순하고 변경 불가능한 데이터 보유 오브젝트 로 볼 수 있습니다 .
이 기능 개념을 통해
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생성자 매개 변수 가 있다고 언급 한 사람은 없지만 일반 클래스의 기본값이기도합니다 ( 스칼라 디자인에 불일치라고 생각합니다 ). 다리 오는 " 불변 " 이라고 언급 한 곳을 암시했다 .
var케이스 클래스에 대해 각 생성자 인수를 앞에 추가하여 기본값을 대체 할 수 있습니다 . 그러나 케이스 클래스를 변경 가능하게하면 메소드 equals및 hashCode메소드가 시간 변형됩니다. [1]
sepp2k는 이미 사례 클래스가 자동으로 생성 equals되고 hashCode메소드가 있다고 언급했습니다 .
또한 아무도 경우 클래스가 자동으로 동반자를 만드는 것이 언급되지 object들어있는 클래스와 같은 이름을 가진 apply및 unapply방법. 이 apply방법을 사용하면 앞에 추가하지 않고 인스턴스를 생성 할 수 있습니다 new. unapply추출 방법은 다른 언급하는 패턴 매칭을 가능하게한다.
또한 컴파일러는 속도를 최적화합니다 match-case 케이스 클래스 패턴 매칭 [2]가.
[1] 케이스 클래스는 멋지다
[2] 사례 분류 및 추출기, pg 15 .
Scala의 케이스 클래스 구성은 보일러 플레이트를 제거하기위한 편의로 볼 수도 있습니다.
케이스 클래스를 구성 할 때 Scala는 다음을 제공합니다.
apply는 팩토리 메소드로 사용할 수 있는 메소드를 구현합니다 . 새 키워드를 사용할 필요가없는 구문 설탕 이점을 얻을 수 있습니다.
클래스는 불변이므로 접근자는 클래스의 변수 또는 속성이지만 뮤 테이터는 없습니다 (변수를 변경할 수 없음). 생성자 매개 변수는 공개 읽기 전용 필드로 자동으로 사용 가능합니다. Java bean 구문보다 사용하기가 훨씬 좋습니다.
hashCode, equals그리고 toString기본적으로 방법과 equals방법은 구조적으로 객체를 비교합니다. copy방법 (일부 필드 방법에 제공된 새로운 값을 갖는) 개체를 복제 할 수 있도록 생성된다.앞에서 언급했듯이 가장 큰 장점은 사례 클래스에서 패턴 일치를 할 수 있다는 것입니다. 그 이유 unapply는 케이스 클래스를 해체하여 필드를 추출 할 수있는 메소드를 가져 오기 때문 입니다.
본질적으로 케이스 클래스 (또는 클래스가 인수를 취하지 않으면 케이스 객체)를 만들 때 스칼라에서 얻는 것은 팩토리 및 추출기 로 목적을 제공하는 단일 객체입니다 .
copy메소드가 필드를 수정할 수 있기 때문에val x = y.copy(foo="newValue")
사람들이 이미 말한 것 외에도 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
수업:
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
사례 클래스가 무엇인지 완전히 이해하려면 :
다음과 같은 케이스 클래스 정의를 가정 해 봅시다.
case class Foo(foo:String, bar: Int)
그런 다음 터미널에서 다음을 수행하십시오.
$ scalac -print src/main/scala/Foo.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>();
()
}
}
...
보시다시피 스칼라 컴파일러는 일반 클래스 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.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
<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
tupled from object는 2 개의 요소 튜플을 적용하여 새 Foo를 만드는 기능을 반환합니다.
따라서 사례 클래스는 단지 구문 설탕입니다.
케이스 클래스 컴패니언 객체에는 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)
튜플없이 객체를 직접 생성하여 동일한 작업을 수행 할 수 있지만 arity 20 (튜플이 20 인 튜플) 인 튜플 목록으로 표현 된 데이터 집합이 튜플 링을 사용하는 경우가 가장 좋습니다.
경우 클래스는 함께 사용 할 수있는 클래스입니다 match/case문.
def isIdentityFun(term: Term): Boolean = term match {
case Fun(x, Var(y)) if x == y => true
case _ => false
}
당신은 그것을 참조 case 그 두번째 매개 변수 var에있다 클래스 재미의 인스턴스옵니다. 이것은 매우 훌륭하고 강력한 구문이지만 어떤 클래스의 인스턴스에서도 작동 할 수 없으므로 케이스 클래스에 대한 제한이 있습니다. 이러한 제한 사항을 준수하면 해시 코드와 등호를 자동으로 정의 할 수 있습니다.
"패턴 매칭을 통한 재귀 적 분해 메커니즘"이라는 애매한 문구는 단지 "와 작동한다"를 의미한다 case. (실제로 뒤 따르는 인스턴스는 뒤 따르는 인스턴스 match와 비교 (일치)합니다 case. 스칼라는 두 인스턴스를 모두 분해해야하며, 구성 요소를 재귀 적으로 분해해야합니다.)
어떤 사례 클래스 가 유용합니까? 대수 데이터 형식에 대한 위키 백과 문서 이 좋은 고전 예, 목록과 나무를 제공합니다. 현대 함수형 언어에서는 대수 데이터 형식 (비교 방법을 포함하여)을 지원해야합니다.
어떤 사례 클래스 가 유용 하지 않습니까? 일부 객체에는 상태가 있으며 코드와 같은 코드 connection.setConnectTimeout(connectTimeout)는 사례 클래스가 아닙니다.
이제 Scala 둘러보기 : 사례 클래스를 읽을 수 있습니다 .
나는 모든 답변이 클래스와 사례 클래스에 대한 의미 론적 설명을 제공했다고 생각합니다. 이는 매우 관련성이 있지만 스칼라의 모든 초보자는 사례 클래스를 만들 때 어떤 일이 발생하는지 알아야합니다. 나는 이것을 썼다 답변 하여 간단히 사례 클래스를 설명합니다.
모든 프로그래머는 사전 구축 된 기능을 사용하는 경우 비교적 적은 코드를 작성하므로 가장 최적화 된 코드를 작성할 수는 있지만 큰 책임이 따릅니다. 따라서 미리 작성된 기능을 매우주의해서 사용하십시오.
일부 개발자는 클래스 파일을 분해하여 볼 수있는 추가 20 개의 메소드로 인해 케이스 클래스 작성을 피합니다.
주요 기능 중 일부 case classes는 다음과 같습니다.
new키워드 없이 사례 클래스를 인스턴스화 할 수 있습니다 .스칼라 문서에서 가져온 스칼라 바이올린의 샘플 스칼라 코드.