86

これが状況です。ケースクラスを次のように定義したい:

case class A(val s: String)

クラスのインスタンスを作成するときに、次のように「s」の値が常に大文字になるようにオブジェクトを定義したいと思います。

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

ただし、Scala は apply(s: String) メソッドが 2 回定義されていると不平を言っているため、これは機能しません。ケースクラスの構文が自動的に定義することは理解していますが、これを達成できる別の方法はありませんか? パターン マッチングに使用したいので、case クラスに固執したいと思います。

4

10 に答える 10

90

競合の理由は、ケース クラスがまったく同じ apply() メソッド (同じシグネチャ) を提供することです。

まず、require を使用することをお勧めします。

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

ユーザーが s に小文字を含むインスタンスを作成しようとすると、例外がスローされます。コンストラクターに入力したものは、パターン マッチング ( ) を使用したときに得られるものでもあるため、これはケース クラスの適切な使用法ですmatch

これが望ましくない場合は、コンストラクターを作成し、ユーザーにapply メソッドのみprivateを使用するように強制します。

class A private (val s: String) {
}

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

ご覧のとおり、 A はもはや ではありませんcase class。「ケースクラス」という名前は、を使用して(変更されていない)コンストラクター引数を抽出できる必要があることを意味するため、不変フィールドを持つケースクラスが着信値の変更を意図しているかどうかはわかりませmatch

于 2011-04-29T06:54:08.817 に答える
28

UPDATE 2016/02/25:
以下に書いた回答で十分ですが、ケース クラスのコンパニオン オブジェクトに関して、これに対する別の関連する回答も参照する価値があります。つまり、ケースクラス自体を定義するだけで発生するコンパイラ生成の暗黙的なコンパニオンオブジェクトをどのように正確に再現するのでしょうか。私にとって、それは直感に反することが判明しました。


概要:
ケース クラス パラメーターの値は、有効な (化された) ADT (抽象データ型) のままで、ケース クラスに格納される前に簡単に変更できます。解決策は比較的単純でしたが、詳細を明らかにするのはかなり困難でした。

詳細:
ADT (抽象データ型) の背後にある必須の前提である、ケース クラスの有効なインスタンスのみをインスタンス化できるようにする場合は、いくつかのことを行う必要があります。

たとえば、コンパイラによって生成さ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
      • apply「メソッドが 2 回定義されています...」というコンパイル エラーの原因となっているコンパニオン オブジェクトで 、Scala コンパイラがメソッドを生成しないようにします (上記の手順 1.2)。
    2. プライマリ コンストラクタを次のようにマークします。private[A]
      • プライマリ コンストラクターは、ケース クラス自体とそのコンパニオン オブジェクト (上記の手順 1.1 で定義したもの) でのみ使用できるようになりました。
    3. readResolveメソッドを 作成する
      1. apply メソッドを使用して実装を提供します (上記のステップ 1.2)。
    4. copyメソッドを 作成する
      1. ケース クラスのプライマリ コンストラクタとまったく同じシグネチャを持つように定義します。
      2. パラメータごとに、同じパラメータ名 (例: s: String = s) を使用してデフォルト値を追加します。
      3. apply メソッドを使用して実装を提供します (以下のステップ 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)
}

そして、require を実装した後のコード (@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 相互運用機能を介して使用される場合、このバージョンはより安全で堅牢です (ケース クラスを実装として非表示にし、派生を防ぐ最終クラスを作成します)。

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 の姉妹サイト) で文書化したさらに広範なソリューションを作成しました。あなたがそれを調べたり、私のソリューションを使用したり活用したりした場合は、フィードバック、提案、または質問を私に残してください.

于 2014-08-27T23:04:53.970 に答える
12

コンパニオンオブジェクトのメソッドをオーバーライドする方法はわかりませんが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:デフォルトのcaseクラスコンストラクターをオーバーライドすることは可能ですか?

于 2011-04-29T06:25:09.063 に答える
5

var 変数で動作します。

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

このプラクティスは、別のコンストラクターを定義する代わりに、ケース クラスで明らかに推奨されます。こちらをご覧ください。. オブジェクトをコピーすると、同じ変更も保持されます。

于 2013-11-28T10:10:42.633 に答える
4

ケースクラスを維持し、暗黙的な定義や別のコンストラクターを持たない別のアイデアは、署名をapplyわずかに異なるものにすることですが、ユーザーの観点からは同じにすることです。どこかで暗黙のトリックを見たことがありますが、それがどの暗黙の引数であったかを覚えていない/見つけることができないため、ここを選択Booleanしました。誰かが私を助けてトリックを完成させることができれば...

object A {
  def apply(s: String)(implicit ev: Boolean) = new A(s.toLowerCase)
}
case class A(s: String)
于 2011-04-29T07:05:56.470 に答える
0

デフォルトでオーバーライドできない、または@mehmet-emreが示したようにコンパイラフラグを追加したくない古いscalaに行き詰まり、ケースクラスが必要な場合は、次のことができます。

case class A(private val _s: String) {
  val s = _s.toUpperCase
}
于 2018-06-25T15:44:34.653 に答える
-2

これは、あなたがすでに望んでいるとおりに機能すると思います。これが私の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 を使用しています

于 2011-04-29T04:34:02.233 に答える