Подтвердить что ты не робот

Scala. Может ли класс case с одним полем быть классом значений?

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

Я хочу создать классы значений, используя синтаксис case class, чтобы разрешить создание без-нового синтаксиса и удобство для пользователя toString. Нет сопоставления с образцом, так как это требует выделения.

Итак, возникает вопрос: будет ли использование синтаксиса case class требовать выделения класса значений?

4b9b3361

Ответ 1

У вас может быть класс case, который является классом значений. Как видно из приведенного ниже примера, нет создания объекта. За исключением, конечно, неизбежного бокса, если вы подниметесь к Any.

Вот небольшая часть кода scala

class ValueClass(val value:Int) extends AnyVal

case class ValueCaseClass(value:Int) extends AnyVal

class ValueClassTest {

  var x: ValueClass = new ValueClass(1)

  var y: ValueCaseClass = ValueCaseClass(2)

  def m1(x:ValueClass) = x.value

  def m2(x:ValueCaseClass) = x.value
}

И байт-код, который не содержит ни малейшего следа двух классов значений.

Compiled from "ValueClassTest.scala"
public class ValueClassTest {
  public int x();
    Code:
       0: aload_0       
       1: getfield      #14                 // Field x:I
       4: ireturn       

  public void x_$eq(int);
    Code:
       0: aload_0       
       1: iload_1       
       2: putfield      #14                 // Field x:I
       5: return        

  public int y();
    Code:
       0: aload_0       
       1: getfield      #21                 // Field y:I
       4: ireturn       

  public void y_$eq(int);
    Code:
       0: aload_0       
       1: iload_1       
       2: putfield      #21                 // Field y:I
       5: return        

  public int m1(int);
    Code:
       0: iload_1       
       1: ireturn       

  public int m2(int);
    Code:
       0: iload_1       
       1: ireturn       

  public rklaehn.ValueClassTest();
    Code:
       0: aload_0       
       1: invokespecial #29                 // Method java/lang/Object."<init>":()V
       4: aload_0       
       5: iconst_1      
       6: putfield      #14                 // Field x:I
       9: aload_0       
      10: iconst_2      
      11: putfield      #21                 // Field y:I
      14: return        
}

Ответ 2

Чтобы продолжить этот вопрос, Wojciech Langiewicz предлагает хороший пример Значение класс, используемый в качестве класса case.

Вместо:

case class Player(id: Int, gameCash: Int, gameCoins: Int, energy: Int)

Wojciech определяет:

case class Player(id: PlayerId, gameCash: GameCash, gameCoins: GameCoins, energy: Energy)

с классами case (без выделения дополнительных объектов в куче):

case class PlayerId(id: Int) extends AnyVal
case class GameCash(value: Int) extends AnyVal
case class GameCoins(value: Int) extends AnyVal
case class Energy(value: Int) extends AnyVal

При создании классов case, содержащих только один параметр, вы должны добавить extends AnyVal, чтобы позволить компилятору Scala запускать больше оптимизаций - в основном проверка типов будет выполняться только на этапе компиляции, но во время выполнения только объекты объекта будет создан базовый тип, что приведет к меньшему издержкам памяти.

Добавление пользовательских типов в определенные пункты нашего кода не только улучшило читаемость, но также позволило нам сделать меньше ошибок - выгрузить часть проверки, которая в противном случае должна была бы быть сделана в тестах (или вообще не выполнена) компилятору, Вы также можете сразу увидеть ошибки в своей среде IDE или редакторе.

Поскольку теперь каждый компонент класса Player сам по себе является отдельным типом, его также очень легко добавить новые операторы, которые в противном случае, вероятно, должны были бы быть добавлены неявно, что загрязнило бы большую область.

Ответ 3

По крайней мере, для "allow create-without-new-syntax" вы можете использовать простые старые методы или object с помощью метода apply. toString также не является проблемой (если я правильно помню), хотя вы должны определить ее самостоятельно, если вы не используете класс case.
BTW, язык позволяет определять классы case, которые расширяют AnyVal, см. http://docs.scala-lang.org/overviews/core/value-classes.html

Ответ 4

См. раздел обзорная документация в разделе "При необходимости распределения".

В классах классов появляется специальное уведомление: "Примечание. На практике вы можете использовать классы case и/или методы расширения для более чистого синтаксиса".

Но, как уже сказал rudiger-klaehn, пример caveat предоставляет AnyVal, где ожидается Any:

package anything

// the caveat from the overview
trait Distance extends Any
case class Meter(val value: Double) extends AnyVal with Distance

class Foo {
  def add(a: Distance, b: Distance): Distance = Meter(2.0)
}

object Test extends App {
  val foo = new Foo
  Console println foo.add(Meter(3.4), Meter(4.3))
}

Показывая, что: javap -app исправлен в последнем 2.11:

scala> :javap -app anything.Test$
  public final void delayedEndpoint$anything$Test$1();
    flags: ACC_PUBLIC, ACC_FINAL
    Code:
      stack=7, locals=1, args_size=1
         0: aload_0       
         1: new           #63                 // class anything/Foo
         4: dup           
         5: invokespecial #64                 // Method anything/Foo."<init>":()V
         8: putfield      #60                 // Field foo:Lanything/Foo;
        11: getstatic     #69                 // Field scala/Console$.MODULE$:Lscala/Console$;
        14: aload_0       
        15: invokevirtual #71                 // Method foo:()Lanything/Foo;
        18: new           #73                 // class anything/Meter
        21: dup           
        22: ldc2_w        #74                 // double 3.4d
        25: invokespecial #78                 // Method anything/Meter."<init>":(D)V
        28: new           #73                 // class anything/Meter
        31: dup           
        32: ldc2_w        #79                 // double 4.3d
        35: invokespecial #78                 // Method anything/Meter."<init>":(D)V
        38: invokevirtual #84                 // Method anything/Foo.add:(Lanything/Distance;Lanything/Distance;)Lanything/Distance;
        41: invokevirtual #88                 // Method scala/Console$.println:(Ljava/lang/Object;)V
        44: return        
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0      45     0  this   Lanything/Test$;
      LineNumberTable:
        line 11: 0
        line 12: 11
}

Есть бокс, как мы были предупреждены.

(Обновление: на самом деле PR для исправления -app еще не объединен. Оставайтесь с нами.)