2016-08-18 3 views
1

В основном, я пытаюсь написать простую программу, которая позволяет пользователю выбрать файл. К сожалению, JFileChooser через Swing немного устарел, поэтому я пытаюсь использовать JavaFX FileChooser для этого. Цель состоит в том, чтобы запустить FileGetter как поток, перенести данные файла в Основной класс и продолжить оттуда.Boolean wont Обновление от Object.getBoolean();

Главный класс:

package application; 
import java.io.File; 
import javafx.application.Application; 
import javafx.stage.Stage; 
import javafx.scene.Scene; 
import javafx.scene.layout.BorderPane; 


public class Main { 

    public static void main(String[] args) { 
     Thread t1 = new Thread(new FileGetter()); 
     FileGetter fg = new FileGetter(); 
     t1.start(); 
     boolean isReady = false; 
     while(isReady == false){ 
      isReady = FileGetter.getIsReady();  
     } 
     File file = FileGetter.getFile(); 

     System.out.println(file.getAbsolutePath()); 
     ... 

    } 
} 

FileGetter Класс:

package application; 

import java.io.File; 
import javafx.application.Application; 
import javafx.application.Platform; 
import javafx.stage.FileChooser; 
import javafx.stage.Stage; 
import javafx.scene.Scene; 
import javafx.scene.layout.BorderPane; 


public class FileGetter extends Application implements Runnable { 

    static File file; 
    static boolean isReady = false; 


    @Override 
    public void start(Stage primaryStage) { 
     try { 

      FileChooser fc = new FileChooser(); 
      while(file == null){ 
      file = fc.showOpenDialog(primaryStage); 
      } 
      isReady = true; 
      Platform.exit(); 
     } catch(Exception e) { 
      e.printStackTrace(); 
     } 
    } 
    @Override 
    public void run() { 
     launch(); 
    } 

    public static boolean getIsReady(){ 
     return isReady; 
    } 

    public static File getFile(){ 
     return file; 
    } 

} 

Проблема заключается в том, что значение isReady в то время цикла не обновляется, чтобы верно, когда пользователь выбрал файл (причину Я хочу, чтобы код в Main продолжался с файлом, установленным в null).

Любая помощь, альтернативные предложения или объяснения относительно того, почему это происходит, очень ценится!

+4

Сделайте свой 'isReady' в FileGetter' volatile'. – Codebender

+0

@Codebender Это сработало! Благодаря! Просто любопытно, как примечание, по какой-то причине это сработало, когда я включил System.out.println (isReady); в цикле while. Кто-нибудь знает причину этого? –

+0

Цикл while в FileGetter # start состоит в том, чтобы гарантировать, что пользователь выбирает файл, тогда как цикл while в главном классе должен гарантировать, что код не будет продолжен до того, как будет выбран файл. Но да, я мог бы использовать одно и то же условное утверждение в обоих, но я считаю, что это та же концепция. –

ответ

2

Самый простой способ реализации этого

Вместо того, чтобы пытаться водить лошадь с тележкой, почему бы не просто следовать стандартным жизненным циклом 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 для этого, как в самом первом блоке кода в ответе.

+0

Какие преимущества будет использовать ваш код, который по-прежнему имеет класс 'main' над моим кодом (если я обновляю его для хранения данных File в моем классе' main' и обновляю логические и типы файлов на летучие) (не пытаясь дискредитировать ваш код или что-то еще, просто интересуются преимуществами)? –

+0

Не знаете, что вы подразумеваете под "обновить его, чтобы сохранить данные файла в моем классе' Main'. Как бы вы начали использовать инструментарий FX? –

+0

Просто создайте переменную файла в моем классе 'main' и сохраните выбор файла пользователя в там (я думаю, вы ранее ссылались на то, что я не мог продолжать ссылаться на файл через 'FileGetter.getFile()', когда поток остановился, поэтому я храню его в переменной File?). –

2

В этом коде

while(isReady == false){ 
     isReady = FileGetter.getIsReady();  
} 

нет ничего, что собирается изменить состояние FileGetter «s isReady к true

+0

Подождите, когда 'file' станет ненулевым в' FileGetter # start'? Я мог бы просто что-то упустить. –

+0

Да, я пробовал это, но по какой-то причине это не работает ... И isReady должен установить значение true, когда пользователь выбирает файл из FileChooser. –

+1

Это ужасно оживленное ожидание. – chrylis

3

Модель памяти Java не требует значения переменных быть одинаковыми в разных за исключением особых условий.

Что происходит здесь, так это то, что поток FileGetter обновляет значение в собственной памяти, доступ к которому осуществляется только из этого потока, но ваш основной поток не видит обновленного значения, так как он видит только версию переменной хранящийся в его собственной памяти, который отличается от потока FileGetter. Каждый из потоков имеет свою собственную копию поля в памяти, что отлично соответствует спецификации java.

Чтобы исправить это, вы можете просто добавить модификатор volatile к isReady:

static volatile boolean isReady = false; 

, который убеждается обновленное значение будет видно из основного потока.

Кроме того, я рекомендую уменьшить количество создаваемых вами экземпляров FileGetter. В вашем коде создаются 3 экземпляра, но используется только 1.

Thread t1 = new Thread(() -> Application.launch(FileGetter.class)); 
t1.start(); 
... 
Смежные вопросы