Scala. Может ли класс case с одним полем быть классом значений?
Scala 2.10 введены классы значений. Они очень полезны для написания типичного кода. Кроме того, есть некоторые ограничения, некоторые из них будут обнаружены компилятором, а некоторые потребуют выделения во время выполнения.
Я хочу создать классы значений, используя синтаксис case class
, чтобы разрешить создание без-нового синтаксиса и удобство для пользователя toString
. Нет сопоставления с образцом, так как это требует выделения.
Итак, возникает вопрос: будет ли использование синтаксиса case class
требовать выделения класса значений?
Ответы
Ответ 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 еще не объединен. Оставайтесь с нами.)