簡體   English   中英

為什么在 Java 中將私有內部 class 成員公開?

[英]Why make private inner class member public in Java?

如果在包含 class 之外仍然無法訪問它,那么在 Java 中聲明私有內部 class 的成員的原因是什么? 或者可以嗎?

public class DataStructure {
    // ...

    private class InnerEvenIterator {
        // ...

        public boolean hasNext() { // Why public?
            // ...
        }
    }
}

如果InnerEvenIterator class 沒有擴展任何 class 或實現任何接口,我認為這是無稽之談,因為沒有其他 class 可以訪問它的任何實例。

但是,如果它擴展或實現任何其他非私有 class 或接口,它就有意義。 一個例子:

interface EvenIterator {
    public boolean hasNext();
}


public class DataStructure {
    // ...

    private class InnerEvenIterator implements EvenIterator{
        // ...

        public boolean hasNext() { // Why public?
            // ...
        }
    }

    InnerEvenIterator iterator;

    public EvenIterator getIterator(){
         return iterator;
    }     

}

盡管編譯器在這種特殊情況下不強制執行可見性規則,但可以將此方法設為public以表明它在語義上是公開的。

想象一下,在一些重構過程中,您需要使這個內部 class 成為頂層。 如果此方法是private ,您將如何決定是否應該將其設為public ,或者應該使用一些更具限制性的修飾符? 將方法聲明為public可以告訴讀者原作者的意圖 - 此方法不應被視為實現細節。

當您實現任何interface時,它很有用。

class DataStructure implements Iterable<DataStructure> {

    @Override
    public Iterator<DataStructure> iterator() {
        return new InnerEvenIterator();
    }
    // ...        

    private class InnerEvenIterator implements Iterator<DataStructure> {
        // ...    
        public boolean hasNext() { // Why public?
            // ...
            return false;
        }

        @Override
        public DataStructure next() {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        @Override
        public void remove() {
            throw new UnsupportedOperationException("Not supported yet.");
        }
    }

    public static void main(String[] ex) {
        DataStructure ds = new DataStructure();
        Iterator<DataStructure> ids = ds.iterator();
        ids.hasNext(); // accessable            
    }
}

我認為您在示例代碼中缺少實現Iterator接口的部分。 在這種情況下,您不能使hasNext()方法具有除 public 之外的任何其他可見性標識符,因為這最終會降低其可見性(接口方法具有公共可見性)並且不會編譯。

有許多無用的訪問修飾符組合。 私有內部 class 中的公共方法僅在實現公共類/接口中的公共方法時才有用。

public class DataStructure {
    // ...

    private class InnerEvenIterator implements Iterator {
        // ...

        public boolean hasNext() { // Why public?
            // ...
        }
    }

    public Iterator iterator() {
        return new InnerEvenIterator();
    }
}

順便說一句:抽象類實際上protected時通常具有public構造函數

如果內部 class 是私有的,則不能通過外部 class 之外的名稱訪問它。 內部和外部類可以訪問彼此的私有方法和私有實例變量。 只要你在內部或外部 class 之內,修飾符 public 和 private 具有相同的效果。 在您的代碼示例中:

public class DataStructure {
    // ...

    private class InnerEvenIterator {
        // ...

        public boolean hasNext() { // Why public?
            // ...
        }
    }
}

就 class DataStructure 而言,這完全等同於:

public class DataStructure {
    // ...

    private class InnerEvenIterator {
        // ...

        private boolean hasNext() {
            // ...
        }
    }
}

這是因為只有 DataStructure 可以訪問它,所以設置為 public 或 private 都沒有關系。 無論哪種方式,DataStructure 仍然是唯一可以訪問它的 class。 使用您喜歡的任何修飾符,它沒有功能上的區別。 唯一不能隨意選擇的時間是在實現或擴展時,這種情況下不能減少訪問,但可以增加訪問。 因此,如果抽象方法具有受保護的訪問權限,您可以將其更改為公共的。 誠然,沒有一個人實際上有任何區別。

如果您計划在其他類中使用內部 class 並因此將其公開,您可能首先不應該將其設為內部 class。

此外,我看不到對內部類擴展或實現其他類的任何要求。 他們這樣做可能很常見,但肯定不是必需的。

這里有多個方面需要考慮。 下面將使用術語“嵌套類”,因為它涵蓋了非static (也稱為“內部類”)和static類(來源)。

private嵌套類無關,但 JLS §8.2 有一個有趣的示例,它顯示了包私有或protected類中的public成員可能有用的地方。

源代碼

覆蓋方法

當您的嵌套 class 實現接口或擴展 class 並覆蓋其方法之一時,則根據JLS §8.4.8.3

覆蓋或隱藏方法的訪問修飾符必須至少提供與覆蓋或隱藏方法一樣多的訪問權限

例如:

public class Outer {
  private static class Nested implements Iterator<String> {
    @Override
    public boolean hasNext() {
      ...
    }
    
