在Scala中,想要通过主类运行程序,一般我们并没有声明main方法,而是通过单例对象继承App来实现的。如果单例对象和同名特质同时出现在同一作用域则会报如下警告: Test的伴生”类”是一个特质,无法生成静态代理,所以导致单例对象Test无法成为一个可运行的程序。
/*
Warning:(140, 8) Test has a main method with parameter type Array[String], but Test will not be a runnable program.
Reason: companion is a trait, which means no static forwarder can be generated.
object Test extends App {}
^
*/
trait Test {}
object Test extends App {}
在解答这个问题之前,先来了解一下什么是静态代理,以单例对象为例来展开。
object Test { def foo = 2 }
编译之后会生成两个类Test.class
, Test$.class
(合成类)。
allen:Desktop allen$ javap Test
Compiled from "Test.scala"
public final class Test {
public static int foo();
}
allen:Desktop allen$ javap Test$
Compiled from "Test.scala"
public final class Test$ {
public static final Test$ MODULE$;
public static {};
public int foo();
}
在Programming In Scala一书中对于单例对象有这样一段解释:
For a singleton object named App, the compiler produces a Java class named App$.This class has all the methods and fields of the Scala singleton object. The Java class also has a single static field named MODULE$ to hold the one instance of the class that is created at run time.
对于单例对象App,编译器会生成一个Java类App$,它包含单例对象中所有的方法和属性,同时还有一个静态的属性MODULE$
引用了一个运行时生成的App$实例。具体到上面的代码Test$
为编译器生成的Java类包含了单例对象中的foo
方法和静态属性MODULE$
。
如果只有这个类的话,那么我们在Java中我们就需要这样调用Test$.MODULE$.foo()
, 它要求我们了解Test$
内部结构并且也不友好。 所以Test
出现了, 这样在Java中可以
通过Test.foo()
来调用(Test$中的方法),Test.class中的静态方法实际上就是前面提到的静态代理。
public class MainTest {
public static void main(String[] args) {
int r = Test$.MODULE$.foo();
int r1 = Test.foo();
System.out.println(r == r1); // true
}
}
当同一作用域内同时出现了伴生类A和伴生对象A时,编译时除了在A.class
中生成静态代理之外,还会添加伴生类本来的方法。
object Test { def foo = 2 }
class Test {}
$ scalac -Ylog:jvm Test.scala
[log jvm] missingHook(package <root>, android): <none>
[log jvm] Adding static forwarders from 'class Test' to implementations in 'object Test'
[log jvm] No mirror class for module with linked class: Test
回到开始的案例,当同一作用域内同时出现了特质和单例对象时,编译器会尝试在Test.class
中添加静态代理,但此时trait Test
编译之后实际上是Java中的接口,而接口是不能定义静态方法的。
所以Test.class
中并没有对应的main
方法,所以不能成为一个可运行的程序。