Reputation: 41909
Given the following function:
def prefixDr(firstName: String, lastName: String): String =
"Dr. " + firstName + " " + lastName
Let's say I decide to add a Value Class to avoid mixing up the first and last names, example:
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
and then use them:
def prefixDr(firstName: FirstName, lastName: LastName): String =
"Dr. " + firstName.value + " " + lastName.value
It now is safer than the previous:
scala> prefixDr( new FirstName("Jane"), new LastName("Doe") )
res6: String = Dr. Jane Doe
First, my understanding of the motivation of a Value Type is to avoid heap allocation, i.e. store the value on the stack, not the heap; where the former access time is faster than the latter.
However, in the above case, I'm using two String
's, which are sub-types of AnyRef
, i.e. java.lang.Object
, which allocate to the heap, as I understand.
So, in the above example, i.e. using the 2 Value Classes, what's the benefit of using them over case class
's?
Upvotes: 4
Views: 1446
Reputation: 14217
First, value class is equal to case class extend AnyVal with only one parameter. see value class.
We’ll be using Case (Value) Classes in all our examples here, but it’s not technically required to do so (although very convinient). You could implement a Value Class using a normal class with one val parameter instead, but using case classes is usually the best way to go.
value class is for avoid allocate object. As your example:
class FullName(val value: String) extends AnyVal
class FirstName(val value: String) extends AnyVal {
def toFullName: FullName = new FullName(value + " lastName")
}
FirstName bytecode:
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);
As see the FirstName$.toFullName$extension:(Ljava/lang/String;)Ljava/lang/String
, it's calling the FirstName
companion object for new FullName
and the return type also be optimized to String
by compiler.
FirstName companion object:
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
so:
As our goal with Value Classes is to avoid having to allocate the entire value object, and instead work directly on the wrapped value we have to stop using instance methods — as they would force us into having an instance of the Wrapper (Meter) class. What we can do instead is promoting the instance method, into an extension method, which we’ll store in the companion object of Meter, and instead of using the value: Double field of the instance, we’ll pass in the Double value each time we’ll be calling the extension method.
Reference:
Upvotes: 1
Reputation: 13117
Value classes are not meant to provide a way to do stack allocation instead of heap allocation. That is generally something you have no control over. Instead, they are designed to prevent extra object allocations that would occur when otherwise creating a "wrapper" class.
In your case of Firstname
, extending AnyVal
means that when you do something like
val x = new Firstname("Bob")
no instance of FirstName
is actually created and x
is actually just a String
at runtime (assuming you don't do one of the things the docs describe that would force an allocation, such as pattern matching). But in no way does AnyVal
change how the wrapped String
gets allocated.
Upvotes: 5