為了讓語言更簡單、更安全或更一致,一些其他功能被簡化或限制。
不相容性 | Scala 3 遷移重寫 |
---|---|
繼承遮蔽 | ✅ |
私有類別中的非私有建構函式 | 遷移警告 |
抽象覆寫 | |
案例類別伴侶 | |
明確呼叫 unapply | |
隱形的 bean 屬性 | |
=>T 作為類型引數 |
|
萬用字元類型引數 |
繼承遮蔽
繼承的成員,來自父特質或類別,可以遮蔽在外層範圍中定義的識別碼。此模式稱為繼承遮蔽。
例如,在此前一段程式碼中,C 中的 x
詞彙可以參照在外層類別 B
中定義的 x
成員,或者可以參照父類別 A
的 x
成員。在您前往 A
的定義之前,您無法得知。
這已知容易出錯。
這就是為什麼在 Scala 3 中,如果父類別 A
確實有一個成員 x
,編譯器需要消除歧義。
它會阻止以下程式碼編譯。
class A {
val x = 2
}
object B {
val x = 1
class C extends A {
println(x)
}
}
但如果你嘗試使用 Scala 3 編譯,你應該會看到類似這樣的錯誤
-- [E049] Reference Error: src/main/scala/inheritance-shadowing.scala:9:14
9 | println(x)
| ^
| Reference to x is ambiguous,
| it is both defined in object B
| and inherited subsequently in class C
Scala 3 遷移編譯 可以自動透過將 println(x)
替換為 println(this.x)
來消除程式碼的歧義。
非私有建構函式在私有類別中
Scala 3 編譯器要求私有類別的建構函式必須是私有的。
例如,在範例中
package foo
private class Bar private[foo] () {}
如果你嘗試在 Scala 3 中編譯,你應該會收到以下錯誤訊息
-- Error: /home/piquerez/scalacenter/scala-3-migration-guide/incompat/access-modifier/src/main/scala-2.13/access-modifier.scala:4:19
4 | private class Bar private[foo] ()
| ^
| non-private constructor Bar in class Bar refers to private class Bar
| in its type signature (): foo.Foo.Bar
Scala 3 遷移編譯 會警告這一點,但沒有提供自動重寫。
解決方案是將建構函式設為私有的,因為類別是私有的。
抽象覆寫
在 Scala 3 中,使用抽象 def 覆寫具體 def 會導致子類別將 def 視為抽象,而在 Scala 2 中則視為具體。
在以下程式碼片段中,Scala 2.13 編譯器將 C
中的 bar
方法視為具體,但 Scala 3 編譯器將其視為抽象,導致出現以下錯誤。
trait A {
def bar(x: Int): Int = x + 3
}
trait B extends A {
def bar(x: Int): Int
}
class C extends B // In Scala 3, Error: class C needs to be abstract, since def bar(x: Int): Int is not defined
此行為已在 Dotty 議題 #4770 中決定。
一個簡單的修復方法是直接移除抽象 def,因為在實務上它在 Scala 2 中沒有任何作用。
案例類別伴生物件
案例類別的伴生物件不再延伸任何 Function{0-23}
特質。特別是,它不會繼承它們的方法:tupled
、curried
、andThen
、compose
…
例如,以下不再被允許
case class Foo(x: Int, b: Boolean)
Foo.curried(1)(true)
Foo.tupled((2, false))
一個跨編譯的解決方案是明確 eta 展開方法 Foo.apply
。
-Foo.curried(1)(true)
+(Foo.apply _).curried(1)(true)
-Foo.tupled((2, false))
+(Foo.apply _).tupled((2, false))
或者,基於效能考量,你可以引入一個中間函式值。
val fooCtr: (Int, Boolean) => Foo = (x, b) => Foo(x, b)
fooCtr.curried(1)(true)
fooCtr.tupled((2, false))
明確呼叫 unapply
在 Scala 中,案例類別有一個自動產生的萃取器方法,稱為其伴生物件中的 unapply
。它的簽章在 Scala 2.13 和 Scala 3 之間有所變更。
新的簽章沒有選項(請參閱新的 模式比對 參考),當 unapply
被明確呼叫時,這會導致不相容性。
請注意,這個問題不會影響使用者定義的萃取器,其簽章在 Scala 版本中保持不變。
給定以下案例類別定義
Scala 2.13 編譯器會產生以下 unapply
方法
object Location {
def unapply(location: Location): Option[(Double, Double)] = Some((location.lat, location.long))
}
而 Scala 3 編譯器會產生
object Location {
def unapply(location: Location): Location = location
}
因此,以下程式碼在 Scala 3 中不再編譯。
def tuple(location: Location): (Int, Int) = {
Location.unapply(location).get // [E008] In Scala 3, Not Found Error: value get is not a member of Location
}
在 Scala 3 中,可能的解決方案是使用樣式繫結
def tuple(location: Location): (Int, Int) = {
- Location.unapply(location).get
+ val Location(lat, lon) = location
+ (lat, lon)
}
隱形的 Bean 屬性
由 BeanProperty
註解產生的 getter 和 setter 方法現在在 Scala 3 中是隱形的,因為它們的主要用例是與 Java 框架的互操作性。
例如,以下 Scala 2 程式碼將無法在 Scala 3 中編譯
class Pojo() {
@BeanProperty var fooBar: String = ""
}
val pojo = new Pojo()
pojo.setFooBar("hello") // [E008] In Scala 3, Not Found Error: value setFooBar is not a member of Pojo
println(pojo.getFooBar()) // [E008] In Scala 3, Not Found Error: value getFooBar is not a member of Pojo
在 Scala 3 中,解決方案是呼叫更慣用的 pojo.fooBar
getter 和 setter。
val pojo = new Pojo()
-pojo.setFooBar("hello")
+pojo.fooBar = "hello"
-println(pojo.getFooBar())
+println(pojo.fooBar)
=> T
作為類型引數
形式為 => T
的類型不能再用作類型參數的引數。
此決定在 Scala 3 原始碼的此註解 中說明。
例如,不允許將類型為 Int => (=> Int) => Int
的函數傳遞給 uncurried
方法,因為它會將 => Int
指定給類型參數 T2
。
-- [E134] Type Mismatch Error: src/main/scala/by-name-param-type-infer.scala:3:41
3 | val g: (Int, => Int) => Int = Function.uncurried(f)
| ^^^^^^^^^^^^^^^^^^
|None of the overloaded alternatives of method uncurried in object Function with types
| [T1, T2, T3, T4, T5, R]
| (f: T1 => T2 => T3 => T4 => T5 => R): (T1, T2, T3, T4, T5) => R
| [T1, T2, T3, T4, R](f: T1 => T2 => T3 => T4 => R): (T1, T2, T3, T4) => R
| [T1, T2, T3, R](f: T1 => T2 => T3 => R): (T1, T2, T3) => R
| [T1, T2, R](f: T1 => T2 => R): (T1, T2) => R
|match arguments ((Test.f : Int => (=> Int) => Int))
解決方案取決於情況。在給定的範例中,你可以
- 定義你自己的
uncurried
方法,並使用適當的簽章 - 內嵌
uncurried
的實作
萬用字元類型引數
Scala 3 無法將高階抽象類型成員的應用程式減少為萬用字元引數。
例如,以下 Scala 2 程式碼將無法在 Scala 3 中編譯
trait Example {
type Foo[A]
def f(foo: Foo[_]): Unit // [E043] In Scala 3, Type Error: unreducible application of higher-kinded type Example.this.Foo to wildcard arguments
}
我們可以使用類型參數來修復此問題
-def f(foo: Foo[_]): Unit
+def f[A](foo: Foo[A]): Unit
但是當 Foo
本身用作類型引數時,這個簡單的解決方案無法運作。
def g(foos: Seq[Foo[_]]): Unit
在這種情況下,我們可以在 Foo
周圍使用包裝類別
+class FooWrapper[A](foo: Foo[A])
-def g(foos: Seq[Foo[_]]): Unit
+def g(foos: Seq[FooWrapper[_]]): Unit