Scala 2.10介绍了value classes。它们对于编写类型安全代码非常有用。此外还有一些限制,其中一些将由编译器检测,有些则需要在运行时进行分配。
我想使用case class
语法创建值类,以允许创建无新语法和人性化toString
。没有模式匹配,因为它需要分配。
所以问题是:使用case class
语法是否需要值类分配?
答案 0 :(得分:32)
您可以拥有一个值类的案例类。从下面的示例中可以看出,没有对象创建。当然,如果你要向任何人倾斜,那么不可避免的拳击。
这是一小段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
}
答案 1 :(得分:7)
为了扩展这个问题,Wojciech Langiewicz提出了一个用作案例类的Value class的好例子。
而不是:
case class Player(id: Int, gameCash: Int, gameCoins: Int, energy: Int)
Wojciech定义:
case class Player(id: PlayerId, gameCash: GameCash, gameCoins: GameCoins, energy: Energy)
使用case类(不在堆上分配其他对象):
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
当创建只包含一个参数的case类时,你应该添加
extends AnyVal
以允许Scala编译器运行更多优化 - 基本上类型检查只在编译阶段完成,但在运行时只有对象的将创建基础类型,从而减少内存开销。在我们的代码中的特定点添加自定义类型不仅提高了可读性,而且还允许我们减少错误 - 卸载一些检查,否则必须在测试中完成(或根本不做)编译器。您还可以立即在IDE或编辑器中看到错误。
因为现在
Player
类中的每个组件本身都是一个单独的类型,所以添加新的运算符也很容易,否则可能必须隐式添加这些运算符会污染更大的范围。
答案 2 :(得分:2)
至少对于“允许创建 - 没有新语法”,您可以使用普通旧方法或object
和apply
方法。 toString
也不是问题(如果我没记错的话),但如果您不使用案例类,则必须自己定义。
顺便说一句,语言允许定义扩展AnyVal
的案例类,参见http://docs.scala-lang.org/overviews/core/value-classes.html
答案 3 :(得分:1)
请参阅overview documentation部分“何时需要分配”。
案例类会收到特别注意:“注意:您可以在实践中使用案例类和/或扩展方法来获得更清晰的语法。”
但是,正如@ rudiger-klaehn已经说过的那样,需要注意的是提供任何预期的AnyVal:
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尚未合并。请继续关注。)