Kevin Meredith
Kevin Meredith

Reputation: 41909

Value Class w/ String Inner Type?

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

Answers (2)

chengpohi
chengpohi

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:

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

Upvotes: 1

Dan Simon
Dan Simon

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

Related Questions