簡體   English   中英

Java(匿名與否)內部類:使用它們好嗎?

[英]Java (anonymous or not) inner classes: is it good to use them?

在我的一些項目和一些書籍中,據說使用內部 class (匿名與否,static 與否) - 除了在某些受限條件下,如EventListener s 或Runnable s - 是最佳實踐。 他們甚至在我的第一個行業項目中被“禁止”。

這真的是最佳實踐嗎? 為什么?

(我不得不說我經常使用它們......)

- 編輯 - -
我無法在所有這些回復中選擇一個正確的答案:大部分都是正確的:我仍然會使用內部類,但我會盡量減少使用它們!

在我看來,Java代碼中90%的內部類是與單個類關聯的實體,因此被“推入”作為內部類,或者因為Java不支持Lambdas而存在的匿名內部類。

我個人不喜歡看復雜的內部課程。 它們增加了源文件的復雜性,它們使它更大,在調試和分析等方面處理它們很難。我喜歡將我的項目分成許多包,在這種情況下我可以使大多數實體成為頂級類僅限於包裝。

這給我留下了必要的內部類 - 例如動作聽眾,虛假的“功能”編程等等。這些通常是匿名的,雖然我不是粉絲(在許多情況下會更喜歡Lambda),但我和他們住在一起但是不要不喜歡他們。

我多年沒有做過任何C#,但我想知道在引入Lambdas時內部類的流行程度或C#等價物的流行程度是否會被刪除。

清潔度。 如果代碼被分解為邏輯部分,而不是所有代碼都存儲在同一個文件中,則更容易理解代碼。

也就是說,我不認為明智地使用內部類是不合適的。 有時這些內部類只存在於一個目的,因此我可以將它們存在於使用它們的唯一文件中。 但是,根據我的經驗,這並沒有發生太多。

在進行基於事件的編程時,特別是在swing中,匿名類很好用。

是的,禁止內部課程是一種有用的做法,因為找到一個禁止他們的地方是警告我在那里工作的好方法,因此保留了我未來的理智。 :)

正如gicappa所指出的,匿名內部類是最接近Java的閉包,並且非常適合在將行為傳遞到方法中的情況下使用,如果沒有別的話。

正如其他人所說,很多時候,當你使用一個匿名的內部類時,它也被用在其他一些地方......

因此,您可以輕松地將內部類代碼復制到許多地方......當您使用非常簡單的內部類來過濾/排序集合,使用謂詞,比較器或類似的東西時,這似乎不是問題...

但是你必須知道,當你使用3次匿名內部類完全相同的事情時(例如刪除Collection的“”),你實際上是在java PermGen上創建了3個新類。

因此,如果每個人到處使用內部類,這可能會導致應用程序具有更大的permgen。 根據應用程序,這可能是一個問題...如果您正在從事該行業,您可以編寫具有有限內存的嵌入式應用程序,應該進行優化...

請注意,這也是為什么雙花括號語法(帶有非靜態初始化塊的匿名內部類)有時被視為反模式的原因:

new ArrayList<String>() {{
     add("java");
     add("jsp");
     add("servlets");
  }}

你應該問那些禁止你使用它們的人...恕我直言,這完全取決於具體情況......

匿名內部類具有能夠查看“新”語句周圍的字段和變量的好處。 這可以使一些非常干凈的設計,並且是一個非常好(但有點羅嗦)的方法“我們如何制作簡單版本的lambda語句”。

命名內部類的好處是有一個名稱,希望能夠以通常的方式記錄,但是它與周圍的類聯系在一起。 一個非常好的例子是Builder模式,其中內部類負責為初始化過程提供狀態,而不是擁有眾多構造函數。 這樣的構建器不能在類之間重用,因此將Builder與父類緊密結合是完全合理的。

如果需要方法參數,我建議在使用它時要謹慎。 我剛剛發現了與此相關的內存泄漏。 它涉及使用GrizzlyContinuation的HttpServlet。
總之,這里是錯誤的代碼:

public void doGet(HttpServletRequest request, final HttpServletResponse response){
  createSubscription(..., new SubscriptionListener(){
    public void subscriptionCreated(final CallController controller) {
      response.setStatus(200);
      ...
      controller.resume();
    }

    public void subscriptionFailed(){
       ...
     }

    public void subscriptionTimeout(){
      ...
  }});
}

因此,由於監聽器是由訂閱保留的,所以HttpServletResponse也會被保留,以防聽眾需要它(不明顯)。 然后只有在刪除訂閱時才會釋放HttpServletResponse實例。 如果您使用在其構造函數中獲取響應的內部類,則一旦調用恢復釋放內存,就可以將其設置為null。

使用它們但要小心!

馬丁

某些框架,如Wicket,確實需要匿名內部類。

說永遠不會是愚蠢的。 永遠不要把話說絕了! 良好使用的一個例子可能是你有一些遺留代碼是由許多類直接在Collection字段上運行的人編寫的,無論出於何種原因,你不能改變其他類,但需要有條件地將操作鏡像到另一個采集。 最簡單的方法是通過匿名內部類添加此行為。

bagOfStuff = new HashSet(){
  @Override
  public boolean add(Object o) {
    boolean returnValue = super.add(o);
    if(returnValue && o instanceof Job)
    {
      Job job = ((Job)o);
      if(job.fooBar())
         otherBagOfStuff.add(job);
    }
    return returnValue;
  }
}

