ProgramingTip

케이스 클래스 컴패니언에서 적용을 재정의하는 방법

bestdevel 2020. 10. 5. 08:11
반응형

케이스 클래스 컴패니언에서 적용을 재정의하는 방법


그래서 여기에 상황이 있습니다. 다음과 같이 케이스 클래스를 정의하고 싶습니다.

case class A(val s: String)

클래스의 인스턴스를 만들 때 's'의 값이 항상 대문자가 개체를 정의하고 싶습니다.

object A {
  def apply(s: String) = new A(s.toUpperCase)
}

그러나 Scala가 apply (s : String) 메소드가 두 번 정의되어있어 작동하지 않습니다. 케이스 클래스 구문이 자동으로 정의한다는 것을 이해하지만 달성 할 수있는 다른 방법이 없습니까? 패턴 매칭에 사용하고 싶기 때문에 케이스 클래스를 고수하고 싶습니다.


충돌의 이유는 케이스 클래스가 똑같은 적용 () 메서드 (동일한 서명)를 제공하기 때문입니다.

먼저 다음을 사용하는 것이 좋습니다.

case class A(s: String) {
  require(! s.toCharArray.exists( _.isLower ), "Bad string: "+ s)
}

사용자가 s에 소문자가 포함 된 인스턴스를 만들려고하면 예외가 발생합니다. 생성자에 넣은 것은 패턴 매칭 ( match) 을 사용할 때 사용할 수있는 것이기 때문에이 케이스 클래스의 좋은 사용입니다 .

이것이 원하는 것이 아니라면 생성 private만들어 사용자가 적용 방법 사용 하도록 강제합니다 .

class A private (val s: String) {
}

object A {
  def apply(s: String): A = new A(s.toUpperCase)
}

보시다시피 A는 더 이상 case class. "case class"라는 이름은 사용하여 (수정할 수없는) 생성자 인수를 추출 할 수 있다는 것을 의미하기 때문에 변경 불가능한 필드가있는 케이스 클래스가 값을 수정하기 위해 확실하지 않습니다 match.


2016/02/25 업데이트 : 아래
에 오류 메시지는 충분하지만 케이스 클래스의 동반자 수업과 관련하여 이것에 대한 다른 관련 답변을 참조하는 것도 가치가 있습니다. 즉, 케이스 클래스 자체 만 정의 할 때 발생 하는 컴파일러 생성 암시 적 컴패니언 object-정확히 어떻게 재현 할 수 있습니까 ? 저 기능하지 않나요?


요약 :
케이스 클래스 선행 변수가 유효한 (정해진) ADT (Abstract Data Type)를 유지하면서 케이스 클래스에 저장되기 전에 케이스 클래스의 값을 설명 수 있습니다. 해결은 있었지만 세부 사항을 발견하는 것은 훨씬 더 어려웠습니다.

세부 사항 :
ADT (Abstract Data Type) 필요한 가정 인 케이스 클래스의 유효한 인스턴스 만 인스턴스화 할 수있는 수행해야 할 여러 가지가 있습니다.

예를 들어 컴파일러 생성 메소드 copy는 케이스 클래스에서 기본적으로 제공됩니다. 따라서 인스턴스 apply만 대문자 값만 포함 할 수있는 것 보장 하는 명시 적 발생 하는 메서드를 통해 생성하는 매우주의를 기울이는 것이 다음 코드는 소문자 값을 가진 케이스 클래스 인스턴스를 생성합니다.

val a1 = A("Hi There") //contains "HI THERE"
val a2 = a1.copy(s = "gotcha") //contains "gotcha"

또한 케이스 클래스는 java.io.Serializable. 즉, 간단한 텍스트 편집기와 역 합성 화를 사용하여 대문자 인스턴스 만 사용하려는 신중한 전략을 뒤집을 수 있습니다.

따라서 케이스 클래스를 사용할 수있는 모든 다양한 방법 (자비 롭거나 악의적으로)에 대해 조치를 취해야합니다.

  1. 명시 적 컴패니언 개체의 경우 :
    1. 케이스 클래스와 동일한 이름을 사용하여 작성하십시오.
      • 케이스 클래스의 개인 부분에 액세스 할 수 있습니다.
    2. apply케이스 클래스의 기본 생성자와 동일한 서명을 사용하여 메소드 만듭니다.
      • 2.1 단계가 완료됩니다.
    3. new연산자를 사용하여 케이스 클래스의 인스턴스를 빈번하게 구현을 제공하는 구현을 제공합니다.{}
      • 이제 조건에 따라 케이스 클래스를 인스턴스화합니다.
      • {}케이스 클래스가 선언 될 빈 구현 을 제공해야합니다 abstract(2.1 단계 참조).

  2. 케이스 클래스 :
    1. 선언 abstract
      • Scala 컴파일러 apply가 "method is defined twice ..."컴파일 오류 (위의 1.2 단계)를 객체로부터 메소드 를 생성하지 못하도록합니다.
    2. 기본 생성 튼 다음과 같이 표시하십시오. private[A]
      • 기본 단계 생성자는 클래스 자체에서 클래스를 생성하고 클래스에서 클래스를 정의합니다.
    3. readResolve방법 만들기
      1. 적용 방법을 사용하여 구현 제공 (위의 1.2 단계)
    4. copy방법 만들기
      1. 케이스 클래스의 기본 생성자와 동일한 서명을 갖도록 정의하십시오.
      2. 각 매개 변수에 대해 동일한 매개 변수 이름을 사용하여 기본 값을 추가 (예를 : s: String = s)
      3. 적용 방법을 사용하여 구현 제공 (아래 1.2 단계)

위의 작업으로 수정 된 코드는 다음과 가변됩니다.

object A {
  def apply(s: String, i: Int): A =
    new A(s.toUpperCase, i) {} //abstract class implementation intentionally empty
}
abstract case class A private[A] (s: String, i: Int) {
  private def readResolve(): Object = //to ensure validation and possible singleton-ness, must override readResolve to use explicit companion object apply method
    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")
    //TODO: Insert normal instance caching mechanism here
    new A(s, i) {} //abstract class implementation intentionally empty
  }
}
abstract case class A private[A] (s: String, i: Int) {
  private def readResolve(): Object = //to ensure validation and possible singleton-ness, must override readResolve to use explicit companion object apply method
    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")
    //TODO: Insert normal instance caching mechanism here
    new A(s, i)
  }
}
final class A private[A] (s: String, i: Int) extends A.AImpl(s, i) {
  private def readResolve(): Object = //to ensure validation and possible singleton-ness, must override readResolve to use explicit companion object apply method
    A.apply(s, i)
  def copy(s: String = s, i: Int = i): A =
    A.apply(s, i)
}

이것은 대답의 질문에 직접적으로 대답하는 클래스 인스턴스 캐싱을 넘어 케이스를 중심으로이 경로를 확장하는 더 많은 방법이 있습니다. 내 프로젝트 요구 에 따라 CodeReview (StackOverflow 자매 사이트) 에 문서화 한 훨씬 더 광범위한 솔루션만들었습니다 . 내 솔루션을 살펴 보거나, 사용하거나, 활용하는 경우 제안, 제안 또는 질문을 다리 바랍니다. 이유 내에서 최선을 다해 하루 내 답변 해 드리겠습니다.


apply동반 객체 메서드 를 재정의하는 방법 을 모르겠지만 (가능하다면) 대문자 문자열에 특수 유형을 사용할 수도 있습니다.

class UpperCaseString(s: String) extends Proxy {
  val self: String = s.toUpperCase
}

implicit def stringToUpperCaseString(s: String) = new UpperCaseString(s)
implicit def upperCaseStringToString(s: UpperCaseString) = s.self

case class A(val s: UpperCaseString)

println(A("hello"))

위의 코드는 다음을 출력합니다.

A(HELLO)

당신은 또한이 질문을 봐야합니다. 그리고 그것은 대답입니다 : Scala : 기본 케이스 클래스 생성자를 재정의 할 수 있습니까?


2017 년 4 월 이후이 글을 읽는 사람들을 위해 : Scala 2.12.2 이상부터 Scala 는 기본적으로 적용 및 적용 취소를 재정의 할 수 있습니다 . -Xsource:2.12Scala 2.11.11+에서도 컴파일러에 옵션을 제공하여이 동작을 얻을 수 있습니다 .


케이스 클래스를 유지하고 암시 적 정의 나 다른 생성자가없는 또 다른 아이디어는 서명을 apply약간 다르지만 사용자 관점에서 동일하게 만드는 것 입니다. 어딘가에서 암시 적 트릭을 보았지만 어떤 암시 적 주장인지 기억 / 찾을 수 없으므로 Boolean여기에서 선택했습니다 . 누군가 나를 도와주고 속임수를 끝낼 수 있다면 ...

object A {
  def apply(s: String)(implicit ev: Boolean) = new A(s.toLowerCase)
}
case class A(s: String)

var 변수와 함께 작동합니다.

case class A(var s: String) {
   // Conversion
   s = s.toUpperCase
}

이 방법은 다른 생성자를 정의하는 대신 케이스 클래스에서 권장됩니다. 여길 봐. . 개체를 복사 할 때도 동일한 수정 사항이 유지됩니다.


나는 같은 문제에 직면 했고이 해결책은 나에게 좋습니다.

sealed trait A {
  def s:String
}

object A {
  private case class AImpl(s:String)
  def apply(s:String):A = AImpl(s.toUpperCase)
}

그리고 어떤 메서드가 필요하면 트레이 트에서 정의하고 케이스 클래스에서 재정의하십시오.


기본적으로 재정의 할 수없는 이전 스칼라를 사용하고 있거나 @ mehmet-emre가 표시 한대로 컴파일러 플래그를 추가하고 싶지 않고 케이스 클래스가 필요한 경우 다음을 수행 할 수 있습니다.

case class A(private val _s: String) {
  val s = _s.toUpperCase
}

나는 이것이 당신이 이미 원하는 방식으로 정확하게 작동한다고 생각합니다. 내 REPL 세션은 다음과 같습니다.

scala> case class A(val s: String)
defined class A

scala> object A {
     | def apply(s: String) = new A(s.toUpperCase)
     | }
defined module A

scala> A("hello")
res0: A = A(HELLO)

이것은 Scala 2.8.1.final을 사용하고 있습니다.

참고 URL : https://stackoverflow.com/questions/5827510/how-to-override-apply-in-a-case-class-companion

반응형