36

如果仍然无法在包含类之外访问它,那么在 Java 中将私有内部类的成员声明为 public 的原因是什么?或者可以吗?

public class DataStructure {
    // ...

    private class InnerEvenIterator {
        // ...

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

7 回答 7

36

如果InnerEvenIterator该类没有扩展任何类或实现任何接口,我认为这是无稽之谈,因为没有其他类可以访问它的任何实例。

但是,如果它扩展或实现任何其他非私有类或接口,它就有意义。一个例子:

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;
    }     

}
于 2011-06-07T11:45:26.537 回答
17

public尽管编译器在这种特殊情况下不强制执行可见性规则,但可以创建此方法以表明它在语义上是公共的。

想象一下,在一些重构过程中,您需要使这个内部类成为顶级。如果这种方法是private,您将如何决定是否应该使用它public,或者应该使用一些更具限制性的修饰符?将方法声明为public告诉读者原作者的意图 - 此方法不应被视为实现细节。

于 2011-06-07T11:54:27.740 回答
11

当您实现任何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            
    }
}
于 2011-06-07T11:48:47.160 回答
4

我认为您在示例代码中缺少实现Iterator接口部分。在这种情况下,您不能使该hasNext()方法具有除 public 之外的任何其他可见性标识符,因为这最终会降低其可见性(接口方法具有公共可见性)并且不会编译。

于 2011-06-07T11:49:04.203 回答
3

有许多无用的访问修饰符组合。私有内部类中的公共方法只有在实现公共类/接口中的公共方法时才有用。

public class DataStructure {
    // ...

    private class InnerEvenIterator implements Iterator {
        // ...

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

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

顺便说一句:抽象类public实际上通常有构造函数protected

于 2011-06-07T11:51:53.743 回答
2

如果内部类是私有的,则不能通过外部类之外的名称访问它。内部和外部类可以访问彼此的私有方法和私有实例变量。只要您在内部或外部类中,修饰符 public 和 private 具有相同的效果。在您的代码示例中:

public class DataStructure {
    // ...

    private class InnerEvenIterator {
        // ...

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

就 DataStructure 类而言,这完全等价于:

public class DataStructure {
    // ...

    private class InnerEvenIterator {
        // ...

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

这是因为只有 DataStructure 可以访问它,所以设置为 public 或 private 都没有关系。无论哪种方式,DataStructure 仍然是唯一可以访问它的类。使用您喜欢的任何修饰符,它没有功能上的区别。唯一不能随意选择的时间是在实现或扩展时,这种情况下不能减少访问,但可以增加访问。因此,如果抽象方法具有受保护的访问权限,您可以将其更改为公共的。当然,没有一个人实际上有任何区别。

如果您打算在其他类中使用内部类并因此将其公开,那么您可能一开始就不应该将其设为内部类。

此外,我看不到对内部类扩展或实现其他类的任何要求。他们这样做可能很常见,但肯定不是必需的。

于 2013-01-11T08:24:36.280 回答
1

这里有多个方面需要考虑。下面将使用术语“嵌套类”,因为它涵盖了非static(也称为“内部类”)和static类()。

private嵌套类无关,但 JLS §8.2 有一个有趣的示例,它显示public了 package-private 或protectedclasses 中的成员可能有用的地方。

源代码

覆盖方法

当您的嵌套类实现接口或扩展类并覆盖其方法之一时,根据JLS §8.4.8.3

覆盖或隐藏方法的访问修饰符必须至少提供与覆盖或隐藏方法一样多的访问权限

例如:

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

方法hasNext()next()覆盖Iterator方法必须是public因为Iterator方法是公共的。

作为旁注:JLS §13.4.7描述了一个类可以提高其方法之一的访问级别,即使子类用它覆盖它,也不会导致链接错误。

传达意图

访问限制在JLS §6.6.1中定义:

引用类型 [...] 的成员 [...] 仅在类型可访问且声明成员或构造函数允许访问时才可访问

[...]

否则,声明成员或构造函数private,并且当且仅当它出现在包含成员或构造函数声明的顶级类型(第 7.6 节)的主体内时才允许访问。

因此private嵌套类的成员只能从封闭的顶级类型的主体中访问(从源代码的角度来看;另请参见“反射”部分)。有趣的是,“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++;
    }
  }
}

因此,从编译器提供的访问限制的角度来看,在嵌套类中使用public成员确实没有意义。private

但是,使用不同的访问级别对于传达意图可能很有用,尤其是(正如其他人指出的那样)嵌套类将来可能被重构为单独的顶级类时。考虑这个例子:

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;
    }
  }

  ...
}

编译的类文件

值得注意的是 Java 编译器如何处理对嵌套类成员的访问。在JEP 181:基于嵌套的访问控制(在 Java 11 中添加)之前,编译器必须创建合成访问器方法,因为类文件无法表达与嵌套类相关的访问控制逻辑。考虑这个例子:

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);
}

这略微增加了类文件的大小并且可能降低了性能。这就是为什么经常为嵌套类的成员选择包私有(即没有访问修饰符)访问以防止创建合成访问方法的原因之一。
对于 JEP 181,这不再是必需的(javap -v使用 JDK 11 编译时的输出):

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

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

反射

另一个有趣的方面是反射。遗憾的是,JLS 没有在这方面进行具体验证,但§15.12.4.3包含一个有趣的提示:

如果 T 与 D 在不同的包中,并且它们的包在同一模块中,并且 T 是publicor protected,则 T 是可访问的。

[...]

如果 T is protected,它必然是一个嵌套类型,因此在编译时,它的可访问性受到包含其声明的类型的可访问性的影响。但是,在链接期间,其可访问性不受包含其声明的类型的可访问性影响。此外,在链接期间,protectedT 与 T 一样可访问public

同样AccessibleObject.setAccessible(...)根本没有提到封闭类型。确实可以在非封闭类型 中访问 apublicprotected嵌套类型的成员:publictest1/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而不必使其可访问,尽管它private位于包私有类中的嵌套类中。

当您为运行不受信任的代码的环境编写代码时,您应该牢记这一点,以防止恶意代码与内部类混淆。
因此,当涉及到嵌套类型的访问级别时,您应该始终选择最不宽松的一种,最好是private包私有的。

于 2020-10-25T20:31:18.970 回答