一些其他功能已简化或受限,以使语言更简单、更安全或更一致。
不兼容性 | 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 编译,你应该会看到类似以下类型的错误
Scala 3 迁移编译可以通过将 println(x)
替换为 println(this.x)
自动消除代码歧义。
私有类中的非私有构造函数
Scala 3 编译器要求私有类的构造函数为私有。
例如,在示例中
package foo
private class Bar private[foo] () {}
如果你尝试在 scala 3 中编译,你应该会收到以下错误消息
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
。
或者,出于性能原因,你可以引入一个中间函数值。
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 版本中保持不变。
给定以下 case 类定义
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 中,一种可能的解决方案是使用模式绑定
不可见的 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。
=> T
作为类型参数
形式为 => T
的类型不能再用作类型参数的参数。
此决定在 Scala 3 源代码的 此注释 中进行了说明。
例如,不允许将类型为 Int => (=> Int) => Int
的函数传递给 uncurried
方法,因为它会将 => Int
分配给类型参数 T2
。
解决方案取决于情况。在给定的示例中,你可以
- 使用适当的签名定义你自己的
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
}
我们可以通过使用类型参数来解决此问题
但当 Foo
本身用作类型参数时,此简单解决方案不起作用。
def g(foos: Seq[Foo[_]]): Unit
在这种情况下,我们可以在 Foo
周围使用包装器类