2010-10-13 54 views
69

我想避免(大部分)Netbeans 6.9.1的警告,並且我的'Leaking this in constructor'警告存在問題。在構造函數中發生泄漏警告

我明白這個問題,調用構造函數中的方法並傳遞「this」是危險的,因爲「this」可能尚未完全初始化。

在我的單例類中很容易修復警告,因爲構造函數是私有的,只能從同一個類中調用。

舊代碼(簡化):

private Singleton() { 
    ... 
    addWindowFocusListener(this); 
} 

public static Singleton getInstance() { 

    ... 
    instance = new Singleton(); 
    ... 
} 

新的代碼(簡化):

private Singleton() { 
    ... 
} 

public static Singleton getInstance() { 

    ... 
    instance = new Singleton(); 
    addWindowFocusListener(instance); 
    ... 
} 

此修復程序無法正常工作,如果構造函數是公共的,可以從其他類調用。這怎麼可能解決下面的代碼:

public class MyClass { 

    ... 
    List<MyClass> instances = new ArrayList<MyClass>(); 
    ... 

    public MyClass() { 
    ... 
    instances.add(this); 
    } 

} 

我當然希望它不需要修改使用這個類,我所有的代碼的補丁(通過調用例如一個init方法)。

+0

不相關的問題直接,但爲什麼'MyClass'有其自身的'List'。即使這種關係在許多情況下是合理的,爲什麼它會將它自己添加到'List'中。想象一下內存中產生的數據結構。 – CKing 2015-05-04 04:06:48

+0

@CKing,我的猜測是OP在實例中鍵入實例變量時忽略了'static'。 – ryvantage 2015-08-23 19:19:01

+1

使用靜態創建方法。 – 2015-10-19 15:40:20

回答

42

既然你務必把你instances.add(this)在構造函數結束時,你應該 恕我直言,是安全的告訴編譯器簡單地抑制警告 (*)。就其性質而言,警告並不一定意味着有什麼問題,而只是需要您的注意。

如果您知道自己在做什麼,則可以使用@SuppressWarnings註釋。像特雷爾在他的評論中提到,下面的註釋做它,因爲NetBeans 6.9.1:

@SuppressWarnings("LeakingThisInConstructor") 

(*)更新:作爲Isthar和謝爾蓋指出,存在以下情況:「泄漏」的構造函數的代碼可以看看完全安全(如你的問題),但事實並非如此。有更多的讀者可以批准嗎?我正在考慮刪除這個答案的原因。

+0

這不是一個標準的java警告,這是一個Netbeans警告。因此添加@SuppressWarnings沒有幫助。 – asalamon74 2010-10-13 08:30:38

+0

@asalamon:我明白了。 Tant pis。而netbeans又是否提供了關閉特定警告的選項? Eclipse的確如此。 – chiccodoro 2010-10-13 11:53:09

+0

@asalamon:明白了。 netbeans甚至會忽略@SuppressWarning(「all」)?取決於解釋「所有」也可以包括netbeans警告。對不起,沒有更精確的,我從來沒有與NetBeans的工作... – chiccodoro 2010-10-13 15:00:09

12

,最好的選擇你有:

  • 另一類提取您的WindowFocusListener部分(也可能是內部或匿名)。最好的解決方案,這樣每個班級都有特定的目的。
  • 忽略警告消息。

使用單例作爲漏泄構造函數的解決方法並不十分有效。

+0

第一個例子中的類已經是單例。 – asalamon74 2010-10-13 08:35:05

+0

@ asalamon74,但我想第二個不是。想要一個帶有公共構造函數的單例是沒有意義的,所以我猜你試圖將單例模式作爲解決方法。 – 2010-10-13 08:38:10

+0

當然,我不想要一個具有公共構造函數的Singleton。我只是想展示一個能夠解決問題的例子(這是Singleton),另一個例子是我無法修復它(這是非Singleton例子)。 – asalamon74 2010-10-13 08:41:52

12

這是一個很好的例子,其中創建類的實例的工廠會有幫助。如果工廠負責創建您的類的實例,那麼您將擁有一個集中的位置,在該位置調用構造函數,並且向代碼添加所需的init()方法將很簡單。

關於您的直接解決方案,我建議您將任何將this泄漏到您的構造函數最後一行的調用,然後在「證明」安全的情況下用註釋將其壓制。

在IntelliJ IDEA的,你可以禁止這種警告與右線以上的評論:
//noinspection ThisEscapedInObjectConstruction

1

註解@SuppressWarnings(「LeakingThisInConstructor」)只適用於一類不構造本身。

妄想我會建議: 創建私有方法init(){/ *在這裏使用* /}並從構造函數中調用它。 NetBeans不會警告你。

+3

這不是一個解決方案。這避免了真正的問題。 – initialZero 2011-05-27 21:43:18

+0

@initialZero - 老實說,我的答案也一樣。重新閱讀所有這些答案,我認爲科林的答案是最好的。 – chiccodoro 2012-11-06 07:12:01

2

使用嵌套類(如Colin建議)可能是您最好的選擇。這裏是僞代碼:

private Singleton() { 
    ... 
} 

public static Singleton getInstance() { 

    ... 
    instance = new Singleton(); 
    addWindowFocusListener(new MyListener()); 
    ... 

    private class MyListener implements WindowFocusListener { 
    ... 
    } 
} 
2

不需要單獨的監聽器類。

public class Singleton implements WindowFocusListener { 

    private Singleton() { 
     ... 
    }  

    private void init() { 
     addWindowFocusListener(this); 
    } 

    public static Singleton getInstance() {  
     ... 
     if(instance != null) { 
     instance = new Singleton(); 
     instance.init(); 
     } 
     ... 
    } 
} 
+0

也許在GUI編程中這是可以的,但通常這個代碼不是線程安全的。 – Tvaroh 2013-12-25 09:58:53

+0

你可以移動構造並調用init()到靜態初始化程序,我猜,在調用init()之後分配給靜態final字段。 – Trejkaz 2018-01-02 01:23:54

5

可以寫成:

addWindowFocusListener(Singleton.this); 

這將防止從NB顯示警告。

0

假設你最初有一個這樣的類,它本身就是一個ActionListener,因此你最終調用addActionListener(this)來產生警告。

private class CloseWindow extends JFrame implements ActionListener { 
    public CloseWindow(String e) { 
     setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); 
     setLayout(new BorderLayout()); 

     JButton exitButton = new JButton("Close"); 
     exitButton.addActionListener(this); 
     add(exitButton, BorderLayout.SOUTH); 
    } 

    @Override 
    public void actionPerformed(ActionEvent e) { 
     String actionCommand = e.getActionCommand(); 

     if(actionCommand.equals("Close")) { 
      dispose(); 
     } 
    } 
} 

作爲@Colin Hebert提到,你可以將ActionListener分離出它自己的類。當然,這將需要引用您想調用.dispose()的JFrame。如果您不想填充變量名稱空間,並且希望能夠將ActionListener用於多個JFrame,則可以使用getSource()來獲取按鈕,然後調用getParent()調用鏈檢索擴展JFrame的Class,然後調用getSuperclass以確保它是JFrame。

private class CloseWindow extends JFrame { 
    public CloseWindow(String e) { 
     setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); 
     setLayout(new BorderLayout()); 

     JButton exitButton = new JButton("Close"); 
     exitButton.addActionListener(new ExitListener()); 
     add(exitButton, BorderLayout.SOUTH); 
    } 
} 

private class ExitListener implements ActionListener { 
    @Override 
    public void actionPerformed(ActionEvent e) { 
     String actionCommand = e.getActionCommand(); 
     JButton sourceButton = (JButton)e.getSource(); 
     Component frameCheck = sourceButton; 
     int i = 0;    
     String frameTest = "null"; 
     Class<?> c; 
     while(!frameTest.equals("javax.swing.JFrame")) { 
      frameCheck = frameCheck.getParent(); 
      c = frameCheck.getClass(); 
      frameTest = c.getSuperclass().getName().toString(); 
     } 
     JFrame frame = (JFrame)frameCheck; 

     if(actionCommand.equals("Close")) { 
      frame.dispose(); 
     } 
    } 
} 