    @Override
    public String next() {
      ...
    }
  }
}

覆蓋Iterator方法的hasNext()next()方法必須是public的,因為Iterator方法是公共的。

作為旁注: JLS §13.4.7描述了 class 可以提高其方法之一的訪問級別,即使子類覆蓋它,也不會導致鏈接錯誤。

傳達意圖

訪問限制在JLS §6.6.1中定義:

引用類型 [...] 的成員 [...] 僅在類型可訪問且聲明成員或構造函數允許訪問時才可訪問

[...]

否則,成員或構造函數被聲明為private ,並且當且僅當它出現在包含成員或構造函數聲明的頂級類型( 第 7.6 節)的主體內時才允許訪問。

因此, private嵌套 class 的成員只能從封閉的頂級類型的主體中訪問(從源代碼的角度來看;另請參見“反射”部分)。 有趣的是,“body”還涵蓋了其他嵌套類:

public class TopLevel {
  private static class Nested1 {
    private int i;
  }

  void doSomething(Nested1 n) {
    // Can access private member of nested class
    n.i++;
  }

  private static class Nested2 {
    void doSomething(Nested1 n) {
      // Can access private member of other nested class
      n.i++;
    }
  }
}

因此,從編譯器提供的訪問限制的角度來看,在private嵌套 class 中使用public成員確實沒有意義。

但是,使用不同的訪問級別對於傳達意圖可能很有用,尤其是(正如其他人所指出的)當嵌套的 class 將來可能被重構為單獨的頂級 class 時。 考慮這個例子:

public class Cache {
  private static class CacheEntry<T> {
    private final T value;
    private long lastAccessed;

    // Signify that enclosing class may use this constructor
    public CacheEntry(T value) {
      this.value = value;
      updateLastAccessed();
    }

    // Signify that enclosing class must NOT use this method
    private void updateLastAccessed() {
      lastAccessed = System.nanoTime();
    }

    // Signify that enclosing class may use this method
    public T getValue() {
      updateLastAccessed();
      return value;
    }
  }

  ...
}

編譯 class 文件

值得注意的是 Java 編譯器如何處理對嵌套類成員的訪問。 JEP 181:基於嵌套的訪問控制(在 Java 11 中添加)之前,編譯器必須創建合成訪問器方法,因為 class 文件無法表達與嵌套類相關的訪問控制邏輯。 考慮這個例子:

class TopLevel {
  private static class Nested {
    private int i;
  }
    
  void doSomething(Nested n) {
    n.i++;
  }
}

當使用 Java 8 編譯並使用javap -p./TopLevel$Nested.class檢查時,您將看到已添加合成access$008方法:

class TopLevel$Nested {
  private int i;
  private TopLevel$Nested();
  static int access$008(TopLevel$Nested);
}

這略微增加了 class 文件的大小,並且可能會降低性能。 這就是為什么經常為嵌套類的成員選擇包私有(即沒有訪問修飾符)訪問以防止創建合成訪問方法的原因之一。
對於 JEP 181,這不再是必需的(使用 JDK 11 編譯時的javap -v output):

class TopLevel$Nested
...
{
  private int i;
  ...

  private TopLevel$Nested();
  ...
}
...
NestHost: class TopLevel
...

反射

另一個有趣的方面是反射。 遺憾的是,JLS 沒有在這方面進行具體驗證,但§15.12.4.3包含一個有趣的提示:

如果 T 與 D 位於不同的 package 中,並且它們的包位於同一模塊中,並且 T 是publicprotected ,則 T 是可訪問的。

[...]

如果 T 是protected ,它必然是一個嵌套類型,因此在編譯時,它的可訪問性受到包含其聲明的類型的可訪問性的影響。 但是,在鏈接期間,其可訪問性不受包含其聲明的類型的可訪問性影響。 此外,在鏈接期間, protected的 T 與public T 一樣可訪問。

同樣AccessibleObject.setAccessible(...)根本沒有提到封閉類型。 確實可以在非public封閉類型中訪問publicprotected嵌套類型的成員: test1/TopLevel1.java

package test1;

// package-private
class TopLevel1 {
  private static class Nested1_1 {
    protected static class Nested1_2 {
      public static int i;
    }
  }
}

test2/TopLevel2.java

package test2;

import java.lang.reflect.Field;

public class TopLevel2 {
  public static void main(String... args) throws Exception {
    Class<?> nested1_2 = Class.forName("test1.TopLevel1$Nested1_1$Nested1_2");
    Field f = nested1_2.getDeclaredField("i");
    f.set(null, 1);
  }
}

此處反射能夠修改字段test1.TopLevel1.Nested1_1.Nested1_2.i而不必使其可訪問,盡管它位於包私有 class 內的private嵌套 class 內。

當您為運行不受信任的代碼的環境編寫代碼時,您應該牢記這一點,以防止惡意代碼與內部類混淆。
因此,當涉及到嵌套類型的訪問級別時,您應該始終選擇最不寬松的一種,最好是private的或包私有的。

暫無
暫無

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

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