나는 통해가는 효과적인 스칼라 슬라이드 과 사용 결코 슬라이드 (10)에 언급 val
A의 trait
추상적 인 회원들과 사용을 위해 def
대신. 이 슬라이드는 왜 추상 val
을 사용하는 trait
것이 반 패턴 인지에 대해서는 자세히 언급하지 않습니다 . 누군가가 추상 방법에 대한 특성에서 val 대 def를 사용하는 것에 대한 모범 사례를 설명 할 수 있다면 감사하겠습니다.
나는 통해가는 효과적인 스칼라 슬라이드 과 사용 결코 슬라이드 (10)에 언급 val
A의 trait
추상적 인 회원들과 사용을 위해 def
대신. 이 슬라이드는 왜 추상 val
을 사용하는 trait
것이 반 패턴 인지에 대해서는 자세히 언급하지 않습니다 . 누군가가 추상 방법에 대한 특성에서 val 대 def를 사용하는 것에 대한 모범 사례를 설명 할 수 있다면 감사하겠습니다.
답변:
A def
는 def
, a val
, a lazy val
또는 object
. 그래서 이것은 멤버를 정의하는 가장 추상적 인 형태입니다. 트레이 트는 일반적으로 추상적 인 인터페이스이기 때문에 당신이 원한다고 val
말하는 것은 구현이 어떻게해야 하는지를 말하는 것입니다. 을 요청 val
하면 구현 클래스는 def
.
A val
는 안정적인 식별자가 필요한 경우에만 필요합니다 (예 : 경로 종속 유형). 그것은 일반적으로 필요하지 않은 것입니다.
비교:
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
}
}
만약 당신이
trait Foo { val bar: Int }
F1
또는 을 정의 할 수 없습니다 F3
.
좋습니다. 혼란스럽고 @ om-nom-nom으로 대답합니다. abstract를 사용 val
하면 초기화 문제가 발생할 수 있습니다.
trait Foo {
val bar: Int
val schoko = bar + bar
}
object Fail extends Foo {
val bar = 33
}
Fail.schoko // zero!!
이것은 내 개인적인 의견으로는 컴파일러에서 수정하여 향후 Scala 버전에서 사라져야하는 추악한 문제이지만, 현재 이것은 추상을 사용해서는 안되는 이유이기도합니다. val
.
편집 (2016 년 1 월) : 구현으로 추상 val
선언 을 재정의 lazy val
할 수 있으므로 초기화 실패도 방지 할 수 있습니다.
val
A를을 lazy val
. 귀하의 주장은 당신이 만들 수없는 것이라고 F3
하면 bar
A가 있었다 val
올바르지 않습니다. 즉, def
val schoko = bar + bar
으로 lazy val schoko = bar + bar
. 이것이 초기화 순서를 제어하는 한 가지 방법입니다. 또한 파생 클래스에서 lazy val
대신 사용 def
하면 재 계산이 방지됩니다.
val bar: Int
하면 def bar: Int
Fail.schoko
여전히 0입니다.
나는 val
val 선언이 명확하지 않고 직관적이지 않은 초기화 순서를 가지고 있기 때문에 특성에 사용하지 않는 것을 선호합니다 . 이미 작동하는 계층 구조에 특성을 추가하면 이전에 작동했던 모든 것을 깨뜨릴 수 있습니다. 내 주제를 참조하십시오. 최종 클래스가 아닌 클래스에서 일반 val을 사용하는 이유
이 val 선언 사용에 대한 모든 것을 염두에 두어야 결국 오류가 발생합니다.
그러나 사용을 피할 수없는 경우가 있습니다 val
. @ 0__이 언급했듯이 때때로 안정적인 식별자가 필요하며 def
하나가 아닙니다.
나는 그가 말한 것을 보여주는 예를 제공 할 것입니다.
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
}
이 코드는 오류를 생성합니다.
StableIdentifier.scala:14: error: stable identifier required, but Access2.this.holder found.
def access : holder.Inner =
잠시 시간을내어 컴파일러가 불평 할 이유가 있다는 것을 이해할 수있을 것이라고 생각한다면. 의 Access2.access
경우는 어떤 방법으로 파생 반환 형식은 할 수 없습니다. def holder
광범위한 방식으로 구현 될 수 있음을 의미합니다. 각 호출에 대해 서로 다른 보유자를 반환 할 수 있으며 보유자는 서로 다른 Inner
유형을 통합 합니다. 그러나 Java 가상 머신은 동일한 유형이 리턴 될 것으로 예상합니다.
다음과 같은 것이 작동하지 않기 때문에 항상 def를 사용하는 것이 약간 어색해 보입니다.
trait Entity { def id:Int}
object Table {
def create(e:Entity) = {e.id = 1 }
}
다음과 같은 오류가 발생합니다.
error: value id_= is not a member of Entity
var
. 요점은 그들이 필드라면 그 자체로 지정되어야한다는 것입니다. 나는 단지 모든 것을 def
근시 라고 생각 합니다.
var
하면 캡슐화를 깰 수 있습니다. 그러나 a def
(또는 a val
)를 사용하는 것이 전역 변수보다 선호됩니다. 나는 당신이 찾고있는 것은 같은 생각 case class ConcreteEntity(override val id: Int) extends Entity
에서 당신이 그것을 만들 수 있도록 def create(e: Entity) = ConcreteEntity(1)
이 캡슐을 깨고 모든 클래스는 엔터티를 변경할 수 있도록보다 안전합니다.