Самый простой способ реализации этого
Вместо того, чтобы пытаться водить лошадь с тележкой, почему бы не просто следовать стандартным жизненным циклом JavaFX? Другими словами, сделайте свой класс Main
подклассом Application
, получите файл в методе start()
, а затем продолжите (в фоновом потоке) с остальной частью приложения?
public class Main extends Application {
@Override
public void init() {
// make sure we don't exit when file chooser is closed...
Platform.setImplicitExit(false);
}
@Override
public void start(Stage primaryStage) {
File file = null ;
FileChooser fc = new FileChooser();
while(file == null){
file = fc.showOpenDialog(primaryStage);
}
final File theFile = file ;
new Thread(() -> runApplication(theFile)).start();
}
private void runApplication(File file) {
// run your application here...
}
}
Что случилось с вашим кодом
Если вы действительно хотите Main
класс отделиться от класса JavaFX Application
(который на самом деле не имеет смысла: как только вы решили для использования JavaFX FileChooser
вы решили написать приложение JavaFX, поэтому класс запуска должен быть подклассом Application
), тогда он становится немного сложным. В вашем коде есть несколько проблем, некоторые из которых рассматриваются в других ответах. Основная проблема, как показано в ответе Фабиана, заключается в том, что вы ссылаетесь на FileGetter.isReady
из нескольких потоков без обеспечения жизнеспособности. Это точно проблема, затронутая в статье Джоша Блоха Effective Java (пункт 66 во втором издании).
Еще одна проблема с вашим кодом заключается в том, что вы не сможете использовать FileGetter
более одного раза (вы не можете позвонить launch()
более одного раза), что может не быть проблемой в вашем коде сейчас, но почти наверняка будет в какой-то момент с этим приложением развиваться. Проблема в том, что у вас смешанные две проблемы: запуск инструментария FX и извлечение файла из FileChooser
. Первое можно сделать только один раз; второй должен быть написан для повторного использования.
И, наконец, ваш цикл
while(isReady == false){
isReady = FileGetter.getIsReady();
}
очень плохая практика: он проверяет isReady
флаг так быстро, как это возможно. При некоторых (довольно необычных) обстоятельствах он может даже помешать потоку приложения FX использовать какие-либо ресурсы для запуска. Это нужно просто заблокировать, пока файл не будет готов.
Как исправить без Main
JavaFX Application
Итак, опять же, только если у вас есть действительно насущная необходимость сделать это, я бы сначала создать класс, который только несет ответственность за запуск инструментарий FX. Что-то вроде:
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.stage.Stage;
public class FXStarter extends Application {
private static final AtomicBoolean startRequested = new AtomicBoolean(false);
private static final CountDownLatch latch = new CountDownLatch(1);
@Override
public void init() {
Platform.setImplicitExit(false);
}
@Override
public void start(Stage primaryStage) {
latch.countDown();
}
/** Starts the FX toolkit, if not already started via this method,
** and blocks execution until it is running.
**/
public static void startFXIfNeeded() throws InterruptedException {
if (! startRequested.getAndSet(true)) {
new Thread(Application::launch).start();
}
latch.await();
}
}
Теперь создайте класс, который доставит вам файл. Это должно обеспечить запуск инструментария FX, используя предыдущий класс. Эта реализация позволяет вызывать getFile()
из любого потока:
import java.io.File;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import javafx.application.Platform;
import javafx.stage.FileChooser;
public class FileGetter {
/**
** Retrieves a file from a JavaFX File chooser. This method can
** be called from any thread, and will block until the user chooses
** a file.
**/
public File getFile() throws InterruptedException {
FXStarter.startFXIfNeeded() ;
if (Platform.isFxApplicationThread()) {
return doGetFile();
} else {
FutureTask<File> task = new FutureTask<File>(this::doGetFile);
Platform.runLater(task);
try {
return task.get();
} catch (ExecutionException exc) {
throw new RuntimeException(exc);
}
}
}
private File doGetFile() {
File file = null ;
FileChooser chooser = new FileChooser() ;
while (file == null) {
file = chooser.showOpenDialog(null) ;
}
return file ;
}
}
и, наконец, ваш Main
просто
import java.io.File;
public class Main {
public static void main(String[] args) throws InterruptedException {
File file = new FileGetter().getFile();
// proceed...
}
}
Опять же, это довольно сложный; Я не вижу причин не просто использовать стандартный жизненный цикл приложения FX для этого, как в самом первом блоке кода в ответе.
Сделайте свой 'isReady' в FileGetter' volatile'. – Codebender
@Codebender Это сработало! Благодаря! Просто любопытно, как примечание, по какой-то причине это сработало, когда я включил System.out.println (isReady); в цикле while. Кто-нибудь знает причину этого? –
Цикл while в FileGetter # start состоит в том, чтобы гарантировать, что пользователь выбирает файл, тогда как цикл while в главном классе должен гарантировать, что код не будет продолжен до того, как будет выбран файл. Но да, я мог бы использовать одно и то же условное утверждение в обоих, но я считаю, что это та же концепция. –