简体   繁体   English

为什么在 Java 中将私有内部 class 成员公开?

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

What is the reason of declaring a member of a private inner class public in Java if it still can't be accessed outside of containing class?如果在包含 class 之外仍然无法访问它,那么在 Java 中声明私有内部 class 的成员的原因是什么? Or can it?或者可以吗?

public class DataStructure {
    // ...

    private class InnerEvenIterator {
        // ...

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

If the InnerEvenIterator class does not extend any class or implement any interface, I think it is nonsense because no other class can access any instance of it.如果InnerEvenIterator class 没有扩展任何 class 或实现任何接口,我认为这是无稽之谈,因为没有其他 class 可以访问它的任何实例。

However, if it extends or implements any other non private class or interface, it makes sense.但是,如果它扩展或实现任何其他非私有 class 或接口,它就有意义。 An example:一个例子:

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

}

This method can be made public in order to indicate that it's semantically public, despite the fact that compiler doesn't enforce visibility rules in this particular case.尽管编译器在这种特殊情况下不强制执行可见性规则,但可以将此方法设为public以表明它在语义上是公开的。

Imagine that during some refactoring you need to make this inner class top-level.想象一下,在一些重构过程中,您需要使这个内部 class 成为顶层。 If this method is private , how would you decide whether it should be made public , or some more restrictive modifier should be used?如果此方法是private ,您将如何决定是否应该将其设为public ,或者应该使用一些更具限制性的修饰符? Declaring method as public tells reader the intentions of original author - this method shouldn't be considered an implementation detail.将方法声明为public可以告诉读者原作者的意图 - 此方法不应被视为实现细节。

It is useful when you implement any interface .当您实现任何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            
    }
}

I think you are missing the implementing the Iterator interface part in your sample code.我认为您在示例代码中缺少实现Iterator接口的部分。 In that case, you can't make the hasNext() method have any other visibility identifier other than public since that would end up reducing its visibility (interface methods have public visibility) and it won't compile.在这种情况下,您不能使hasNext()方法具有除 public 之外的任何其他可见性标识符,因为这最终会降低其可见性(接口方法具有公共可见性)并且不会编译。

There are many combinations of access modifiers which are not useful.有许多无用的访问修饰符组合。 A public method in a private inner class is only useful if it implements a public method in a public class/interface.私有内部 class 中的公共方法仅在实现公共类/接口中的公共方法时才有用。

public class DataStructure {
    // ...

    private class InnerEvenIterator implements Iterator {
        // ...

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

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

BTW: abstract classes often have public constructors when actually they are protected顺便说一句:抽象类实际上protected时通常具有public构造函数

If the inner class is private it cannot be accessed by name outside of the outer class.如果内部 class 是私有的,则不能通过外部 class 之外的名称访问它。 Inner and outer classes have access to each other's private methods and private instance variables.内部和外部类可以访问彼此的私有方法和私有实例变量。 As long as you are within the inner or outer class, the modifiers public and private have the same effect.只要你在内部或外部 class 之内,修饰符 public 和 private 具有相同的效果。 In your code example:在您的代码示例中:

public class DataStructure {
    // ...

    private class InnerEvenIterator {
        // ...

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

As far as the class DataStructure is concerned, this is completely equivalent to:就 class DataStructure 而言,这完全等同于:

public class DataStructure {
    // ...

    private class InnerEvenIterator {
        // ...

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

This is because only DataStructure can access it, so it doesn't matter if you set it to public or private.这是因为只有 DataStructure 可以访问它,所以设置为 public 或 private 都没有关系。 Either way, DataStructure is still the only class that can access it.无论哪种方式,DataStructure 仍然是唯一可以访问它的 class。 Use whichever modifier you like, it makes no functional difference.使用您喜欢的任何修饰符,它没有功能上的区别。 The only time you can't choose at random is when you are implementing or extending, in which case you can't reduce the access, but you can increase it.唯一不能随意选择的时间是在实现或扩展时,这种情况下不能减少访问,但可以增加访问。 So if an abstract method has protected access you can change it to public.因此,如果抽象方法具有受保护的访问权限,您可以将其更改为公共的。 Granted neither one actually makes any difference.诚然,没有一个人实际上有任何区别。

If you plan on using an inner class in other classes, and therefore making it public, you probably shouldn't make it an inner class in the first place.如果您计划在其他类中使用内部 class 并因此将其公开,您可能首先不应该将其设为内部 class。

Additionally, I don't see any requirement for inner classes extending or implementing other classes.此外,我看不到对内部类扩展或实现其他类的任何要求。 It might be common for them to do so, but it's certainly not required.他们这样做可能很常见,但肯定不是必需的。

There are multiple aspects which have to be considered here.这里有多个方面需要考虑。 The following will use the term "nested class" because it covers both non- static (also called "inner class") and static classes (source ).下面将使用术语“嵌套类”,因为它涵盖了非static (也称为“内部类”)和static类(来源)。

Not related to private nested classes, but JLS §8.2 has an interesting example which shows where public members in package-private or protected classes could be useful.private嵌套类无关,但 JLS §8.2 有一个有趣的示例,它显示了包私有或protected类中的public成员可能有用的地方。

Source code源代码

Overriding methods覆盖方法

When your nested class implements an interface or extends a class and overrides one of its methods, then per JLS §8.4.8.3 :当您的嵌套 class 实现接口或扩展 class 并覆盖其方法之一时,则根据JLS §8.4.8.3

The access modifier of an overriding or hiding method must provide at least as much access as the overridden or hidden method覆盖或隐藏方法的访问修饰符必须至少提供与覆盖或隐藏方法一样多的访问权限

For example:例如:

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

The methods hasNext() and next() which override the Iterator methods have to be public because the Iterator methods are public.覆盖Iterator方法的hasNext()next()方法必须是public的,因为Iterator方法是公共的。

As a side note: JLS §13.4.7 describes that it is possible for a class to increase the access level of one of its methods, even if a subclass overrides it with, without causing linkage errors.作为旁注: JLS §13.4.7描述了 class 可以提高其方法之一的访问级别,即使子类覆盖它,也不会导致链接错误。

Conveying intention传达意图

Access restriction is defined in JLS §6.6.1 :访问限制在JLS §6.6.1中定义:

A member [...] of a reference type [...] is accessible only if the type is accessible and the member or constructor is declared to permit access引用类型 [...] 的成员 [...] 仅在类型可访问且声明成员或构造函数允许访问时才可访问

[...] [...]

Otherwise, the member or constructor is declared private , and access is permitted if and only if it occurs within the body of the top level type ( §7.6 ) that encloses the declaration of the member or constructor.否则,成员或构造函数被声明为private ,并且当且仅当它出现在包含成员或构造函数声明的顶级类型( 第 7.6 节)的主体内时才允许访问。

Therefore members of a private nested class can (from a source code perspective; see also "Reflection" section) only be accessed from the body of the enclosing top level type.因此, private嵌套 class 的成员只能从封闭的顶级类型的主体中访问(从源代码的角度来看;另请参见“反射”部分)。 Interestingly the "body" also covers other nested classes:有趣的是,“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++;
    }
  }
}

So from a compiler-provided access restriction perspective there is indeed no point in using a public member in a private nested class.因此,从编译器提供的访问限制的角度来看,在private嵌套 class 中使用public成员确实没有意义。

However, using different access levels can be useful for conveying intention, especially (as pointed out by others) when the nested class might be refactored to a separate top level class in the future.但是,使用不同的访问级别对于传达意图可能很有用,尤其是(正如其他人所指出的)当嵌套的 class 将来可能被重构为单独的顶级 class 时。 Consider this example:考虑这个例子:

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

