值类是否为字符串内部类型?

时间:2017-01-22 02:23:14

标签: scala

给出以下功能:

def prefixDr(firstName: String, lastName: String): String = 
    "Dr. " + firstName + " " + lastName

我们假设我决定添加Value Class以避免混淆名字和姓氏,例如:

scala> prefixDr("Doe", "Jane") 
res5: String = Dr. Doe Jane // whoops - should've been in reverse order

class FirstName(val value: String) extends AnyVal
class LastName(val value: String) extends AnyVal

然后使用它们:

def prefixDr(firstName: FirstName, lastName: LastName): String = 
   "Dr. " + firstName.value + " " + lastName.value

现在比以前更安全了:

scala> prefixDr( new FirstName("Jane"), new LastName("Doe") )
res6: String = Dr. Jane Doe

首先,我对值类型动机的理解是避免堆分配,即将值存储在堆栈上,而不是堆中;以前的访问时间比后者快。

但是,在上述情况下,我使用了两个String,它们是AnyRef的子类型,即java.lang.Object,它们分配给堆,据我所知。

那么,在上面的例子中,即使用2个价值类,使用它们比case class更好的是什么?

2 个答案:

答案 0 :(得分:4)

值类不是为了提供一种堆栈分配而不是堆分配的方法。这通常是你无法控制的。相反,它们旨在防止在创建"包装器时发生的额外对象分配。类。

对于Firstname,扩展AnyVal意味着当您执行类似

的操作时
val x = new Firstname("Bob")

实际上并未创建FirstName的实例,而x实际上只是String在运行时(假设您没有执行文档所描述的强制事项之一分配,例如模式匹配)。但AnyVal决不会改变包装String的分配方式。

答案 1 :(得分:1)

首先,值类等于只有一个参数的案例类扩展AnyVal 。见value class

  

我们将在这里的所有示例中使用Case(Value)类,但技术上并不需要这样做(尽管非常方便)。您可以使用带有一个val参数的普通类来实现Value Class,但使用case类通常是最好的方法。

值类用于避免分配对象。作为你的例子:

class FullName(val value: String) extends AnyVal
class FirstName(val value: String) extends AnyVal {
  def toFullName: FullName = new FullName(value + " lastName")
}

FirstName 字节码:

scala> :javap -c FirstName
Compiled from "<console>"
public final class $line12.$read$$iw$$iw$FirstName {
  public java.lang.String value(); 
  public java.lang.String toFullName();
    Code:
       0: getstatic     #28                 // Field $line12/$read$$iw$$iw$FirstName$.MODULE$:L$line12/$read$$iw$$iw$FirstName$;
       3: aload_0
       4: invokevirtual #30                 // Method value:()Ljava/lang/String;
       7: invokevirtual #34                 // Method $line12/$read$$iw$$iw$FirstName$.toFullName$extension:(Ljava/lang/String;)Ljava/lang/String;
      10: areturn

  public int hashCode();
  public boolean equals(java.lang.Object);

正如FirstName$.toFullName$extension:(Ljava/lang/String;)Ljava/lang/String所示,它正在为新FirstName调用FullName伴随对象,并且返回类型也会被编译器优化为String

FirstName随播广告

scala> :javap -c FirstName$
Compiled from "<console>"
public class $line12.$read$$iw$$iw$FirstName$ {
  ...
  public final java.lang.String toFullName$extension(java.lang.String);
    Code:
       0: new           #28                 // class java/lang/StringBuilder
       3: dup
       4: invokespecial #29                 // Method java/lang/StringBuilder."<init>":()V
       7: aload_1
       8: invokevirtual #33                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      11: ldc           #35                 // String  lastName
      13: invokevirtual #33                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      16: invokevirtual #39                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      19: areturn

这样:

  

由于我们使用Value Classes的目标是避免必须分配整个值对象,而是直接在包装的值上工作,我们必须停止使用实例方法 - 因为它们会强制我们使用Wrapper的实例(Meter) )课。我们可以做的是将实例方法提升为扩展方法,我们将其存储在Meter的伴随对象中,而不是使用值:实例的Double字段,我们将传递Double值我们将调用扩展方法的时间。

参考:

  1. http://ktoso.github.io/scala-types-of-types/#value-class
  2. http://docs.scala-lang.org/overviews/core/value-classes.html