Scala'nın vaka sınıfı ve sınıfı arasındaki fark nedir?


440

A case classve a arasındaki farkları bulmak için Google'da arama yaptım class. Herkes, sınıfta desen eşlemesi yapmak istediğinizde, vaka sınıfını kullandığından bahseder. Aksi takdirde sınıfları kullanın ve eşittir ve karma kod geçersiz kılma gibi ekstra avantajlardan bahsedin. Ama bu kişinin sınıf yerine vaka örneği kullanmasının tek nedeni bunlar mı?

Sanırım Scala'da bu özelliğin çok önemli bir nedeni olmalı. Açıklama nedir veya Scala vaka sınıfları hakkında daha fazla bilgi edinmek için bir kaynak var mı?

Yanıtlar:


394

Vaka sınıfları, yalnızca yapıcı argümanlarına bağlı olması gereken sade ve değişmez veri tutma nesneleri olarak görülebilir .

Bu fonksiyonel konsept,

  • kompakt bir başlatma sözdizimi ( Node(1, Leaf(2), None))) kullanma
  • desen eşleştirmeyi kullanarak bunları ayrıştır
  • örtük olarak tanımlanmış eşitlik karşılaştırmalarına sahip olmak

Kalıtım ile kombinasyon halinde, vaka sınıfları cebirsel veri tiplerini taklit etmek için kullanılır .

Bir nesne içeride durumsal hesaplamalar yaparsa veya başka türden karmaşık davranışlar sergiliyorsa, sıradan bir sınıf olmalıdır.


11
@Teja: Bir şekilde. ADT en tür olan Çeteleler parametreli son derece güçlü ve türgüvenli.
Dario

8
Mühürlü vaka sınıfları cebirsel veri tiplerini taklit etmek için kullanılır. Aksi takdirde, alt sınıfların sayısı sınırlı değildir.
Thomas Jung

6
@Tomlar: Doğru söylenmiş, kapalı soyut sınıflardan türetilmiş vaka sınıfları kapalı cebirsel veri tiplerini taklit ederken, ADT başka şekilde açıktır .
Dario

2
@Dario ... ve türü aksi halde açık ve bir ADT değil. :-)
Thomas Jung

1
@Thomas: Evet, bu sadece bir varoluşsal;)
Dario

165

Teknik olarak, sınıf ve vaka sınıfı arasında hiçbir fark yoktur - derleyici vaka sınıflarını kullanırken bazı şeyleri optimize etse bile. Bununla birlikte, cebirsel veri türlerini uygulayan belirli bir model için kazan plakası ile ortadan kaldırmak için bir vaka sınıfı kullanılır .

Bu türlere çok basit bir örnek ağaçtır. Örneğin, bir ikili ağaç şu şekilde uygulanabilir:

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

Bu, aşağıdakileri yapmamızı sağlar:

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

Ağaçların aynı sözdizimiyle (desen eşleşmesi yoluyla) oluşturduğunu ve yapısını bozduğunu, bu da tam olarak nasıl yazdırıldıklarını (eksi boşluklar) unutmayın.

Ayrıca geçerli, kararlı bir hashCode'una sahip oldukları için hash haritaları veya kümeleriyle de kullanılabilirler.


71
  • Vaka sınıfları kalıp eşleştirilebilir
  • Vaka sınıfları otomatik olarak hashcode ve eşittir
  • Vaka sınıfları, yapıcı bağımsız değişkenleri için alıcı yöntemlerini otomatik olarak tanımlar.

(Sonuncusundan başka zaten bahsettiniz).

Normal sınıflar arasındaki tek fark bunlar.


13
Yapıcı bağımsız değişkeninde "var" belirtilmedikçe, ayarlayıcılar vaka sınıfları için oluşturulmaz; bu durumda normal sınıflarla aynı alıcı / ayarlayıcı nesnesini alırsınız.
Mitch Blevins

1
@Mitch: Doğru, benim hatam. Şimdi düzeltildi.
sepp2k

2 farkı atladın, cevabımı gör.
Shelby Moore III

@MitchBlevins, normal sınıflarda her zaman alıcı / ayarlayıcı üretimi yoktur.
Shelby Moore III

Vaka sınıfları unapply yöntemini tanımlar, bu yüzden desen eşleşebilirler.
Mutlu İşkenceci

30

Hiç kimse vaka sınıflarının da Productbu yöntemlerin örnekleri olduğunu ve bu nedenle miras aldığını söylememiştir:

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

nerede productAritydöner sınıf parametrelerinin sayısı, productElement(i)döner i inci parametreyi ve productIteratoronlar aracılığıyla iterating verir.


2
Ancak bunlar Product1, Product2 vb. Örnekleri değildir.
Jean-Philippe Pellet

27

Hiç kimse vaka sınıflarının valyapıcı parametrelerine sahip olduğunu söylemedi, ancak bu aynı zamanda normal sınıflar için de varsayılan (Scala tasarımında bir tutarsızlık olduğunu düşünüyorum ). Dario, " değişmez " olduklarını belirten yerlere ima etti .

Her bir yapıcı bağımsız değişkenini varcase sınıflarına ekleyerek varsayılanı geçersiz kılabileceğinizi unutmayın . Bununla birlikte, vaka sınıflarını değişken kılmak onların equalsve hashCodeyöntemlerinin zaman varyantı olmasına neden olmaktadır . [1]

sepp2k , vaka sınıflarının otomatik olarak oluşturulduğunu equalsve hashCodeyöntemlerini belirtti.

Ayrıca hiç kimse vaka sınıflarının otomatik objectolarak sınıf applyve unapplyyöntem içeren sınıfla aynı ada sahip bir yoldaş oluşturduğunu söylemedi . applyYöntem ile prepending olmadan örneklerini inşa sağlar new. unapplyÇıkarıcı yöntemi diğerleri de bu model eşleştirme sağlar.

Ayrıca derleyici hızını optimize match- casedurum sınıfları için desen eşleştirme [2].

[1] Vaka Sınıfları Serin

[2] Vaka Sınıfları ve Çıkarıcılar, sayfa 15 .


12

Scala'daki vaka sınıfı yapısı, bazı kazanların çıkarılması için bir kolaylık olarak da görülebilir.

Bir vaka sınıfı oluştururken Scala size aşağıdakileri verir.

  • Bir sınıfın yanı sıra yardımcı nesnesini de oluşturur
  • Eşlik nesnesi, applyfabrika yöntemi olarak kullanabileceğiniz yöntemi uygular . Yeni anahtar kelimeyi kullanmak zorunda kalmama sözdizimsel şeker avantajı elde edersiniz.

Sınıf değişmez olduğu için, yalnızca sınıfın değişkenleri (veya özellikleri) olan ancak mutasyona sahip olmayan (yani değişkenleri değiştirme yeteneği olmayan) erişimciler alırsınız. Yapıcı parametreleri, herkese açık salt okunur alanlar olarak otomatik olarak kullanılabilir. Java fasulye yapısından çok daha hoş.

  • Ayrıca olsun hashCode, equalsve toStringvarsayılan olarak yöntemleri ve equalsyöntem yapısal bir nesne karşılaştırır. Bir copynesneyi klonlayabilmek için bir yöntem üretilir (bazı alanlar yönteme verilen yeni değerlere sahiptir).

Daha önce de belirtildiği gibi en büyük avantaj, vaka sınıfları üzerinde eşleştirme düzenleyebilmenizdir. Bunun nedeni unapply, alanlarını ayıklamak için bir vaka sınıfını yapılandırabilmenizi sağlayan yöntemi almanızdır.


Aslında, bir vaka sınıfı (ya da sınıfınız argüman almazsa bir vaka nesnesi) oluştururken Scala'dan aldığınız şey, bir fabrika ve çıkarıcı olarak amaca hizmet eden tekli bir nesnedir .


Değişmez bir nesnenin neden bir kopyasına ihtiyacınız var?
Paŭlo Ebermann

@ PaŭloEbermann Çünkü copyyöntem alanları değiştirebilir:val x = y.copy(foo="newValue")
Thilo

8

İnsanların söylediklerinin yanı sıra, classve arasında daha temel farklılıklar var.case class

1. sınıf ile çağrılmak gerekir iken Case Classaçık new, gerekmeznew

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

Varsayılan Yapıcılar tarafından parametreleri içinde özel classiken,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 classkendilerini değere göre karşılaştırır

// 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'nın belgelerine göre :

Vaka sınıfları sadece normal sınıflardır:

  • Varsayılan olarak etkilenmez
  • Kalıp eşleşmesi ile ayrıştırılabilir
  • Referans yerine yapısal eşitlik ile karşılaştırılmıştır
  • Çalıştırmak ve işlemek için özlü

Case anahtar sözcüğünün başka bir özelliği , derleyicinin otomatik olarak bizim için otomatik olarak tanıdık toString, eşittir ve hashCode yöntemleri de dahil olmak üzere çeşitli yöntemler üretmesidir.


5

Sınıf:

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
           ^

Ancak aynı kodu kullanırsak ancak case sınıfını kullanırsak:

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

Kişi sınıfı:

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)

Desen Eşleme:

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

nesne: singleton:

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

Vaka sınıfının ne olduğunu en iyi şekilde anlamak için:

aşağıdaki vaka sınıfı tanımını varsayalım:

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

ve sonra terminalde aşağıdakileri yapın:

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

Scala 2.12.8 çıktı:

...
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>();
    ()
  }
}
...

Gördüğümüz gibi Scala derleyicisi düzenli bir sınıf Foove yardımcı nesne üretir Foo.

Derlenmiş sınıftan geçelim ve elimizde neler olduğunu yorumlayalım:

  • Foosınıfın iç durumu , değişmez:
val foo: String
val bar: Int
  • getters:
def foo(): String
def bar(): Int
  • kopyalama yöntemleri:
def copy(foo: String, bar: Int): Foo
def copy$default$1(): String
def copy$default$2(): Int
  • scala.Productözellik uygulama :
override def productPrefix(): String
def productArity(): Int
def productElement(x$1: Int): Object
override def productIterator(): Iterator
  • scala.Equalsvaka sınıfı örneklerini eşitlikle karşılaştırılabilir kılmak için özellik uygulamak ==:
def canEqual(x$1: Object): Boolean
override def equals(x$1: Object): Boolean
  • java.lang.Object.hashCodeeşittir-hashcode sözleşmesine uymak için geçersiz kılma :
override <synthetic> def hashCode(): Int
  • geçersiz kılma java.lang.Object.toString:
override def toString(): String
  • newanahtar kelime ile örnekleme yapıcısı :
def <init>(foo: String, bar: Int): Foo 

Object Foo: - anahtar kelime applyolmadan örnekleme yöntemi new:

case <synthetic> def apply(foo: String, bar: Int): Foo = new Foo(foo, bar);
  • unupplydesen eşleşmesinde vaka sınıfı Foo kullanmak için ekstraktör yöntemi :
case <synthetic> def unapply(x$0: Foo): Option
  • bir örnek daha üretilmesine izin vermemek için nesneyi serileştirmeden koruma olarak tek yöntem
<synthetic> private def readResolve(): Object = Foo;
  • nesne Foo scala.runtime.AbstractFunction2böyle bir hile yapmak için uzanır :
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 öğeden oluşan bir demet uygulayarak yeni bir Foo oluşturmak için bir işlev döndürür.

Yani vaka sınıfı sadece sözdizimsel şekerdir.


4

Sınıflardan farklı olarak, vaka sınıfları sadece verileri tutmak için kullanılır.

Vaka sınıfları veri merkezli uygulamalar için esnektir, yani vaka sınıfındaki veri alanlarını ve tamamlayıcı bir nesnede iş mantığını tanımlayabilirsiniz. Bu şekilde, verileri iş mantığından ayırırsınız.

Copy yöntemiyle, gerekli özelliklerin herhangi birini veya tümünü kaynaktan devralabilir ve bunları istediğiniz gibi değiştirebilirsiniz.


3

Hiç kimse, vaka sınıfı tamamlayıcı nesnesinin aşağıdaki tupledtürden bir eksiklikten bahsetmedi:

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

Bulabildiğim tek kullanım örneği, tuple'dan case sınıfı oluşturmanız gerektiğinde, örnek:

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

Aynısını, doğrudan nesne oluşturarak yapmadan da yapabilirsiniz, ancak veri kümeleriniz 20 nolu arity (20 elemanlı tuple) ile tuple listesi olarak ifade edilirse, tupled kullanmak sizin tercihinizdir.


3

Bir vaka sınıfı , match/caseifadeyle kullanılabilecek bir sınıftır .

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

Bunu case, 2. parametresi bir Var olan Fun sınıfının bir örneği izler. Bu çok güzel ve güçlü bir sözdizimidir, ancak herhangi bir sınıfın örnekleriyle çalışamaz, bu nedenle vaka sınıfları için bazı kısıtlamalar vardır. Ve bu kısıtlamalara uyulursa, hashcode ve eşitlerini otomatik olarak tanımlamak mümkündür.

Belirsiz ifade "kalıp eşleşmesi yoluyla özyinelemeli bir ayrışma mekanizması" sadece "onunla çalışır" anlamına gelir case. (Aslında, izlenen örnek, takip eden örnekle matchkarşılaştırılır (eşleştirilir) case, Scala her ikisini de ayrıştırmalı ve yapıldıklarını tekrar tekrar ayrıştırmalıdır.)

Hangi vaka sınıfları için yararlıdır? Cebirsel veri türleri hakkında Wikipedia makalesi iki iyi klasik örnekler, listeler ve ağaçlar verir. Cebirsel veri türleri için destek (bunların nasıl karşılaştırılacağını bilmek dahil) herhangi bir modern işlevsel dil için bir zorunluluktur.

Ne durumda sınıfları vardır değil için yararlı? Bazı nesnelerin durumu vardır, kod connection.setConnectTimeout(connectTimeout)büyük / küçük harf sınıfları için değildir.

Ve şimdi Scala Turu: Vaka Sınıfları'nı okuyabilirsiniz


2

Genel olarak tüm cevapların sınıflar ve vaka sınıfları hakkında anlamsal bir açıklama yaptığını düşünüyorum. Bu çok alakalı olabilir, ancak scala'daki her acemi bir vaka sınıfı oluşturduğunuzda ne olacağını bilmelidir. Vaka sınıfını kısaca açıklayan bu cevabı yazdım .

Her programcı, önceden oluşturulmuş herhangi bir işlev kullanıyorsa, nispeten daha az bir kod yazdıklarını bilmelidir, bu da en iyi duruma getirilmiş kodu yazma yetkisi vererek onlara güç sağlar, ancak güç büyük sorumluluklarla gelir. Bu nedenle, önceden oluşturulmuş işlevleri çok dikkatli kullanın.

Bazı geliştiriciler, sınıf dosyasını parçalarına ayırarak görebileceğiniz ek 20 yöntem nedeniyle vaka sınıfları yazmaktan kaçınır.

Bir vaka sınıfındaki tüm yöntemleri kontrol etmek istiyorsanız lütfen bu bağlantıya bakın .


1
  • Vaka sınıfları, uygula ve uygula yöntemleriyle bir compagnon nesnesi tanımlar
  • Vaka sınıfları Serializable'ı genişletir
  • Vaka sınıfları eşittir hashCode ve copy yöntemlerini tanımlar
  • Yapıcının tüm özellikleri val (sözdizimsel şeker)

1

İle ilgili bazı temel özellikler case classesaşağıda listelenmiştir.

  1. vaka sınıfları değişmez.
  2. newAnahtar kelime olmadan vaka sınıflarını başlatabilirsiniz .
  3. vaka sınıfları değere göre karşılaştırılabilir

Skala belgelerinden alınan skala kemanı üzerinde örnek scala kodu.

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

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.