上述代碼適用於擴展JFrame的任何級別的子級別的任何按鈕。很明顯,如果你的對象只是一個JFrame,那只是直接檢查這個類而不是檢查超類的問題。

最終使用這種方法,你會得到一個像這樣的引用:MainClass $ CloseWindow它具有超類JFrame,然後你將該引用轉換爲JFrame並處置它。

32

[備註通過chiccodoro:一種解釋爲什麼/泄漏this時可能會引發問題,即使泄漏聲明最後放置在構造函數中:]

最後一個字段的語義是「正常」的領域語義不同。例如,

我們玩網絡遊戲。讓我們讓一個Game對象從網絡中檢索數據,並讓一個Player對象偵聽來自遊戲的事件以相應地執行。遊戲對象隱藏了所有網絡的詳細信息,玩家只有在事件感興趣:

import java.util.*; 
import java.util.concurrent.Executors; 

public class FinalSemantics { 

    public interface Listener { 
     public void someEvent(); 
    } 

    public static class Player implements Listener { 
     final String name; 

     public Player(Game game) { 
      name = "Player "+System.currentTimeMillis(); 
      game.addListener(this);//Warning leaking 'this'! 
     } 

     @Override 
     public void someEvent() { 
      System.out.println(name+" sees event!"); 
     } 
    } 

    public static class Game { 
     private List<Listener> listeners; 

     public Game() { 
      listeners = new ArrayList<Listener>(); 
     } 

     public void start() { 
      Executors.newFixedThreadPool(1).execute(new Runnable(){ 

       @Override 
       public void run() { 
        for(;;) { 
         try { 
          //Listen to game server over network 
          Thread.sleep(1000); //<- think blocking read 

          synchronized (Game.this) { 
           for (Listener l : listeners) { 
            l.someEvent(); 
           } 
          } 
         } catch (InterruptedException e) { 
          e.printStackTrace(); 
         } 
        } 
       }    
      }); 
     } 

     public synchronized void addListener(Listener l) { 
      listeners.add(l); 
     } 
    } 

    public static void main(String[] args) throws InterruptedException { 
     Game game = new Game(); 
     game.start(); 
     Thread.sleep(1000); 
     //Someone joins the game 
     new Player(game); 
    } 
} 
//Code runs, won't terminate and will probably never show the flaw. 

似乎都不錯:訪問列表正確同步。缺點是這個例子泄露了Player.this到Game,它正在運行一個線程。

決賽是相當scary

...編譯器有自由的大量移動跨越障礙同步最終領域的讀取...

這幾乎是所有失敗適當的同步。但幸運的

一個線程,只能看到一個參考的對象該對象後,已經完全初始化的保證看到正確初始化值,該對象的final領域。

在該示例中,構造函數將對象引用寫入列表。 (因此尚未完全初始化,因爲構造函數沒有完成。)寫入之後,構造函數仍未完成。它只需要從構造函數返回,但我們假設它尚未。現在執行者可以完成工作並向所有聽衆廣播事件,包括尚未初始化的播放器對象!玩家(姓名)的最後一個字段可能不會寫入,並且會導致打印null sees event!

+0

正如我們在討論中提到的,我的回答是:非常有力的一點! Ishtar的回答在閱讀他的初始評論後可能會稍微容易理解*「在單線程設置中這只是'安全的'(...)如果將此漏洞泄露給構造函數中的另一個線程,在構造函數的末尾'可能會被虛擬機重新排序。「* – chiccodoro 2014-04-16 06:48:24

+3

順便說一句 - 也許OP的問題是一個設計問題。一個對象在創建時註冊自己的事實可能表明它的構造函數做了一些它並不意味着的事情:構造函數應該初始化實例並且不修改任何其他對象 – chiccodoro 2014-04-16 06:53:51

+0

我剛剛得到了這個錯誤,但是實現了一個這樣的類,它被封裝在一個init方法中,這個方法被構造函數調用。@Ishtar是否會做任何事情來幫助這個「泄漏問題?」,沒有更多的警告,所以我想我沒事吧? – XaolingBao 2016-07-03 20:13:00

相關問題