Scala 2.10 引入了值类。它们对于编写类型安全代码非常有用。此外还有一些限制,有些会被编译器检测到,有些需要在运行时分配。
我想使用case class
语法创建值类,以允许创建没有新语法和人类友好toString
。没有模式匹配,因为它需要分配。
所以问题是:使用case class
语法是否需要值类分配?
Scala 2.10 引入了值类。它们对于编写类型安全代码非常有用。此外还有一些限制,有些会被编译器检测到,有些需要在运行时分配。
我想使用case class
语法创建值类,以允许创建没有新语法和人类友好toString
。没有模式匹配,因为它需要分配。
所以问题是:使用case class
语法是否需要值类分配?
您可以拥有一个作为值类的案例类。从下面的示例中可以看出,没有对象创建。当然,如果你愿意接受任何的话,那不可避免的拳击除外。
这是一小段scala代码
class ValueClass(val value:Int) extends AnyVal
case class ValueCaseClass(value:Int) extends AnyVal
class ValueClassTest {
var x: ValueClass = new ValueClass(1)
var y: ValueCaseClass = ValueCaseClass(2)
def m1(x:ValueClass) = x.value
def m2(x:ValueCaseClass) = x.value
}
还有字节码,它不包含任何两个值类的丝毫痕迹。
Compiled from "ValueClassTest.scala"
public class ValueClassTest {
public int x();
Code:
0: aload_0
1: getfield #14 // Field x:I
4: ireturn
public void x_$eq(int);
Code:
0: aload_0
1: iload_1
2: putfield #14 // Field x:I
5: return
public int y();
Code:
0: aload_0
1: getfield #21 // Field y:I
4: ireturn
public void y_$eq(int);
Code:
0: aload_0
1: iload_1
2: putfield #21 // Field y:I
5: return
public int m1(int);
Code:
0: iload_1
1: ireturn
public int m2(int);
Code:
0: iload_1
1: ireturn
public rklaehn.ValueClassTest();
Code:
0: aload_0
1: invokespecial #29 // Method java/lang/Object."<init>":()V
4: aload_0
5: iconst_1
6: putfield #14 // Field x:I
9: aload_0
10: iconst_2
11: putfield #21 // Field y:I
14: return
}
为了扩展这个问题,Wojciech Langiewicz提出了一个很好的将Value 类用作案例类的例子。
代替:
case class Player(id: Int, gameCash: Int, gameCoins: Int, energy: Int)
沃伊切赫定义:
case class Player(id: PlayerId, gameCash: GameCash, gameCoins: GameCoins, energy: Energy)
使用案例类(没有在堆上分配其他对象):
case class PlayerId(id: Int) extends AnyVal
case class GameCash(value: Int) extends AnyVal
case class GameCoins(value: Int) extends AnyVal
case class Energy(value: Int) extends AnyVal
当创建只包含一个参数的案例类时,您应该添加
extends AnyVal
以允许 Scala 编译器运行更多优化 - 基本上类型检查将仅在编译阶段进行,但在运行时只会创建底层类型的对象,从而减少内存开销.在我们代码的特定点添加自定义类型不仅提高了可读性,而且还让我们减少了错误——将一些原本必须在测试中完成(或根本不做)的检查卸载到编译器。您还可以立即在 IDE 或编辑器中看到错误。
因为现在
Player
类中的每个组件本身都是一个单独的类型,所以添加新的运算符也很容易,否则可能必须隐式添加,这会污染更大的范围。
至少对于“允许在没有新语法的情况下创建”,您可以使用普通的旧方法或object
带有apply
方法的 s。toString
也不是问题(如果我没记错的话),尽管如果您不使用案例类,则必须自己定义它。
顺便说一句,语言允许定义扩展的案例类AnyVal
,请参阅http://docs.scala-lang.org/overviews/core/value-classes.html
请参阅概述文档部分“需要分配时”。
案例类收到特别通知:“注意:您可以在实践中使用案例类和/或扩展方法来获得更清晰的语法。”
但是,正如@rudiger-klaehn 已经说过的那样,警告示例是提供一个 AnyVal ,其中一个 Any 是预期的:
package anything
// the caveat from the overview
trait Distance extends Any
case class Meter(val value: Double) extends AnyVal with Distance
class Foo {
def add(a: Distance, b: Distance): Distance = Meter(2.0)
}
object Test extends App {
val foo = new Foo
Console println foo.add(Meter(3.4), Meter(4.3))
}
显示 :javap -app 在最新的 2.11 中已修复:
scala> :javap -app anything.Test$
public final void delayedEndpoint$anything$Test$1();
flags: ACC_PUBLIC, ACC_FINAL
Code:
stack=7, locals=1, args_size=1
0: aload_0
1: new #63 // class anything/Foo
4: dup
5: invokespecial #64 // Method anything/Foo."<init>":()V
8: putfield #60 // Field foo:Lanything/Foo;
11: getstatic #69 // Field scala/Console$.MODULE$:Lscala/Console$;
14: aload_0
15: invokevirtual #71 // Method foo:()Lanything/Foo;
18: new #73 // class anything/Meter
21: dup
22: ldc2_w #74 // double 3.4d
25: invokespecial #78 // Method anything/Meter."<init>":(D)V
28: new #73 // class anything/Meter
31: dup
32: ldc2_w #79 // double 4.3d
35: invokespecial #78 // Method anything/Meter."<init>":(D)V
38: invokevirtual #84 // Method anything/Foo.add:(Lanything/Distance;Lanything/Distance;)Lanything/Distance;
41: invokevirtual #88 // Method scala/Console$.println:(Ljava/lang/Object;)V
44: return
LocalVariableTable:
Start Length Slot Name Signature
0 45 0 this Lanything/Test$;
LineNumberTable:
line 11: 0
line 12: 11
}
正如我们被警告的那样,有拳击。
(更新:实际上,修复 -app 的 PR 尚未合并。敬请期待。)