晚上想做个小应用,不想用 丑陋的swing(确信)。于是试了试JavaFX。MVC 模式,XML 和 Scene Builder 设计 UI,设计出的 UI 颜值还高。这样一看还是比较熟悉的嘛,好像和 Android 的套路差不多。

当然还是要用Kotlin。不过在写Main函数的时候就遇到问题了,简直气炸(

三次尝试

首先,在 Java 里的代码应该是这样的:

1
2
3
4
5
6
public class Main extends Application {
    //...
    public static void main(String[] args) {
        launch(args);
    }
}

没有任何问题。

那么在 Kotlin 中,我首先想到的是 Top level 的 Main 函数:

1
2
3
4
5
fun main(args: Array<String>) = Application.launch(*args)

class Main : Application() {
	//...
}

结果会报错:

Exception in thread "main" java.lang.RuntimeException: Error: class moe.leer.MainKt is not a subclass of javafx.application.Application,很明显我猜想 这个Main函数是另一个类的 Main 函数,而不是 Main 类的。

利用 IDEA 的 Kotlin Bytecode 工具可以看到字节码,再把字节码反编译,可以看到 Java 的实现。

1533215976909

Java 的实现确实是再创建一个类,来实现Top level 的 Main 函数:)

接下来尝试了companion object :

1
2
3
4
5
6
7
class Main : Application() {  
	//...
    companion object {
    	@JvmStatic fun main(args: Array<String>) {
      	launch(*args)
    }
}

结果类似与第一个尝试,还是相同的报错:java.lang.RuntimeException: Error: class moe.leer.Main$Companion is not a subclass of javafx.application.Application。那也可以肯定 companion object 是创建了内部类来实现的。

最后出场的是object,Kotlin 里单例模式的实现。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10

object Main : Application() {

  private lateinit var stage: Stage
  private lateinit var rootLayout: BorderPane

  @JvmStatic fun main(args: Array<String>) {
    launch(*args)
  }
}

这次的错误信息终于和前两次不同了(笑)

1
2
3
4
java.lang.reflect.InvocationTargetException
	at com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:389)
	at sun.launcher.LauncherHelper$FXHelper.main(LauncherHelper.java:767)
Caused by: java.lang.RuntimeException: Unable to construct Application instance: class moe.leer.Main

那么应该是反射的时候,调用Main方法失败了,关于反射的我也是浅尝辄止,所以解释不了了。

面向 stack overflow

wordless

Kotlin. Basic JavaFX application,于是就有了。Stack overflow 真好啊(

至于最后一次为什么会报错,大佬是这样说的: 

an object declaration in Kotlin is a singleton, so it only has one instance constructed by calling the private constructor when the class is initialized. JavaFX is trying to call the constructor of the class reflectively but fails because the constructor is private as it should be.

不过我反编译了一下好像没有看到所说的private constructor,所以还是一知半解了。

但是解决方法和这个没关系,只是调用了另一个launch函数而已,不过最终都是反射来调用里面的方法吧。

可以这样写:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class Main : Application() {

  private lateinit var stage: Stage
  private lateinit var rootLayout: BorderPane

  companion object {
    @JvmStatic fun main(args: Array<String>) {
      launch(Main::class.java, *args)
    }
  }
}

你也可以用 Top level 的 Main 函数:

1
fun main(args: Array<String>) = Application.launch(Main::class.java)

🎉🎉🎉 可以慢慢玩JavaFX了。