也就是說,他們絕對可以像窮人的關閉一樣使用。

這里沒有提到的一個項目是(非靜態)內部類帶有對它的封閉類的引用。 更重要的是,內部類可以訪問其封閉類的私有成員。 它可能會破壞封裝。

如果你有選擇,不要使用內部類。

在嘗試模擬多重繼承時,內部類是合適的。 它類似於使用C ++在幕后發生的事情:當你在C ++中有多個繼承時,內存中的對象布局實際上是幾個對象實例的串聯; 然后編譯器計算出在調用方法時如何調整“this”指針。 在Java中,沒有多重繼承,但內部類可用於在另一種類型下提供給定實例的“視圖”。

大多數情況下,可以堅持單繼承,但偶爾使用多繼承是正確的工具,這是使用內部類的時候。

這意味着內部類在某種程度上比通常的類更復雜,就像多繼承比單繼承更復雜一樣:許多程序員在圍繞這個概念時難以理解。 因此,“最佳實踐”:避免內部課程,因為它會讓你的同事感到困惑。 在我看來,這不是一個好的論據,在我的工作場所,當我們認為合適時,我們很樂意使用內部類。

(內部類的一個小缺點是它們在源代碼中添加了一個額外級別的縮進。當有人希望將代碼保留在79列之外時,這有點令人厭煩。)

當我們需要使用一個方法(如Runnable,ActionListener和其他方法)實現接口時,通常會使用匿名內部類。

匿名內部類的另一個優秀設備是,當您不想創建某個類的子類但需要覆蓋其中一個(或兩個)方法時。

當您希望在兩個類之間實現緊密一致時,可以使用命名的內部類。 它們不像匿名內部類那么有用,我不能確定它是一個很好的做法,永遠使用它們。

Java還有嵌套(或內部靜態)類。 當您想要提供某些特殊訪問權限並且標准公共或默認訪問級別不夠時,可以使用它們。

內部類通常用於“傳遞行為”作為方法的參數。 具有閉包的其他語言以優雅的方式支持此功能。 由於語言的限制,使用內部類會產生一些不優雅的代碼(恕我直言),但它很有用,並且廣泛用於處理內部類的事件和

所以我會說內部類非常有用。

沒有內部類的代碼更易於維護讀取 當您從內部類訪問外部類的私有數據成員時,JDK編譯器在外部類中創建package-access成員函數,以便內部類訪問private members 這留下了一個安全漏洞 一般來說,我們應該避免使用內部類。

僅當內部類僅與外部類的上下文相關時才使用內部類,並且/或者內部類可以設置為私有,以便只有外部類可以訪問它。 內部類主要用於實現輔助類,如迭代器,比較器等 ,它們在外部類的上下文中使用。

是的,使用它們是好的,當你試圖保持一個類的內聚,並且這些類永遠不應該從它們的外部類的上下文之外實例化,使構造函數成為私有的,並且你有非常好的內聚封裝。 任何說你永遠不應該使用它們的人都不知道他們在說什么。 對於事件處理程序和匿名內部類擅長的其他事情,它們比使用大量僅適用於特定類的事件處理程序來混淆包命名空間的方法更好。

由於其他海報給出的原因,我傾向於避免使用非靜態內部類。 但是我有一個特別喜歡的模式,其中非靜態內部類非常有效地工作:延遲加載有狀態類。

典型的延遲加載有狀態類使用實體ID構造,然后按需可以延遲加載其他實體信息。 通常,為了延遲加載附加信息,我們將需要依賴項。 但依賴+狀態==反模式!

非靜態內部類提供了一種避免這種反模式的方法。 希望以下簡單示例比單詞更好地說明這一點:

/*
 * Stateless outer class holding dependencies
 */
public class DataAssembler {
  private final LoadingService loadingService;

  @Inject
  DataAssembler(LoadingService loadingService) {
    this.loadingService = loadingService;
  }

  public LazyData assemble(long id) {
    return new LazyData(id);
  }

  /*
   * Stateful non-static inner class that has access to the outer
   * class' dependencies in order to lazily load data.
   */
  public class LazyData {
    private final long id;

    private LazyData(long id) {
      this.id = id;
    }

    public long id() {
      return id;
    }

    public String expensiveData() {
      return loadingService.buildExpensiveDate(id);
    }
  }
}

值得注意的是,除了上面的例子之外還有許多其他模式,其中內部類是有用的; 內部類與任何其他Java功能一樣 - 適當的時候可以使用它們並且不合適的時間!

在Java中使用或避免使用內部class時?

內部 class 具有以下字符。

  1. 無論如何.class文件被分隔為OuterClassName$InnerClassName.class

  2. 內部 class 的 class 名稱和 class 文件名始終包含外部 ZA2F2ED4F8EBC2CBB4C21A29DC40AB1 名稱。

上述人物揭示了這一事實。 外部 class 名稱是內部 class 的強制性信息。 我們可以從事實中得出這個結果。 當外部 class 是內部 class 的必填信息時,最好定義內部 class。

內部 class 的字符使開發人員有時調試起來很煩人。 因為它迫使開發人員知道外部 class 名稱和內部 class 名稱。

建議

避免定義內部 class 可以作為設計原則,除非外部 class 名稱是內部 class 的強制性信息,原因有兩個。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM