2016-08-18 37 views
1

非常多,我試圖編寫一個簡單的程序,讓用戶選擇一個文件。不幸的是,通過Swing的JFileChooser有點過時了,所以我正在嘗試使用JavaFX FileChooser。目標是將FileGetter作爲線程運行,將文件數據傳輸到主類,然後從那裏繼續。布爾值不會從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在while循環的值不更新爲true(原因我的目的是防止Main中的代碼繼續將文件設置爲空)。

任何幫助,替代建議或解釋爲什麼發生這種情況非常感謝!

+4

在FileGetter volatile中創建'isReady'。 – Codebender

+0

@Codebender工作!謝謝!只是好奇作爲一個方面的說明,出於某種原因,當我包括一個System.out.println(isReady)時它工作;在while循環中。任何人都知道這個的原因? –

+0

FileGetter#start中的while循環是爲了確保用戶選擇文件,而Main Class中的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的子類),那麼它會有點棘手。代碼中存在幾個問題,其中一些問題在其他答案中得到解決。如Fabian的答案所示,主要問題是您從多個線索中引用FileGetter.isReady而不確保活力。這正是Josh Bloch的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'類在我的代碼上(如果我更新它來在我的'main'類中存儲文件數據並更新布爾類型和文件類型爲volatile)有什麼好處(不要試圖抹黑你的代碼代碼或任何東西,只是好奇的好處)? –

+0

不確定你的意思是「更新它以將文件數據存儲在我的Main類中」。你將如何啓動FX工具包? –

+0

只需在我的'main'類中創建一個文件變量並將用戶的文件選擇存儲在在那裏(我認爲你之前引用的是,當線程停止時,我無法通過'FileGetter.getFile()'繼續引用該文件,因此我將它存儲在File變量中?)。 –

2

在這段代碼

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

沒有什麼打算FileGetter的的isReady狀態更改爲true

+0

等待'File'在FileGetter#start中變爲非空值時怎麼辦?我可能會錯過一些東西。 –

+0

是的,我試過了,但由於某種原因它不起作用...而當用戶從FileChooser中選擇一個文件時,isReady應該設置爲true。 –

+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(); 
...