2 回答

TA貢獻(xiàn)1829條經(jīng)驗(yàn) 獲得超4個(gè)贊
發(fā)生沖突的原因是案例類提供了完全相同的apply()方法(相同的簽名)。
首先,我建議您使用require:
case class A(s: String) {
require(! s.toCharArray.exists( _.isLower ), "Bad string: "+ s)
}
如果用戶嘗試創(chuàng)建s包含小寫字符的實(shí)例,則將引發(fā)Exception。這是用例類的一種很好的用法,因?yàn)槭褂媚J狡ヅ洌╩atch)時(shí),您在構(gòu)造函數(shù)中輸入的內(nèi)容也同樣如此。
如果這不是您想要的,那么我將創(chuàng)建構(gòu)造函數(shù)private并強(qiáng)制用戶僅使用apply方法:
class A private (val s: String) {
}
object A {
def apply(s: String): A = new A(s.toUpperCase)
}
如您所見,A不再是case class。我不確定具有不可變字段的case類是否用于修改傳入的值,因?yàn)槊Q“ case class”表示應(yīng)該可以使用提?。ㄎ葱薷牡模?gòu)造函數(shù)參數(shù)match。

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

TA貢獻(xiàn)2036條經(jīng)驗(yàn) 獲得超8個(gè)贊
我不知道如何apply在伴隨對(duì)象中重寫該方法(如果可能的話),但是您也可以對(duì)大寫字符串使用特殊類型:
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)
您還應(yīng)該看看這個(gè)問題及其答案:Scala:是否可以覆蓋默認(rèn)的case類構(gòu)造函數(shù)?
- 2 回答
- 0 關(guān)注
- 505 瀏覽
添加回答
舉報(bào)