为什么要在Java中公开私有内部类成员



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

public class DataStructure {
    // ...
    private class InnerEvenIterator {
        // ...
        public boolean hasNext() { // Why public?
            // ...
        }
    }
}

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

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

想象一下,在一些重构过程中,您需要使这个内部类成为顶级类。如果这个方法是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 DataStructure {
    // ...
    private class InnerEvenIterator implements Iterator {
        // ...
        public boolean hasNext() { // Why public?
            // ...
        }
    }
    public Iterator iterator() {
        return new InnerEvenIterator();
    }
}

BTW:抽象类通常有public构造函数,而实际上它们是protected

如果内部类是私有的,则不能通过外部类之外的名称访问它。内部类和外部类可以访问彼此的私有方法和私有实例变量。只要您在内部或外部类中,修饰符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仍然是唯一可以访问它的类。使用任何你喜欢的修饰符,它在功能上都没有区别。唯一不能随机选择的时间是在实现或扩展时,在这种情况下,你不能减少访问,但可以增加访问。因此,如果抽象方法具有受保护的访问权限,你可以将其更改为公共方法。当然,两者都没有什么区别。

如果您计划在其他类中使用内部类,并因此将其公开,那么您可能不应该首先将其作为内部类。

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

这里需要考虑多个方面。下面将使用术语";嵌套类";因为它覆盖了非static(也称为"内部类")和static类(源)。

private嵌套类无关,但JLS§8.2有一个有趣的例子,它显示了包私有或protected类中的public成员可能有用。

源代码

重写方法

当你的嵌套类实现了一个接口或扩展了一个类并重写了它的一个方法时,那么根据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描述了一个类可以增加其方法之一的访问级别,即使子类用覆盖它,也不会导致链接错误。

传达意图

访问限制的定义见JLS§6.6.1:

引用类型[…]的成员[…]只有在该类型是可访问的并且该成员或构造函数被声明为允许访问时才可访问

[…]

否则,成员或构造函数被声明为private,并且仅当访问发生在包含成员或构造函数声明的顶级类型(§7.6)的主体内时,才允许访问。

因此,private嵌套类的成员(从源代码的角度来看;另请参阅"反射"部分)只能从封闭顶层类型的主体访问。有趣的是;身体;还包括其他嵌套类:

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嵌套类中使用public成员确实没有意义。

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

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,这不再是必要的(当使用JDK 11编译时,javap -v输出):

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

反射

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

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

[…]

如果T是protected,那么它必然是一个嵌套类型,因此在编译时,它的可访问性会受到包含其声明的类型的可达性的影响。但是,在链接过程中,其可访问性不受包含其声明的类型的可访问性的影响。此外,在链接期间,protectedT和publicT一样可访问。

类似地,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,而不必使其可访问,尽管它位于包私有类中的private嵌套类中。

当您为运行不受信任代码的环境编写代码时,应该记住这一点,以防止恶意代码干扰内部类
因此,当涉及到嵌套类型的访问级别时,您应该始终选择权限最小的一个,最好是private或package private。

相关内容

  • 没有找到相关文章

最新更新