状況は次のとおりです。ケースクラスを次のように定義します。
case class A(val s: String)
そして、クラスのインスタンスを作成するときに、「s」の値が常に大文字になるようにオブジェクトを定義します。
object A {
def apply(s: String) = new A(s.toUpperCase)
}
ただし、Scalaはapply(s:String)メソッドが2回定義されていると不平を言っているため、これは動作しません。これを実現する別の方法はありますか?パターンマッチングに使用したいので、ケースクラスに固執したいと思います。
競合の理由は、ケースクラスがまったく同じapply()メソッドを提供するためです(同じシグネチャ)。
まず、requireを使用することをお勧めします。
case class A(s: String) {
require(! s.toCharArray.exists( _.isLower ), "Bad string: "+ s)
}
Sに小文字の文字が含まれるインスタンスをユーザーが作成しようとすると、例外がスローされます。コンストラクターに入れるものは、パターンマッチング(match
)を使用するときに得られるものでもあるため、これはケースクラスの適切な使用法です。
これが望んでいない場合は、コンストラクタをprivate
にして、ユーザーにonlyを適用メソッドを使用するように強制します。
class A private (val s: String) {
}
object A {
def apply(s: String): A = new A(s.toUpperCase)
}
ご覧のとおり、Aはcase class
ではなくなりました。 「ケースクラス」という名前は、match
を使用して(変更されていない)コンストラクター引数を抽出できることを意味するため、不変フィールドを持つケースクラスが着信値の変更を意味するかどうかはわかりません。
更新2016/02/25:
以下に書いた回答で十分ですが、ケースクラスのコンパニオンオブジェクトに関して、これに関連する別の回答も参照する価値があります。つまり、 コンパイラが生成した暗黙的なコンパニオンオブジェクトを正確に再現する方法 これは、ケースクラス自体を定義するだけの場合に発生します。私にとっては、直感に反することがわかりました。
要約:
ケースクラスパラメータの値は、まだ有効な(指定された)ADT(抽象データ型)のままで、ケースクラスに格納する前に変更できます。解決策は比較的簡単でしたが、詳細を見つけるのはかなり困難でした。
詳細:
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
を実装します。つまり、大文字のインスタンスのみを使用するという慎重な戦略は、単純なテキストエディターと逆シリアル化によって覆すことができます。
そのため、ケースクラスを使用するさまざまな方法(好意的および/または悪意的に)について、以下のアクションを実行する必要があります。
apply
メソッドを作成しますnew
演算子を使用してケースクラスのインスタンスを取得し、空の実装{}
を提供する実装を提供しますabstract
として宣言されているため、空の実装{}
を提供する必要があります(ステップ2.1を参照)abstract
を宣言しますapply
メソッドを生成しないようにします。これにより、 "method is defined ..."コンパイルエラーが発生します(上記のステップ1.2)private[A]
としてマークします。readResolve
メソッドを作成しますcopy
メソッドを作成しますs: String = s
)上記のアクションで変更されたコードは次のとおりです。
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 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)
また、この質問とその答えを見てください: スカラ:デフォルトのケースクラスコンストラクタをオーバーライドすることは可能ですか?
2017年4月以降にこれを読んでいる人の場合:Scala 2.12.2+以降、Scala は適用と適用解除のオーバーライドを許可しますデフォルトで 。この動作は、Scala 2.11.11+)で-Xsource:2.12
オプションをコンパイラに指定することで取得できます。
ケースクラスを保持し、暗黙のdefや別のコンストラクタを持たない別のアイデアは、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)
}
また、メソッドが必要な場合は、トレイトで定義し、ケースクラスでオーバーライドします。
古いscalaでデフォルトでオーバーライドできない場合、または@ mehmet-emreが示したようにコンパイラフラグを追加したくない場合、ケースクラスが必要な場合は、以下:
case class A(private val _s: String) {
val s = _s.toUpperCase
}