给出以下功能:
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
更好的是什么?
答案 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值我们将调用扩展方法的时间。
参考: