我想了解抽象类应用程序的 launch() 方法在 JavaFX 中如何工作

0 java abstract-class static-methods javafx abstract-methods

在javaFX中Application是一个抽象类。在这个抽象类中,有一些抽象方法在扩展抽象应用程序类的主类中被重写,并且在抽象类应用程序中也有静态 launch() 方法。launch() 方法从主类中的 main 方法调用。现在 launch() 方法如何调用这些抽象方法,并且对于这些调用,执行主类中的重写方法?请帮助我了解这个程序实际上是有效的。我知道非静态方法不能从静态方法调用,并且不可能创建抽象类的实例。抽象方法不能是静态方法。

我不想创建主类的对象。因为launch()方法不知道主类的名称是什么。

给我写一段java代码,这个过程很好地说明了这一点。像这样

public class main extends Application{
    public static void main(String[] args)
        launch();                       // launch() was a static method in Application class
    }
    @override
    public void init(){                 // init() was an abstract method in Application class
        System.out.println("hello");
    }
}

public abstract class Application {
    public static void launch(){
        init();
    }
    public abstract void init();
}
Run Code Online (Sandbox Code Playgroud)

我想得到输出:你好

Sla*_*law 6

Application#launch(String...)方法定义为:

启动独立应用程序。该方法通常是从main方法中调用的。[...]。这相当于调用 launch 的方法的直接封闭类在launch(TheClass.class, args)where 。TheClass

因此,它确实通过获取“调用者类”来知道主类的名称。然后,它通过反射创建应用程序类的实例,并根据需要调用init()和方法。start(Stage)

