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

Scala инициализация объекта

У меня есть объект:

object A {
 val init = println("Hello")
}

Я использую его в признаке:

trait SomeTratit {
  val a = A.init
}

Затем я использую его в классе:

class SomeClass extends SomeTrait

Когда я создаю экземпляр SomeClass с помощью new SomeClass, я ожидаю увидеть "Hello" в консоли, но не получаю его. Почему?

Также я ожидаю увидеть "Hello" только один раз при создании нескольких объектов, но не вижу никаких "Hello" в консоли

4b9b3361

Ответ 1

Если вам действительно нужен такой побочный эффект в вашем коде, вы можете увидеть "привет" при непосредственном доступе к A (так что просто укажите A в своей характеристике). Это особенность объектов - они не инициализируются, пока они действительно не нужны:

scala> object A {val a = println("hello"); val b = (); println("bye") }
defined module A

scala> A.b

scala> A.a

scala> A
hello
bye
res10: A.type = [email protected]

Вы можете заметить simmilar поведение для членов типа:

scala> object A {println("init"); type T = Int }
defined module A

scala> val i: A.T = 5
i: A.T = 5

scala> A
init
res15: A.type = [email protected]

scala>

Ответ 2

Я не знаю, следует ли это считать ошибкой, но здесь, КАК это произошло.

Если вы посмотрите на сгенерированный байт-код object A, он выглядит так:

public final class A$ {
  public static final A$ MODULE$;

  private final scala.runtime.BoxedUnit init;

  public static {};
    Code:
       0: new           #2                  // class A$
       3: invokespecial #12                 // Method "<init>":()V
       6: return

  public void init();
    Code:
       0: return

  private A$();
    Code:
       0: aload_0
       1: invokespecial #16                 // Method java/lang/Object."<init>":()V
       4: aload_0
       5: putstatic     #18                 // Field MODULE$:LA$;
       8: aload_0
       9: getstatic     #23                 // Field scala/Predef$.MODULE$:Lscala/Predef$;
      12: ldc           #25                 // String Hello
      14: invokevirtual #29                 // Method scala/Predef$.println:(Ljava/lang/Object;)V
      17: getstatic     #34                 // Field scala/runtime/BoxedUnit.UNIT:Lscala/runtime/BoxedUnit;
      20: putfield      #36                 // Field init:Lscala/runtime/BoxedUnit;
      23: return
}

Вы можете найти println("Hello"), который вы ожидали в конструкторе A$, но в init() ничего нет. Это совершенно правильно, потому что вы намерены, что println("Hello") не будет выполняться каждый раз, когда вы вызываете init(), правильно?

Однако проблема возникает в SomeClass:

public class SomeClass implements SomeTrait {
  public void a();
    Code:
       0: return

  public void SomeTrait$_setter_$a_$eq(scala.runtime.BoxedUnit);
    Code:
       0: return

  public SomeClass();
    Code:
       0: aload_0
       1: invokespecial #21                 // Method java/lang/Object."<init>":()V
       4: aload_0
       5: invokestatic  #27                 // Method SomeTrait$class.$init$:(LSomeTrait;)V
       8: return
}

Что?! Там ничего в SomeClass.a()! Но подумайте об этом, это также вполне разумно: почему я буду называть его, потому что там буквально ничего в A$.init() и он ничего не возвращает (т.е. Нет поля для установки)? Почему бы просто не оптимизировать это (возможно, Java сделала эту оптимизацию. Или Scala сделал. Не знаю)? Однако эта оптимизация также стирает единственный вид A$, что означает, что для A$ не будет вызываться конструктор. Вот почему Hello никогда не появлялся.

Однако, если вы немного измените код, чтобы байт-код init() не был пустым, например:

object A {
  val init = { println("Hello"); 1 }
}

который компилируется в следующий байт-код:

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

в этом случае вы найдете байт-код для SomeClass.a() следующим образом:

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

где поле было установлено в SomeTrait$class:

public abstract class SomeTrait$class {
  public static void $init$(SomeTrait);
    Code:
       0: aload_0
       1: getstatic     #13                 // Field A$.MODULE$:LA$;
       4: invokevirtual #17                 // Method A$.init:()I
       7: invokeinterface #23,  2           // InterfaceMethod SomeTrait.SomeTrait$_setter_$a_$eq:(I)V
       12: return
}

A$.init() вызывается для установки этого поля, поэтому в этом случае вы можете ожидать появления Hello.

Ответ 3

Мне кажется, что это связано с оптимизацией компилятора. Например, в приведенном ниже коде не отображается сообщение "Hello", потому что компилятор, кажется, заключает, что, поскольку A.init имеет тип Unit, локальный val a of SomeTrait всегда будет содержать такое же постоянное значение. Поэтому компилятор просто назначает его и что он.

Тем не менее, если вы не оцениваете оценку init, сделав поле init в Object A также lazy, тогда сообщение действительно будет напечатано:

object A {
  lazy val init = println("Hello")
}

Кроме того, даже если вы просто делаете то же самое, но заставляете функцию init в Object A возвращать какое-то значимое значение, тогда сообщение также будет напечатано, поскольку компилятор не может вывести результирующее значение как постоянное.

  object A {
    val init = { println("Hello"); 1} // returns some integer
  }

  trait SomeTrait {
    val a = A.init
  }

  class SomeClass extends SomeTrait

Это моя интерпретация, но я могу что-то упустить. Надеюсь, что это поможет.

Ответ 4

Когда вы инициализируете a в свойстве, все, что вы сделали, копирует ссылку на функцию A.init в SomeTrait.a. Затем вы можете сделать() для вызова функции и увидеть ожидаемый результат.

Ответ 5

init - это тип Unit, поэтому оцените, что функция напрасна.