2017-01-22 48 views
2

鉴于以下功能:Value Class w/String Inner Type?

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个Value Classes,使用它们的好处是什么,比使用case class的好处多多少少?

回答

4

值类并不意味着提供一种方法来执行堆栈分配而不是堆分配。这通常是你无法控制的东西。相反,它们旨在防止在创建“包装器”类时发生的额外对象分配。

在你的Firstname情况下,延长AnyVal意味着,当你做这样的事情

val x = new Firstname("Bob") 

没有FirstName实例实际创建和x实际上只是一个String在运行时(假设你不做一个文档描述的那些会强制分配的东西,比如模式匹配)。但是AnyVal绝不会改变包装的String的分配方式。

+0

作为一个方面说明,我想补充一点,它能够在编译删除。因此,你可以在情况下两种方法结束接受两个不同的值类,但它不能编译,因为该方法最终具有相同的签名。 –

1

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

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

value class是为了避免分配对象。作为你的例子:

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

字节码:

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

姓伴侣对象

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 

这样:

由于我们与值类的目标是避免分配整个价值对象,而不是直接在包裹值工作我们必须停止使用实例方法 - 因为它们会迫使我们拥有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
相关问题