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

时间:2011-06-07 11:42:19

标签: java private-members access-specifier public-members

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

public class DataStructure {
    // ...

    private class InnerEvenIterator {
        // ...

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

7 个答案:

答案 0 :(得分:32)

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

}

答案 1 :(得分:14)

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

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

答案 2 :(得分:8)

实施任何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            
    }
}

答案 3 :(得分:4)

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

答案 4 :(得分:3)

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

public class DataStructure {
    // ...

    private class InnerEvenIterator implements Iterator {
        // ...

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

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

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

答案 5 :(得分:1)

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

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仍然是唯一可以访问它的类。使用你喜欢的任何修改器,它没有任何功能差异。您无法随意选择的唯一时间是您实施或扩展时,在这种情况下您无法减少访问权限,但您可以增加它。因此,如果抽象方法具有受保护的访问权限,则可以将其更改为public。当然,没有人真正有所作为。

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

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

答案 6 :(得分:0)

这里必须考虑多个方面。以下内容将使用术语“嵌套类”,因为它既涵盖了非static类(也称为“内部类”),又涵盖了static类(source)。

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

源代码

覆盖方法

当嵌套类实现接口或扩展类并覆盖其方法之一时,则按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: Nest-Based Access Control(在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,则它必然是一个嵌套类型,因此在编译时,其可访问性受包围其声明的类型的可访问性的影响。但是,在链接期间,其可访问性不受包围其声明的类型的可访问性的影响。此外,在链接期间,protected T与public T一样容易访问。

类似地,AccessibleObject.setAccessible(...)完全没有提到封闭类型。实际上,可以访问非public封闭类型内的protectedpublic嵌套类型的成员: 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。