Val и объект внутри класса scala?
В чем разница между объявлением поля как val
, lazy val
и object
внутри класса scala, как в следующем фрагменте:
class A
class B {
val a1 = new A { def foo = 1 }
object a2 extends A { def foo = 1 }
lazy val a3 = new A { def foo = 1 }
}
Ответы
Ответ 1
В первом, любой введенный код выполняется, как только будет создан класс B. В последнем, однако, пока вы фактически не используете объект, он не будет создан.
Здесь вы можете увидеть разницу:
class A { println("Creating a new A") }
class B {
val a1 = new A { println("a1"); def foo = 1 }
object a2 extends A { println("a2"); def foo = 1 }
}
scala> val b = new B
Creating a new A
a1
b: B = [email protected]
scala> b.a2.foo
Creating a new A
a2
res0: Int = 1
Есть также скрытые различия в том, какие имена созданных файлов .class и так; и, конечно, у них разные типы.
Ответ 2
Одно существенное отличие состоит в том, что val может быть переопределен, а объекты не могут.
class C extends B {
override val a1 = new A { def foo = 2 }
override object a2 extends A { def foo = 2 }
}
приводит к:
<console>:9: error: overriding object a2 in class B of type object C.this.a2;
object a2 cannot be used here - classes and objects cannot be overridden
override object a2 extends A { def foo = 2 }
Ответ 3
Я не уверен, что aioobe признал значение своего ответа, но разные типы на самом деле представляют собой критическую разницу между vals
и objects
. В частности, val
и lazy val
имеют структурный тип (например, A{def foo: Int}
), а object
имеет одноэлементный тип. В результате вызовы метода foo
на val
включают отражение, а вызовы метода foo
на object
не выполняют:
class A
class B {
val a1 = new A { def foo = printStack }
object a2 extends A { def foo = printStack }
lazy val a3 = new A { def foo = printStack }
def printStack() =
new Exception().getStackTrace take 3 foreach println
}
scala> val b = new B
b: B = [email protected]
scala> b.a1.foo // the val
line124$object$$iw$$iw$B.printStack(<console>:12)
line124$object$$iw$$iw$B$$anon$1.foo(<console>:7)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
scala> b.a2.foo // the object
line124$object$$iw$$iw$B.printStack(<console>:12)
line124$object$$iw$$iw$B$a2$.foo(<console>:8)
line128$object$$iw$$iw$.<init>(<console>:9)
scala> b.a3.foo // the lazy val
line124$object$$iw$$iw$B.printStack(<console>:12)
line124$object$$iw$$iw$B$$anon$2.foo(<console>:9)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
Ответ 4
Я полагаю, что одно отличие состоит в том, что a1
будет иметь один подтип A
, а a2
будет другого подтипа A
, а именно a2.type
.
scala> class A
defined class A
scala> val a1 = new A {def foo = 1}
a1: A{def foo: Int} = [email protected]
scala> object a2 extends A {def foo = 1}
defined module a2
scala> a1
res0: A{def foo: Int} = [email protected]
scala> a2
res1: a2.type = [email protected]
scala>
Ответ 5
Другим важным отличием является то, что объекты знают свое имя, а val - нет.
Ответ 6
Первое практическое отличие состоит в том, что ленивые vals и объекты ленивы, тогда как vals стремятся.
Основное различие между объектами и ленивыми vals заключается в том, что объект с точки зрения языков считается "одноэлементным", который с точки зрения jvm обычно рассматривается как статический член. Определение объекта в данном примере не может быть переоценено, как показали другие, по той же причине, что статические члены не могут быть переопределены: без привязки к экземпляру нет мыслимого способа поиска виртуальной функции.
object Foo { object Bar extends A; }
не похож на следующий код java:
class Foo {
private static class Bar extends A{}
public static Bar Bar = new Bar;
}
Если в приведенном выше примере, если был определен подкласс C extends Foo, он не сможет переопределить определение Bar. Статический массив экземпляров в Java будет доступен как Foo.Bar. C.Bar не означает то же самое, что (новый C).Bar. Я мог бы быть немного отсюда, я на самом деле не пытался декомпилировать scala код, это просто пример, иллюстрирующий общую концепцию объектов как статических членов.
lazy vals могут быть немного менее эффективными. В прошлый раз, когда я проверил, они были реализованы путем сохранения скрытого поля в классе, который отслеживал, какие ленивые валы были инициализированы. Поддержание этого поля требует блокировки, что может вызвать проблемы с производительностью.
Одним из основных практических различий между ленивым val и объектом является обработка отказа:
Если у меня есть:
class Foo() { throw new Exception("blah!"); }
object Stuff { object Bar extends Foo { val x = "hi" } }
Stuff.Bar
//exception "blah!" thrown.
Stuff.Bar.x
//NoClassDefFoundError: Could not initialize Stuff$Bar$
тогда как если:
object Stuff2 { lazy val Bar = new Foo() { val x = "hi" } }
Stuff2.Bar
// "blah!"
Stuff2.Bar.x
// "blah!"
"NoClassDefFoundError" может быть действительно запутанным, и поскольку он не является исключением, он может разбить код обработки ошибок, который (соответственно) ловит/регистрирует "Исключение", но позволяет распространять ошибки. Я мог бы даже рассмотреть такую ошибку на языке scala, так как этот прецедент на самом деле указывает на исключительное условие, а не на ошибку JVM. Я видел NoClassDefFoundErrors при доступе к объектам, зависящим от внешних ресурсов (например, подключений к базе данных или файлов на диске). Только первый доступ регистрирует основную причину, поэтому для правильной отладки такой проблемы обычно требуется перезапуск сервера приложений.
Ответ 7
Это не структурный тип: val a = new A {def foo = 1}
Создает уникальный анонимный подкласс; a.foo вызывает foo в этом классе.
x здесь структурный тип: def bar (x: {def bass: Int})
x.bass будет интроспекции x (неизвестного типа), чтобы найти метод с именем "бас". Он будет работать с рыбой или музыкальными инструментами.;)
Одно из различий между ленивым val и объектом заключается в следующем:
var someA = (new B).a3
someA = (new B).a3 // ok
var anotherA = (new B).a2
anotherA = = (new B).a2 // compile error