UPDATE 2016/02/25:
以下に書いた答えはまだ十分ですが、ケースクラスのコンパニオンオブジェクトに関して、これに関連する別の答えも参照する価値があります。つまり、ケースクラス自体を定義するだけの場合に発生する、コンパイラによって生成された暗黙のコンパニオンオブジェクトをどのように正確に再現するのでしょうか。私にとって、それは直感に反することがわかりました。
要約:
ケースクラスパラメーターの値は、有効な(ated)ADT(抽象データ型)のままで、ケースクラスに格納される前に非常に簡単に変更できます。解決策は比較的単純でしたが、詳細を見つけることはかなり困難でした。
詳細:
ケースクラスの有効なインスタンスのみをインスタンス化できるようにしたい場合は、ADT(抽象データ型)の背後にある重要な前提条件ですが、実行する必要のあることがいくつかあります。
たとえば、コンパイラで生成された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
がコンパニオンオブジェクトでメソッドを生成しないようにします。これが「メソッドが2回定義されています...」コンパイルエラーの原因でした(上記のステップ1.2)
- プライマリコンストラクタを次のようにマークします
private[A]
- プライマリコンストラクタは、ケースクラス自体とそのコンパニオンオブジェクト(上記の手順1.1で定義したもの)でのみ使用できるようになりました。
readResolve
メソッドを
作成する
- applyメソッドを使用して実装を提供します(上記のステップ1.2)
copy
メソッドを
作成する
- ケースクラスのプライマリコンストラクターとまったく同じシグネチャを持つように定義します
- 各パラメータについては、同じパラメータ名(例を:使用してデフォルト値を追加します
s: String = s
)
- applyメソッドを使用して実装を提供します(以下のステップ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)
}
そして、require(@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相互運用機能を介して使用される場合、このバージョンはより安全で堅牢です(実装としてケースクラスを非表示にし、派生を防ぐ最終クラスを作成します)。
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姉妹サイト)で文書化したさらに広範なソリューションを作成しました。私のソリューションを検討したり、使用したり、活用したりした場合は、フィードバック、提案、質問を残しておくことを検討してください。合理的な範囲内で、1日以内に対応できるよう最善を尽くします。