  ...
}

Compiled class files编译 class 文件

It is also interesting to note how the Java compiler treats access to members of nested classes.值得注意的是 Java 编译器如何处理对嵌套类成员的访问。 Prior to JEP 181: Nest-Based Access Control (added in Java 11) the compiler had to create synthetic accessor methods because the class file could not express the access control logic related to nested classes.JEP 181:基于嵌套的访问控制(在 Java 11 中添加)之前,编译器必须创建合成访问器方法,因为 class 文件无法表达与嵌套类相关的访问控制逻辑。 Consider this example:考虑这个例子:

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

When compiled with Java 8 and inspected with javap -p./TopLevel$Nested.class you will see that a synthetic access$008 method has been added:当使用 Java 8 编译并使用javap -p./TopLevel$Nested.class检查时,您将看到已添加合成access$008方法:

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

This slightly increased the size of the class files and might have decreased performance.这略微增加了 class 文件的大小,并且可能会降低性能。 This is one reason why package-private (ie no access modifier) access has often be chosen for members of nested classes to prevent creation of synthetic access methods.这就是为什么经常为嵌套类的成员选择包私有(即没有访问修饰符)访问以防止创建合成访问方法的原因之一。
With JEP 181 this is no longer necessary ( javap -v output when compiled with JDK 11):对于 JEP 181,这不再是必需的(使用 JDK 11 编译时的javap -v output):

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

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

Reflection反射

Another interesting aspect is reflection.另一个有趣的方面是反射。 The JLS is sadly not verify specific in that regard, but §15.12.4.3 contains an interesting hint:遗憾的是,JLS 没有在这方面进行具体验证,但§15.12.4.3包含一个有趣的提示:

If T is in a different package than D, and their packages are in the same module, and T is public or protected , then T is accessible.如果 T 与 D 位于不同的 package 中,并且它们的包位于同一模块中,并且 T 是publicprotected ,则 T 是可访问的。

[...] [...]

If T is protected , it is necessarily a nested type, so at compile time, its accessibility is affected by the accessibility of types enclosing its declaration.如果 T 是protected ,它必然是一个嵌套类型,因此在编译时,它的可访问性受到包含其声明的类型的可访问性的影响。 However, during linkage, its accessibility is not affected by the accessibility of types enclosing its declaration.但是,在链接期间,其可访问性不受包含其声明的类型的可访问性影响。 Moreover, during linkage, a protected T is as accessible as a public T.此外,在链接期间, protected的 T 与public T 一样可访问。

Similarly AccessibleObject.setAccessible(...) does not mention the enclosing type at all.同样AccessibleObject.setAccessible(...)根本没有提到封闭类型。 And indeed it is possible to access the members of a public or protected nested type within non- public enclosing type: test1/TopLevel1.java确实可以在非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);
  }
}

Here reflection is able to modify the field test1.TopLevel1.Nested1_1.Nested1_2.i without having to make it accessible despite it being inside a private nested class inside a package-private class.此处反射能够修改字段test1.TopLevel1.Nested1_1.Nested1_2.i而不必使其可访问,尽管它位于包私有 class 内的private嵌套 class 内。

When you are writing code for an environment where untrusted code is run you should keep that in mind to prevent malicious code from messing with internal classes.当您为运行不受信任的代码的环境编写代码时,您应该牢记这一点,以防止恶意代码与内部类混淆。
So when it comes to the access level of nested types you should always choose the least permissive one, ideally private or package-private.因此,当涉及到嵌套类型的访问级别时,您应该始终选择最不宽松的一种,最好是private的或包私有的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM