Scala özelliklerinde val veya def ne zaman kullanılır?


90

Ben geçiyordu etkili skala slaytlar ve kullanımı asla slayt 10 bahseder valbir de traitsoyut üyeleri ve kullanım için defyerine. Soyut kullanarak neden slayt ayrıntılı olarak söz etmez valbir de traitbir anti-kalıptır. Birisi, soyut yöntemler için bir özellikte val vs def kullanma konusundaki en iyi uygulamayı açıklayabilirse memnun olurum.

Yanıtlar:


130

A def, a def, a val, a lazy valveya an ile uygulanabilir object. Dolayısıyla, bir üyeyi tanımlamanın en soyut şekli. Özellikler genellikle soyut arayüzler olduğundan, a istediğinizi valsöylemek , uygulamanın nasıl yapılması gerektiğini söylemektir. Bir istersen val, uygulama sınıfı bir def.

A val, yalnızca kararlı bir tanımlayıcıya ihtiyacınız varsa, örneğin yola bağımlı bir tür için gereklidir. Bu genellikle ihtiyacınız olmayan bir şeydir.


Karşılaştırmak:

trait Foo { def bar: Int }

object F1 extends Foo { def bar = util.Random.nextInt(33) } // ok

class F2(val bar: Int) extends Foo // ok

object F3 extends Foo {
  lazy val bar = { // ok
    Thread.sleep(5000)  // really heavy number crunching
    42
  }
}

Olsaydı

trait Foo { val bar: Int }

F1veya tanımlayamazsınız F3.


Tamam, kafanızı karıştırmak ve @ om-nom-nom'u yanıtlamak için - özet vals kullanmak başlatma sorunlarına neden olabilir:

trait Foo { 
  val bar: Int 
  val schoko = bar + bar
}

object Fail extends Foo {
  val bar = 33
}

Fail.schoko  // zero!!

Bu, benim kişisel görüşüme göre, gelecekteki Scala sürümlerinde onu derleyicide düzelterek ortadan kaldırması gereken çirkin bir sorundur, ancak evet, şu anda bu aynı zamanda soyut vals kullanılmaması için bir nedendir .

Düzenleme (Ocak 2016): valBir lazy valuygulama ile soyut bir bildirimi geçersiz kılmanıza izin verilir , böylece başlatma hatasını da önler.


8
aldatıcı başlatma sırası ve şaşırtıcı boşluklarla ilgili kelimeler?
om-nom-nom

Evet ... oraya bile gitmem. Doğru, bunlar aynı zamanda val'e karşı argümanlardır, ancak bence temel motivasyon sadece uygulamayı gizlemek olmalıdır.
0__

2
Bu, yeni bir Scala sürümünde (bu yorum itibariyle 2.11.4) değişmiş olabilir, ancak a valile a'yı geçersiz kılabilirsiniz lazy val. Sizin iddia oluşturmak mümkün olmaz F3ise barbir oldu valdoğru değil. Dedi ki, özelliklerin soyut üyeleri her zaman olmalı defs'
mplis

Foo / Fail örneği, val schoko = bar + barile değiştirirseniz beklendiği gibi çalışır lazy val schoko = bar + bar. Bu, başlatma sırası üzerinde biraz kontrole sahip olmanın bir yolu. Ayrıca, türetilmiş sınıfta lazy valyerine kullanmak defyeniden hesaplamayı önler.
Adrian

2
Eğer değiştirirseniz val bar: Intiçin def bar: Int Fail.schokohala sıfırdır.
Jasper-M

8

Özelliklerde kullanmayı tercih etmiyorum valçünkü val beyanı net olmayan ve sezgisel olmayan başlatma sırasına sahip. Zaten çalışan hiyerarşiye bir özellik ekleyebilirsiniz ve daha önce işe yarayan her şeyi bozabilir, konuma bakın: neden final olmayan sınıflarda sade val kullanılıyor?

Bu val bildirimlerini kullanmakla ilgili her şeyi aklınızda tutmanız ve sonunda sizi bir hataya yönlendirmeniz gerekir.


Daha karmaşık örnekle güncelleme

Ancak kullanmaktan kaçınamayacağınız zamanlar vardır val. @ 0__’ın da bahsettiği gibi bazen kararlı bir tanımlayıcıya ihtiyacınız vardır vedef bu bir değildir.

Ne hakkında konuştuğunu göstermek için bir örnek verirdim:

trait Holder {
  type Inner
  val init : Inner
}
class Access(val holder : Holder) {
  val access : holder.Inner =
    holder.init
}
trait Access2 {
  def holder : Holder
  def access : holder.Inner =
    holder.init
}

Bu kod şu hatayı üretir:

 StableIdentifier.scala:14: error: stable identifier required, but Access2.this.holder found.
    def access : holder.Inner =

Bir dakikanızı ayırırsanız, derleyicinin şikayet etmek için bir nedeni olduğunu anlayabilirsiniz. Herhangi bir şekilde Access2.accessdönüş türü türetemediği durumda. def holdergeniş bir şekilde uygulanabileceği anlamına gelir. Her arama için farklı sahipler döndürebilir ve bu sahipleri farklı Innertürler içerebilir. Ancak Java sanal makinesi aynı türün döndürülmesini bekler.


3
Başlatma sırası önemli olmamalı, bunun yerine çalışma sırasında anti-model karşısında şaşırtıcı NPE'ler alıyoruz.
Jonathan Neufeld

scala, zorunlu doğayı arkasına gizleyen bildirimsel sözdizimine sahiptir. Bazen bu zorunluluk sezgisel olarak işe
yaramaz

-4

Böyle bir şey işe yaramayacağı için her zaman def kullanmak biraz garip görünüyor:

trait Entity { def id:Int}

object Table { 
  def create(e:Entity) = {e.id = 1 }  
}

Aşağıdaki hatayı alacaksınız:

error: value id_= is not a member of Entity

2
Alakalı değil. Def yerine val kullanırsanız da bir hatanız var (hata: val'ye yeniden atama) ve bu tamamen mantıklı.
volia17

Eğer kullanırsan değil var. Mesele şu ki, eğer bunlar alan iseler, bu şekilde tanımlanmaları gerekir. Her şeyin defkısa görüşlü olduğunu düşünüyorum .
Dimitry

@Dimitry, elbette, varhadi kullanarak kapsüllemeyi kıralım . Ancak a def(veya a val) kullanmak, global bir değişkene tercih edilir. Sanırım aradığınız şey case class ConcreteEntity(override val id: Int) extends Entity, onu yaratabilmeniz gibi bir şey . def create(e: Entity) = ConcreteEntity(1)Bu, kapsüllemeyi kırmaktan ve herhangi bir sınıfın Varlığı değiştirmesine izin vermekten daha güvenlidir.
Jono
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.