斯卡拉。具有一个字段的案例类可以是值类吗?

时间:2013-07-15 15:45:31

标签: scala scala-2.10

Scala 2.10介绍了value classes。它们对于编写类型安全代码非常有用。此外还有一些限制,其中一些将由编译器检测,有些则需要在运行时进行分配。

我想使用case class语法创建值类,以允许创建无新语法和人性化toString。没有模式匹配,因为它需要分配。

所以问题是:使用case class语法是否需要值类分配?

4 个答案:

答案 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)

至少对于“允许创建 - 没有新语法”,您可以使用普通旧方法或objectapply方法。 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尚未合并。请继续关注。)