Scala 对象初始化

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/24221601/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-22 06:20:59  来源:igfitidea点击:

Scala object initialization

scala

提问by maks

I have an object:

我有一个对象:

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

I use it in a trait:

我在一个特征中使用它:

trait SomeTratit {
  val a = A.init
}

Then I use trait it in a class:

然后我在一个类中使用 trait it:

class SomeClass extends SomeTrait

When I instantiate SomeClass with new SomeClassI expect to see "Hello"in the console, but don't get it. Why?

当我实例化 SomeClass 时,new SomeClass我希望"Hello"在控制台中看到,但没有得到它。为什么?

Also I expect to see "Hello" only once while instantiating several objects, but don't see any "Hello" in console

此外,我希望在实例化多个对象时只看到一次“Hello”,但在控制台中看不到任何“Hello”

回答by lastland

I don't know if this should be considered a bug, but here's HOW this happened.

我不知道这是否应该被视为错误,但这是如何发生的。

If you look at the generated bytecode of object A, it's like this:

如果你查看生成的字节码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
}

You can find the println("Hello")you expected in the constructor of A$, but there's nothing in init(). This is perfectly correct because it is you intention that println("Hello")would not be executed every time when you called init(), right?

您可以println("Hello")在 的构造函数中找到您所期望的A$,但在init(). 这是完全正确的,因为您的意图println("Hello")是每次调用时都不会执行init(),对吗?

However, the problem comes in SomeClass:

然而,问题来了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
}

What?! There's nothing in SomeClass.a(), either! But think about it, it's also perfectly reasonable: why would I bother calling it since there's literally nothing in A$.init()and it returns nothing (i.e. no field to be set)? Why not just optimize that out (Maybe Java did this optimization. Or Scala did. I don't know)? However, this optimization also erase the only appearance of A$, which means no constructor will be called for A$. That's why the Hellonever appeared.

什么?!里面也什么都没有SomeClass.a()!但想想看,这也是完全合理的:我为什么要费心调用它,因为实际上什么都没有A$.init(),它也不返回任何内容(即没有要设置的字段)?为什么不直接优化它(也许 Java 做了这个优化。或者 Scala 做了。我不知道)?但是,这种优化也消除了 的唯一外观A$,这意味着不会调用 构造函数A$。这就是为什么Hello从来没有出现过。

However, if you change the code a little bit so that the bytecode of init()will not be empty, like this:

但是,如果您稍微更改代码,使 的字节码init()不会为空,如下所示:

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

which compiles to the following bytecode:

编译为以下字节码:

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

in which case you will find the bytecode for SomeClass.a()like this:

在这种情况下,你会找到这样的字节码SomeClass.a()

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

where the field was set in SomeTrait$class:

该字段的设置位置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()is called to set this field, so in this case you can expect Helloto appear.

A$.init()被调用来设置这个字段,所以在这种情况下你可以期待Hello出现。

回答by dk14

If you really need such side-effect in your code - you can see "hello" when accessing A directly (so just mention A in your trait). This is a feature of objects - they don't initialize while they don't really need to:

如果您的代码中确实需要这种副作用 - 您可以在直接访问 A 时看到“hello”(因此只需在您的特征中提及 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 = A$@1be2f5d8

You may notice simmilar behavior for type members:

您可能会注意到类型成员的类似行为:

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 = A$@73f5477e

scala>

回答by ale64bit

Seems to me like it's related with the compiler optimization. For example, the code as given doesn't show the "Hello"message, because the compiler seems to infer that as A.initis of type Unit, the local val aof SomeTraitwill always end up with the same constant value. So the compiler just assign it and that's it.

在我看来,它与编译器优化有关。例如,给定的代码没有显示"Hello"消息,因为编译器似乎推断出A.init类型是Unit,本地val aSomeTrait总是以相同的常量值结束。所以编译器只是分配它,就是这样。

Nonetheless, if you make the evaluation of initnot eager, by making the field initat Object Aalso lazy, then the message is actually printed:

尽管如此,如果您init通过使字段initat Object Aalso进行不渴望的评估lazy,那么实际上会打印消息:

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

Moreover, even if you just do the same but force the initfunction in Object Ato return some meaningful value, then the message will also be printed, since the compiler cannot infer the resulting value to be constant.

此外,即使您只是执行相同的操作但强制init函数Object A返回一些有意义的值,也会打印该消息,因为编译器无法将结果值推断为常量。

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

  trait SomeTrait {
    val a = A.init
  }

  class SomeClass extends SomeTrait

This is my interpretation, but I can be missing something. Hope it helps.

这是我的解释,但我可能会遗漏一些东西。希望能帮助到你。

回答by Robert Simmons Jr.

When you initialize a in the trait all you did is copy a reference to function A.init into SomeTrait.a. You could then do a() to invoke the function and see the expected output.

当你在 trait 中初始化 a 时,你所做的就是将一个对函数 A.init 的引用复制到 SomeTrait.a 中。然后,您可以执行 a() 来调用该函数并查看预期的输出。

回答by Miuler

init is the type Unit, therefore assess the function is in vain.

init 是类型 Unit,因此评估函数是徒劳的。