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)
我想得到输出:你好
该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 实现略有不同。
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会被阻塞,直到框架退出。
如果您确实对整个过程感到好奇,那么我建议您按照源代码进行操作。
[警告:我不是JavaFX专家。我在某些观点上很可能是错的。]
\n抽象javafx.application.Application类没有被实例化。正如您正确地注意到的那样,实例化抽象类将违反Java 语言规范。
JavaFX 框架使用反射的魔力来实例化具体类的对象,即Application.
非静态方法init、start和stop定义的方法Application由 的子类实现Application。JavaFX 框架正在执行您在具体子类上编写的那些实现。
因此,不涉及具体类的对象的具体方法abstract。
让我们回顾一下现代JavaFX / OpenJFX应用程序的生命周期。
\nmain当任何类型的 Java 应用程序启动时,JVM 都会main在专用于该应用程序的线程中执行指定的方法。
launch在 JavaFX 应用程序中,main应该编写该方法来调用两个重载static方法中的一个javafx.application.Application.launch。包含此方法的类main必须 (a) 扩展自javafx.application.Application,或(b) 调用launch必须传递扩展自Class的类的对象。Application
该launch方法在您的应用程序的每个生命周期中仅调用一次。并且该launch方法直到app\xe2\x80\x99s生命结束才返回。
像这样的东西:
\npublic class TimeApplication extends Application\n{\n public static void main ( String[] args )\n {\n javafx.application.Application.launch ();\n }\n}\nRun Code Online (Sandbox Code Playgroud)\ninitApplication通过子类加载并构造该类后,Application#init会立即自动调用该方法。此调用发生在后台线程上,如下面的示例代码所示。该后台线程不是原始应用程序线程,也不是下面讨论的JavaFX应用程序线程。
该方法的默认实现不执行任何操作。您可以选择提供覆盖此方法的实现。该覆盖是您的钩子,用于在建立任何 GUI之前执行对您的应用程序至关重要的初始化。
\n您可能希望使用init覆盖来建立必要的资源,并验证操作环境。例如,您可能想要验证预期的外部服务是否确实可用,例如用户身份验证器、Web 服务、消息队列和数据库服务器。您的init方法可能会分出线程来准备用户稍后需要的信息。
但是您无法构造Stageor Sceneininit,因为 JavaFX 显示框架尚未建立。您可以构造其他 JavaFX 对象。您可以在单独线程上运行的方法中设置Scene&Stage及其内容。start
简单的例子:
\npublic 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}\nRun Code Online (Sandbox Code Playgroud)\nstart该init方法(您的重写或默认的no-op)完成执行后,Application#start将自动调用 then 。JavaFX 框架自动构造并传递一个Stage代表应用程序初始窗口的对象。
引用Javadoc:
\n\n\n所有 JavaFX 应用程序的主要入口点。该
\nstart方法在init方法返回后且系统准备好应用程序开始运行后调用。
该Application#start方法是为您的应用程序建立初始 GUI 的地方。您构造一个Scene对象,并将其分配给传递的Stage对象。
此方法在为 JavaFX GUI 事件处理而建立的新线程上执行。该线程在 Javadoc 中称为JavaFX 应用程序线程。
\n请注意,start被标记为abstract,这意味着该类Application不包含此方法的实现。这意味着您的子类Application 必须包含此方法的实现 \xe2\x80\x94\xc2\xa0if 省略您的应用程序将无法编译。请注意与没有实现的方法init和具有实现的方法的对比(一个实现碰巧什么也不做,但仍然是一个实现)。因此,为&编写重写对我们来说是可选的,但编写方法是必需的。stopabstractApplicationinitstopstart
stop对应的Application#start是Application#stop. 当应用程序要退出时,JavaFX 框架会自动调用该方法。
默认实现不执行任何操作。您可以选择重写此方法作为钩子,以关闭资源并执行适合应用程序结束的其他清理工作。
\n该方法在JavaFX 应用程序线程上执行。
\npackage 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}\nRun Code Online (Sandbox Code Playgroud)\n请注意,跨线程调用时,输出System.out不一定按时间顺序出现。包括时间戳并研究它们,以验证操作的顺序。
输出示例:
\nThe 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\nRun Code Online (Sandbox Code Playgroud)\n这是我的生命周期活动表。请注意,init和start是同时运行,而不是顺序运行,在单独的线程上运行。
| 顺序 | 主 线程 | JavaFX-启动器 线程 | JavaFX 应用程序线程 线程 |
|---|---|---|---|
| 1 | main方法 | ||
| 2 | 您的来电(块)Application.launch | ||
| 3 | 通过JavaFX框架反射构建您的子类Application | ||
| 4 | 要么无操作,要么你覆盖Application#init | 您的实施(您建立& )Application#startSceneStage | |
| 5 | init可能会无限期地继续运行 | \xe2\x80\xa2 用户事件 \xe2\x80\xa2 动画时间线 | |
| 6 | \xe2\x80\xa2 用户关闭最后一个窗口 \xe2\x80\xa2 或者, Platform.exit在此线程或任何其他线程上调用 | ||
| 7 | 要么无操作,要么你覆盖Application#stop | ||
| 8 | Application.launch回报 |
序列步骤 #3 是 JavaFX 框架使用反射来实例化Application.
\n\n在javaFX中Application是一个抽象类。
\n
是的。抽象意味着您打算编写Application. 因此我们TimeApplication extends Application在上面的代码中就有了。
\n\n一些抽象方法在主类中被重写,该主类扩展了抽象应用程序类,并且还具有静态 launch() 方法
\n
是的,这两种launch方法都是static。所以我们用这样的语法来调用它们:Application.launch()。没有对对象的引用。
我们通常从方法中进行static调用。所以我们在另一个调用中调用 \xe2\x80\x94\xc2\xa0 不涉及任何对象。Application.launch()staticmainstaticstatic
\n\nlaunch() 方法从主类中的 main 方法调用。
\n
请注意,该main方法可能位于也可能不位于从 继承的类中Application。
JavaFX 框架检查包含调用的类Application.launch。如果该类扩展了类abstract,Application那么您可以使用launch仅带有单个参数 的方法args。如果您安排了对 的调用Application.launch发生在某个不是 的子类的类中Application,那么您必须使用另一种launch方法来传递表示属于 的子类的Class类的对象。Application
可能让您感到困惑的是缺少对 的子类的对象的引用Application。在上面的示例代码中,我们从未看到这样的实例化:
TimeApplication timeApp = new TimeApplication() ;\nRun Code Online (Sandbox Code Playgroud)\n我们看不到这样的实例化的原因是因为 JavaFX 中的一些 \xe2\x80\x9cmagic\xe2\x80\x9d 。JavaFX 框架使用反射来代表我们实例化我们的子类。
\n我们的子类的构建发生在幕后,在我们的视野中没有任何参考。我们永远不会得到对子类对象的引用。请参阅javafx 中有关问题获取应用程序实例的讨论。如果您确实想要引用您的子类的对象,请参阅此答案Application中的解决方法。
\n\n现在 launch() 方法如何调用这些抽象方法,并且对于这些调用,执行主类中的重写方法?
\n
调用static导致Application.launch具体子类(TimeApplication在我们的示例中)继承抽象Application类。所以我们TimeApplication在内存中有一个对象,一个对象,但是我们看不到任何对它的引用。init该对象具有、start和的重写方法stop。因此,我们有一个具体的对象,而不是抽象的,具有在不同线程上的不同时间点执行的常规重写方法。您提到的 Java 规则得到了正确遵循,如这两个项目符号中所述:
\n\n我知道非静态方法不能从静态方法调用
\n
我们不会从静态上下文中调用非静态方法。非静态init、start和stop方法正在我们的具体子类的对象上执行,TimeApplication该子类是自动为我们实例化的,而不是由我们实例化。init对这些方法、start、 和 的调用stop也是为我们发出的,而不是由我们发出的。
\n\n并且不可能创建抽象类的实例。
\n
你是对的 \xe2\x80\x94 没有由抽象类创建实例Application。JavaFX 使用反射的魔力在幕后创建了Application. TimeApplication通过反射,JavaFX在上面的示例中创建了一个实例。该框架实例化了扩展抽象类的具体子类的对象。
| 归档时间: |
|
| 查看次数: |
242 次 |
| 最近记录: |