업데이트 2016/02/25 :
아래에 작성한 답변은 충분하지만 케이스 클래스의 동반자 객체와 관련하여 이에 대한 다른 관련 답변을 참조하는 것도 가치가 있습니다. 즉, 케이스 클래스 자체 만 정의 할 때 발생 하는 컴파일러 생성 암시 적 컴패니언 객체 를 정확히 어떻게 재현합니까 ? 나에게는 직관적이지 않은 것으로 판명되었습니다.
요약 :
케이스 클래스 매개 변수가 유효한 (정해진) ADT (Abstract Data Type)를 유지하면서 케이스 클래스에 저장되기 전에 케이스 클래스 매개 변수의 값을 변경할 수 있습니다. 해결책은 비교적 간단했지만 세부 사항을 발견하는 것은 훨씬 더 어려웠습니다.
세부 정보 :
ADT (Abstract Data Type) 뒤에 필수적인 가정 인 케이스 클래스의 유효한 인스턴스 만 인스턴스화 할 수 있도록하려면 수행해야하는 여러 가지가 있습니다.
예를 들어 컴파일러 생성 copy
메서드는 케이스 클래스에서 기본적으로 제공됩니다. 따라서 인스턴스 apply
만 대문자 값만 포함 할 수 있음을 보장 하는 명시 적 컴패니언 객체의 메서드를 통해 생성되도록 매우 신중한 경우에도 다음 코드는 소문자 값을 가진 케이스 클래스 인스턴스를 생성합니다.
val a1 = A("Hi There")
val a2 = a1.copy(s = "gotcha")
또한 케이스 클래스는 java.io.Serializable
. 즉, 간단한 텍스트 편집기와 역 직렬화로 대문자 인스턴스 만 사용하려는 신중한 전략을 뒤집을 수 있습니다.
따라서 케이스 클래스를 사용할 수있는 모든 다양한 방법 (자비 롭거나 악의적으로)에 대해 취해야 할 조치는 다음과 같습니다.
- 명시 적 컴패니언 객체의 경우 :
- 케이스 클래스와 정확히 동일한 이름을 사용하여 작성하십시오.
- 이것은 케이스 클래스의 개인 부분에 액세스 할 수 있습니다.
apply
케이스 클래스의 기본 생성자와 정확히 동일한 서명을 사용하여 메서드를
만듭니다.
- 2.1 단계가 완료되면 성공적으로 컴파일됩니다.
new
연산자를 사용하여 케이스 클래스의 인스턴스를 가져 오고 빈 구현을 제공하는 구현을 제공합니다.{}
- 이제 조건에 따라 케이스 클래스를 인스턴스화합니다.
{}
케이스 클래스가 선언되었으므로 빈 구현 을 제공해야합니다 abstract
(2.1 단계 참조).
- 케이스 클래스 :
- 선언
abstract
- Scala 컴파일러
apply
가 "method is twice ..."컴파일 오류를 일으키는 동반 객체에서 메소드 를 생성하지 못하도록합니다 (위의 1.2 단계).
- 기본 생성자를 다음과 같이 표시하십시오.
private[A]
- 기본 생성자는 이제 케이스 클래스 자체와 동반 객체 (위의 1.1 단계에서 정의한 객체)에서만 사용할 수 있습니다.
readResolve
방법
만들기
- 적용 방법을 사용하여 구현 제공 (위의 1.2 단계)
copy
방법
만들기
- 케이스 클래스의 기본 생성자와 정확히 동일한 서명을 갖도록 정의하십시오.
- 각 매개 변수에 대해 동일한 매개 변수 이름을 사용하여 기본 값을 추가 (예를 :
s: String = s
)
- 적용 방법을 사용하여 구현 제공 (아래 1.2 단계)
위의 작업으로 수정 된 코드는 다음과 같습니다.
object A {
def apply(s: String, i: Int): A =
new A(s.toUpperCase, i) {}
}
abstract case class A private[A] (s: String, i: Int) {
private def readResolve(): Object =
A.apply(s, i)
def copy(s: String = s, i: Int = i): A =
A.apply(s, i)
}
그리고 다음은 요구 사항을 구현하고 (@ollekullberg 답변에서 제 안됨) 모든 종류의 캐싱을 배치 할 이상적인 위치를 식별 한 후의 코드입니다.
object A {
def apply(s: String, i: Int): A = {
require(s.forall(_.isUpper), s"Bad String: $s")
new A(s, i) {}
}
}
abstract case class A private[A] (s: String, i: Int) {
private def readResolve(): Object =
A.apply(s, i)
def copy(s: String = s, i: Int = i): A =
A.apply(s, i)
}
이 코드가 Java interop을 통해 사용될 경우이 버전은 더 안전하고 견고합니다 (케이스 클래스를 구현으로 숨기고 파생을 방지하는 최종 클래스를 만듭니다).
object A {
private[A] abstract case class AImpl private[A] (s: String, i: Int)
def apply(s: String, i: Int): A = {
require(s.forall(_.isUpper), s"Bad String: $s")
new A(s, i)
}
}
final class A private[A] (s: String, i: Int) extends A.AImpl(s, i) {
private def readResolve(): Object =
A.apply(s, i)
def copy(s: String = s, i: Int = i): A =
A.apply(s, i)
}
이것이 귀하의 질문에 직접적으로 대답하지만 인스턴스 캐싱을 넘어 케이스 클래스를 중심으로이 경로를 확장하는 더 많은 방법이 있습니다. 내 프로젝트 요구를 위해 CodeReview (StackOverflow 자매 사이트) 에 문서화 한 훨씬 더 광범위한 솔루션 을 만들었습니다 . 내 솔루션을 살펴 보거나 사용하거나 활용하게된다면 피드백, 제안 또는 질문을 남겨 주시기 바랍니다. 이유 내에서 최선을 다해 하루 이내에 답변 해 드리겠습니다.