2 回答

TA貢獻1829條經驗 獲得超4個贊
發生沖突的原因是案例類提供了完全相同的apply()方法(相同的簽名)。
首先,我建議您使用require:
case class A(s: String) {
require(! s.toCharArray.exists( _.isLower ), "Bad string: "+ s)
}
如果用戶嘗試創建s包含小寫字符的實例,則將引發Exception。這是用例類的一種很好的用法,因為使用模式匹配(match)時,您在構造函數中輸入的內容也同樣如此。
如果這不是您想要的,那么我將創建構造函數private并強制用戶僅使用apply方法:
class A private (val s: String) {
}
object A {
def apply(s: String): A = new A(s.toUpperCase)
}
如您所見,A不再是case class。我不確定具有不可變字段的case類是否用于修改傳入的值,因為名稱“ case class”表示應該可以使用提?。ㄎ葱薷牡模嬙旌瘮祬祄atch。

TA貢獻1818條經驗 獲得超11個贊
盡管我在下面編寫的答案仍然足夠,但值得一提的是與案例類的伴隨對象相關的另一個答案。即,如何精確地重現編譯器生成的隱式伴隨對象,該對象僅在定義案例類本身時發生。對我來說,事實證明這與直覺相反。
摘要:
您可以更改案例類參數的值,然后再將其存儲在案例類中,非常簡單,同時仍保留有效的(已指定)ADT(抽象數據類型)。盡管解決方案相對簡單,但是發現細節卻更具挑戰性。
詳細信息:
如果要確保只能實例化case類的有效實例,這是ADT(抽象數據類型)背后的基本假設,則必須執行許多操作。
例如,copy默認情況下,案例類提供了編譯器生成的方法。因此,即使您非常小心以確保僅通過顯式伴隨對象的apply方法創建了僅能包含大寫值的實例,以下代碼仍將生成具有小寫值的case類實例:
val a1 = A("Hi There") //contains "HI THERE"
val a2 = a1.copy(s = "gotcha") //contains "gotcha"
此外,案例類還實現了java.io.Serializable。這意味著可以通過簡單的文本編輯器和反序列化來顛覆只包含大寫實例的謹慎策略。
因此,對于使用案例類的各種方式(善意和/或惡意),您必須采取以下措施:
對于您的顯式伴侶對象:
使用與案例類完全相同的名稱創建它
可以訪問案例類的私有部分
創建一個apply與您的case類的主構造函數具有完全相同的簽名的方法
一旦完成步驟2.1,它將成功編譯
提供一個使用new運算符獲取案例類實例的實現,并提供一個空的實現{}
現在,這將嚴格按照您的條件實例化案例類
{}由于聲明了案例類,因此必須提供空實現abstract(請參閱步驟2.1)。
對于您的案例類:
聲明它 abstract
防止Scala編譯器apply在伴隨對象中生成方法,而該方法正是導致“方法被定義兩次...”的編譯錯誤(上面的步驟1.2)
將主要構造函數標記為 private[A]
現在,主要構造函數僅可用于case類本身及其伴隨對象(我們在上面的步驟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 interop使用此代碼,則此版本將更安全/更可靠(將case類隱藏為實現,并創建一個防止派生的最終類):
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姐妹網站)上進行了記錄。如果您最終查看,使用或利用我的解決方案,請考慮給我留下反饋,建議或問題,并在合理的范圍內,我將盡力在一天之內做出答復。

TA貢獻2036條經驗 獲得超8個贊
我不知道如何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類構造函數?
- 2 回答
- 0 關注
- 509 瀏覽
添加回答
舉報