Application 实现1的实例化以及init()start(Stage)和的调用stop()都是JavaFX 生命周期的一部分(由 记录Application。您不实例化应用程序类,也不调用这些方法。它由 JavaFX 处理。

请注意,您不应该拥有自己的Application课程。将类命名为与您正在使用的框架中的类相同的名称不仅会令人困惑,而且对您没有任何帮助。javafx.application.Application如果您想要正常的 JavaFX 生命周期,您需要扩展。


1. 类本身不是Application被实例化的,因为它是抽象的。但是,您必须提供一个具体的子类(即实现)作为项目的一部分,并且实例化的正是该类。当然,从 JavaFX 的角度来看,它只知道它有一个Application. 但这是基本的多态性在起作用。


JavaFX 是如何做到的?

下面是一些演示该过程的非 JavaFX 代码。请注意,它被大大简化了,并且与实际的 JavaFX 实现略有不同。

package com.example;

import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;

public abstract class Application {

  private static final AtomicBoolean LAUNCHED = new AtomicBoolean();
  private static Throwable error;

  public static void launch(Class<? extends Application> appClass, String... args) {
    Objects.requireNonNull(appClass);
    Objects.requireNonNull(args);
    if (!LAUNCHED.compareAndSet(false, true)) {
      throw new IllegalStateException("already launched");
    }

    CountDownLatch startLatch = new CountDownLatch(1);

    Thread eventThread = new Thread(() -> {
      try {
        Application instance = appClass.getConstructor().newInstance();
        instance.start(args);
      } catch (Throwable error) {
        Application.error = error;
      } finally {
        startLatch.countDown();
      }
    });
    eventThread.setName("Application Event Thread");
    eventThread.start();

    try {
      startLatch.await();
    } catch (InterruptedException ex) {
      throw new RuntimeException(ex);
    }

    if (error != null) {
      throw new RuntimeException("Exception in Application start method", error);
    }
  }

  @SuppressWarnings("unchecked")
  public static void launch(String... args) {
    StackWalker walker = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
    Class<?> callerClass = walker.getCallerClass();
    if (!Application.class.isAssignableFrom(callerClass)) {
      throw new IllegalStateException("caller class is not a subclass of com.example.Application");
    }
    launch((Class<? extends Application>) callerClass, args);
  }

  public abstract void start(String[] args);
}
Run Code Online (Sandbox Code Playgroud)

然后其他一些代码可以做到:

package com.example;

import java.util.Arrays;

public class Main extends Application {

  public static void main(String[] args) {
    launch(args);
  }

  @Override
  public void start(String[] args) {
    System.out.println("Started! args = " + Arrays.toString(args));
  }
}
Run Code Online (Sandbox Code Playgroud)

无需将课程传递给launch.

如前所述,这与 JavaFX 实际所做的相比大大简化,并且它使用了一些不同的 API。例如:

  • 我用过java.lang.StackWalker(在Java 9中添加)。JavaFX 目前(截至 21-ea)仍然使用通过Thread.currentThread().getStackTrace().

  • JavaFX应用程序线程不是像上面的“应用程序事件线程”那样手动创建的。相反,FX 线程由 JavaFX 框架管理,真正的启动代码使用Platform#runLater(Runnable).

    • 然而,真正的 JavaFX 代码确实执行了与我为用于调用该init()方法的“JavaFX-Launcher Thread”执行的操作类似的操作。

    • 我的代码有“应用程序事件线程”实例化该类,调用start(String[]),然后死亡(一旦start返回)。这不是JavaFX 应用程序线程的工作方式,它本质上是在循环中运行。

  • 真正的 JavaFX 代码必须处理潜在的Preloaders。

  • 真正的 JavaFX 代码创建一个Stage传递给start(Stage)方法的实例;我通过传递给定的命令行参数数组来“模拟”这一点。但请注意,在真实的 JavaFX 应用程序中,如果将命令行参数传递给launch,那么这些参数将封装在一个Parameters对象中(并进行基本解析)。

  • 在真实的 JavaFX 代码中,调用的线程launch会被阻塞,直到框架退出。

如果您确实对整个过程感到好奇,那么我建议您按照源代码进行操作


Bas*_*que 5

[警告:我不是JavaFX专家。我在某些观点上很可能是错的。]

\n

太长了;博士

\n

抽象javafx.application.Application没有被实例化。正如您正确地注意到的那样,实例化抽象类将违反Java 语言规范

\n

JavaFX 框架使用反射的魔力来实例化具体类的对象,即Application.

\n

非静态方法initstartstop定义的方法Application由 的子类实现Application。JavaFX 框架正在执行您在具体子类上编写的那些实现。

\n

因此,不涉及具体类的对象的具体方法abstract

\n

生命周期

\n

让我们回顾一下现代JavaFX / OpenJFX应用程序的生命周期。

\n

main

\n

当任何类型的 Java 应用程序启动时,JVM 都会main在专用于该应用程序的线程中执行指定的方法。

\n

launch

\n

在 JavaFX 应用程序中,main应该编写该方法来调用两个重载static方法中的一个javafx.application.Application.launch。包含此方法的类main必须 (a) 扩展自javafx.application.Application(b) 调用launch必须传递扩展Class的类的对象。Application

\n

launch方法在您的应用程序的每个生命周期中仅调用一次。并且该launch方法直到app\xe2\x80\x99s生命结束才返回。

\n

像这样的东西:

\n
public class TimeApplication extends Application\n{\n    public static void main ( String[] args )\n    {\n        javafx.application.Application.launch ();\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

init

\n

Application通过子类加载并构造该类后,Application#init会立即自动调用该方法。此调用发生在后台线程上,如下面的示例代码所示。该后台线程不是原始应用程序线程,也不是下面讨论的JavaFX应用程序线程

\n

该方法的默认实现不执行任何操作。您可以选择提供覆盖此方法的实现。该覆盖是您的钩子,用于在建立任何 GUI之前执行对您的应用程序至关重要的初始化。

\n

您可能希望使用init覆盖来建立必要的资源,并验证操作环境。例如,您可能想要验证预期的外部服务是否确实可用,例如用户身份验证器、Web 服务、消息队列和数据库服务器。您的init方法可能会分出线程来准备用户稍后需要的信息。

\n

但是您无法构造Stageor Sceneininit,因为 JavaFX 显示框架尚未建立。您可以构造其他 JavaFX 对象。您可以在单独线程上运行的方法中设置Scene&Stage及其内容。start

\n

简单的例子:

\n
public class TimeApplication extends Application\n{\n    @Override\n    public void init ( ) throws Exception  // On original app thread.\n    {\n        if ( ! userAuthServiceIsAvailable ( ) )\n        {\n            // \xe2\x80\xa6 handle failure \xe2\x80\xa6 \n        }\n    }\n\n    public static void main ( String[] args )  // On original app thread.\n    {\n        Application.launch ( );\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

start

\n

init方法(您的重写或默认的no-op)完成执行后,Application#start将自动调用 then 。JavaFX 框架自动构造并传递一个Stage代表应用程序初始窗口的对象。

\n

引用Javadoc:

\n
\n

所有 JavaFX 应用程序的主要入口点。该start方法在init方法返回后且系统准备好应用程序开始运行后调用。

\n
\n

Application#start方法是为您的应用程序建立初始 GUI 的地方。您构造一个Scene对象,并将其分配给传递的Stage对象。

\n

此方法在为 JavaFX GUI 事件处理而建立的新线程上执行。该线程在 Javadoc 中称为JavaFX 应用程序线程

\n

请注意,start被标记为abstract,这意味着该类Application包含此方法的实现。这意味着您的子类Application 必须包含此方法的实现 \xe2\x80\x94\xc2\xa0if 省略您的应用程序将无法编译。请注意与没有实现的方法init和具有实现的方法的对比(一个实现碰巧什么也不做,但仍然是一个实现)。因此,为&编写重写对我们来说是可选的,但编写方法是必需的。stopabstractApplicationinitstopstart

\n

stop

\n

对应的Application#startApplication#stop. 当应用程序要退出时,JavaFX 框架会自动调用该方法。

\n

默认实现不执行任何操作。您可以选择重写此方法作为钩子,以关闭资源并执行适合应用程序结束的其他清理工作。

\n

该方法在JavaFX 应用程序线程上执行。

\n

示例代码

\n
package work.basil.example;\n\nimport javafx.application.Application;\nimport javafx.scene.Scene;\nimport javafx.scene.control.Button;\nimport javafx.scene.layout.VBox;\nimport javafx.stage.Stage;\n\nimport java.time.Instant;\n\npublic class TimeApplication extends Application\n{\n    @Override\n    public void init ( ) throws Exception\n    {\n        System.out.println ( "The init method is running on thread " + Thread.currentThread ( ).threadId ( ) + " \xe2\x80\x9c" + Thread.currentThread ( ).getName ( ) + "\xe2\x80\x9d" + " at " + Instant.now ( ) );\n        if ( ! this.userAuthServiceIsAvailable ( ) )\n        { /* \xe2\x80\xa6 handle failure \xe2\x80\xa6 */ }\n    }\n\n    @Override\n    public void start ( Stage stage )\n    {\n        System.out.println ( "The start method is running on thread " + Thread.currentThread ( ).threadId ( ) + " \xe2\x80\x9c" + Thread.currentThread ( ).getName ( ) + "\xe2\x80\x9d" + " at " + Instant.now ( ) );\n\n        // Scene\n        Button button = new Button ( );\n        button.setText ( "Tell time" );\n        button.setOnAction ( ( event ) -> System.out.println ( Instant.now ( ) ) );\n\n        VBox vbox = new VBox ( button );\n        Scene scene = new Scene ( vbox );\n\n        // Stage\n        stage.setTitle ( "Time keeps on slipping, slipping, slipping" );\n        stage.setHeight ( 300 );\n        stage.setWidth ( 500 );\n\n        stage.setScene ( scene );\n        stage.show ( );\n    }\n\n    @Override\n    public void stop ( ) throws Exception\n    {\n        System.out.println ( "The stop method is running on thread " + Thread.currentThread ( ).threadId ( ) + " \xe2\x80\x9c" + Thread.currentThread ( ).getName ( ) + "\xe2\x80\x9d" + " at " + Instant.now ( ) );\n    }\n\n    private boolean userAuthServiceIsAvailable ( )\n    {\n        return true;\n    }\n\n    public static void main ( String[] args )\n    {\n        System.out.println ( "The main method is beginning on thread " + Thread.currentThread ( ).threadId ( ) + " \xe2\x80\x9c" + Thread.currentThread ( ).getName ( ) + "\xe2\x80\x9d" + " at " + Instant.now ( ) );\n        Application.launch ( );\n        System.out.println ( "The main method is ending on thread " + Thread.currentThread ( ).threadId ( ) + " \xe2\x80\x9c" + Thread.currentThread ( ).getName ( ) + "\xe2\x80\x9d" + " at " + Instant.now ( ) );\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

请注意,跨线程调用时,输出System.out不一定时间顺序出现。包括时间戳并研究它们,以验证操作的顺序。

\n

输出示例:

\n
The main method is beginning on thread 1 \xe2\x80\x9cmain\xe2\x80\x9d at 2023-07-31T19:57:37.255140Z\nThe init method is running on thread 27 \xe2\x80\x9cJavaFX-Launcher\xe2\x80\x9d at 2023-07-31T19:57:37.262522Z\nThe start method is running on thread 24 \xe2\x80\x9cJavaFX Application Thread\xe2\x80\x9d at 2023-07-31T19:57:37.268091Z\n2023-07-31T19:57:39.206604Z\n2023-07-31T19:57:39.391998Z\n2023-07-31T19:57:39.524548Z\nThe stop method is running on thread 24 \xe2\x80\x9cJavaFX Application Thread\xe2\x80\x9d at 2023-07-31T19:57:42.402692Z\nThe main method is ending on thread 1 \xe2\x80\x9cmain\xe2\x80\x9d at 2023-07-31T19:57:42.404950Z\n
Run Code Online (Sandbox Code Playgroud)\n

生命周期总结

\n

这是我的生命周期活动表。请注意,initstart是同时运行,而不是顺序运行,在单独的线程上运行。

\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n
顺序
线程
JavaFX-启动器
线程
JavaFX 应用程序线程
线程
1main方法
2您的来电(块)Application.launch
3通过JavaFX框架反射构建您的子类Application

4要么无操作,要么你覆盖Application#init您的实施(您建立& )Application#start
SceneStage
5init可能会无限期地继续运行\xe2\x80\xa2 用户事件
\xe2\x80\xa2 动画时间线
6\xe2\x80\xa2 用户关闭最后一个窗口
\xe2\x80\xa2 或者,Platform.exit在此线程或任何其他线程上调用
7要么无操作,要么你覆盖Application#stop
8Application.launch回报
\n
\n

序列步骤 #3 是 JavaFX 框架使用反射来实例化Application.

\n

你的问题

\n
\n

在javaFX中Application是一个抽象类。

\n
\n

是的。抽象意味着您打算编写Application. 因此我们TimeApplication extends Application在上面的代码中就有了。

\n
\n

一些抽象方法在主类中被重写,该主类扩展了抽象应用程序类,并且还具有静态 launch() 方法

\n
\n

是的,这两种launch方法都是static。所以我们用这样的语法来调用它们:Application.launch()。没有对对象的引用。

\n

我们通常从方法中进行static调用。所以我们在另一个调用中调用 \xe2\x80\x94\xc2\xa0 不涉及任何对象。Application.launch()staticmainstaticstatic

\n
\n

launch() 方法从主类中的 main 方法调用。

\n
\n

请注意,该main方法可能位于也可能不位于从 继承的类中Application

\n

JavaFX 框架检查包含调用的类Application.launch。如果该类扩展了类abstractApplication那么您可以使用launch仅带有单个参数 的方法args。如果您安排了对 的调用Application.launch发生在某个不是 的类的类中Application,那么您必须使用另一种launch方法来传递表示属于 的子类的Class类的对象。Application

\n

可能让您感到困惑的是缺少对 的子类的对象的引用Application。在上面的示例代码中,我们从未看到这样的实例化:

\n
TimeApplication timeApp = new TimeApplication() ;\n
Run Code Online (Sandbox Code Playgroud)\n

我们看不到这样的实例化的原因是因为 JavaFX 中的一些 \xe2\x80\x9cmagic\xe2\x80\x9d 。JavaFX 框架使用反射来代表我们实例化我们的子类。

\n

我们的子类的构建发生在幕后,在我们的视野中没有任何参考。我们永远不会得到对子类对象的引用。请参阅javafx 中有关问题获取应用程序实例的讨论。如果您确实想要引用您的子类的对象,请参阅此答案Application中的解决方法。

\n
\n

现在 launch() 方法如何调用这些抽象方法,并且对于这些调用,执行主类中的重写方法?

\n
\n

调用static导致Application.launch具体子类(TimeApplication在我们的示例中)继承抽象Application类。所以我们TimeApplication在内存中有一个对象,一个对象,但是我们看不到任何对它的引用。init该对象具有、start和的重写方法stop。因此,我们有一个具体的对象,而不是抽象的,具有在不同线程上的不同时间点执行的常规重写方法。您提到的 Java 规则得到了正确遵循,如这两个项目符号中所述:

\n
    \n
  • \n
    \n

    我知道非静态方法不能从静态方法调用

    \n
    \n
  • \n
\n

我们不会从静态上下文中调用非静态方法。非静态initstartstop方法正在我们的具体子类的对象上执行,TimeApplication该子类是自动为我们实例化的,而不是由我们实例化。init对这些方法、start、 和 的调用stop也是为我们发出的,而不是由我们发出的。

\n
    \n
  • \n
    \n

    并且不可能创建抽象类的实例。

    \n
    \n
  • \n
\n

你是对的 \xe2\x80\x94 没有由抽象类创建实例Application。JavaFX 使用反射的魔力在幕后创建了Application. TimeApplication通过反射,JavaFX在上面的示例中创建了一个实例。该框架实例化了扩展抽象类的具体子类的对象。

\n