Search code examples
javajavafxabstract-classstatic-methodsabstract-methods

I Want to Understand How abstract class Application's launch() method work in JavaFX


in javaFX Application is an abstract class. In this abstract class there have some abstract methods which are overridden in main class which extends abstract Application class and also have static launch() method in abstract class Application. The launch() method calls from the main method in main class. Now how is it possible launch() method calls these abstract methods and for these calls overridden methods in main class are executes? please help me to understand this procedure actually works. I know that non-static method can't invoked from static method and it is not possible to create instance of an abstract class. abstract method can't be a static method.

I don't want to create object of main class. Because the launch() method don't know what is the name of main class.

Write me a java code where this procedure illustrate well. like this

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();
}

I want to get output : hello


Solution

  • The Application#launch(String...) method is defined to:

    Launch a standalone application. This method is typically called from the main method. [...]. This is equivalent to launch(TheClass.class, args) where TheClass is the immediately enclosing class of the method that called launch.

    So, it does know the name of the main class by getting the "caller class". It then creates an instance of the application class via reflection and invokes the init() and start(Stage) methods as appropriate.

    The instantiation of the Application implementation1 and the invoking of init(), start(Stage), and stop(), are all part of the JavaFX life-cycle (documented by Application). You don't instantiate the application class, nor do you call those methods. It's handled by JavaFX.

    Note you should not have your own Application class. Not only is it confusing to name a class the same as a class from the framework you're using, but it does nothing for you. You need to extend javafx.application.Application if you want the normal JavaFX life-cycle.


    1. It's not the Application class itself that's instantiated, as it's abstract. However, you must provide a concrete subclass (i.e., implementation) as part of your project, and it's that class that's instantiated. Of course, from the point of view of JavaFX, it only knows it has an instance of Application. But that is basic polymorphism at work.


    How Does JavaFX Do It?

    Here's some non-JavaFX code demonstrating the process. Note it's vastly simplified and does things slightly differently from the actual JavaFX implementation.

    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);
    }
    

    Then some other code can do:

    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));
      }
    }
    

    Without having to pass the class to launch.

    As noted, this is vastly simplified from what JavaFX actually does, and it uses some different APIs. For instance:

    • I used java.lang.StackWalker (added in Java 9). JavaFX currently (as of 21-ea) still uses code that manually walks the stack trace via Thread.currentThread().getStackTrace().

    • The JavaFX Application Thread is not created manually like I did for the "Application Event Thread" above. Instead, the FX thread is managed by the JavaFX framework, and the real launch code makes use of the internal version of Platform#runLater(Runnable).

      • However, the real JavaFX code does do something similar to what I did for the "JavaFX-Launcher Thread" used to invoke the init() method.

      • My code has the "Application Event Thread" instantiate the class, call start(String[]), and then die (once start returns). That's not how the JavaFX Application Thread works, which essentially runs in a loop.

    • The real JavaFX code has to deal with potential Preloaders.

    • The real JavaFX code creates a Stage instance that it passes to the start(Stage) method; I "simulate" that by passing the given command line arguments array. Though note in a real JavaFX application, if you pass the command line arguments to launch, then those arguments are encapsulated in a Parameters object (and rudimentarily parsed).

    • In the real JavaFX code, the thread that calls launch is blocked until the framework exits.

    If you're truly curious about the entire process, then I recommend you follow the source code.