2017-01-22 2 views
2

Учитывая следующую функцию:Класс значения w/Строковый внутренний тип?

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 «S, которые являются подтипами AnyRef, т.е. java.lang.Object, которые выделяют в кучу, как я понимаю.

Итак, в приведенном выше примере, то есть с использованием 2 классов значений, в чем преимущество использования их по сравнению с case class?

ответ

4

Классы значений не предназначены для обеспечения распределения стека вместо распределения кучи. Это, как правило, то, что вы не контролируете. Вместо этого они предназначены для предотвращения дополнительных распределений объектов, которые могут возникнуть при создании класса «обертка».

В вашем случае Firstname, простираясь AnyVal означает, что когда вы делаете что-то вроде

val x = new Firstname("Bob") 

не экземпляр FirstName фактически не создается и x на самом деле просто String во время выполнения (если вы не выполните одно о тех документах, которые описывают это, что приведет к распределению, например сопоставлению с образцом). Но ни в коем случае AnyVal не изменит, как распределяется обернутый String.

+0

Как примечание стороны, я хотел бы добавить, что она стирается при компиляции, так что вы можете оказаться в ситуации, где два метода принимают два разных класса значений, но он не компилируется, так как метод в конечном итоге имеет одну и ту же подпись. –

1

Во-первых, значение класса равно случае класса расширить AnyVal только с одним параметром. см. value class.

Мы будем использовать классы Case (Value) во всех наших примерах здесь, но это не требуется технически (хотя и очень удобно). Вы можете реализовать класс значений, используя обычный класс с одним параметром val, но использование классов 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, чтобы избежать того, чтобы выделить весь объект значения, и вместо этого работать непосредственно на обернутой значения мы должны прекратить использовать методы экземпляра - поскольку они заставили бы нас иметь экземпляр класса Wrapper (Meter). Вместо этого мы продвигаем метод экземпляра в метод расширения, который мы будем хранить в сопутствующем объекте Meter, и вместо использования значения: Двойное поле экземпляра мы передадим в двойном значении каждый мы будем называть метод расширения.

Ссылка:

  1. http://ktoso.github.io/scala-types-of-types/#value-class
  2. http://docs.scala-lang.org/overviews/core/value-classes.html
Смежные вопросы