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
ます。これは、大文字のインスタンスのみを持つという慎重な戦略が、単純なテキスト エディターと逆シリアル化によって覆される可能性があることを意味します。
したがって、ケース クラスをさまざまな方法で (善意または悪意を持って) 使用できるすべての場合、実行する必要があるアクションは次のとおりです。
- 明示的なコンパニオン オブジェクトの場合:
- ケースクラスとまったく同じ名前を使用して作成します
- これは、ケース クラスのプライベート パーツにアクセスできます。
apply
ケース クラスのプライマリ コンストラクターとまったく同じシグネチャを持つメソッドを
作成します。
- ステップ 2.1 が完了すると、これは正常にコンパイルされます。
- 演算子を使用してケース クラスのインスタンスを取得し
new
、空の実装を提供する実装を提供する{}
- これにより、条件に厳密に基づいてケースクラスがインスタンス化されます
{}
ケース クラスが宣言されているため、空の実装を提供する必要がありますabstract
(手順 2.1 を参照) 。
- ケースクラスの場合:
- 宣言する
abstract
apply
「メソッドが 2 回定義されています...」というコンパイル エラーの原因となっているコンパニオン オブジェクトで 、Scala コンパイラがメソッドを生成しないようにします (上記の手順 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 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 の姉妹サイト) で文書化したさらに広範なソリューションを作成しました。あなたがそれを調べたり、私のソリューションを使用したり活用したりした場合は、フィードバック、提案、または質問を私に残してください.