Java 中的 public,protected,package-private 和 private 之间有什么区别?

在 Java 中,关于何时使用每个访问修饰符(即默认(程序包私有), publicprotectedprivate )的明确规则,同时使classinterface以及继承都可以使用?

答案

官方教程可能对您有用。

______________________________________________________________
|           │ Class │ Package │ Subclass │ Subclass │ World  |
|           │       │         │(same pkg)│(diff pkg)│        |
|───────────┼───────┼─────────┼──────────┼──────────┼────────|
|public     │   +   │    +    │    +     │     +    │   +    | 
|───────────┼───────┼─────────┼──────────┼──────────┼────────|
|protected  │   +   │    +    │    +     │     +    │        | 
|───────────┼───────┼─────────┼──────────┼──────────┼────────|
|no modifier│   +   │    +    │    +     │          │        | 
|───────────┼───────┼─────────┼──────────┼──────────┼────────|
|private    │   +   │         │          │          │        |
|___________|_______|_________|__________|__________|________|
 + : accessible         blank : not accessible

(注意:我不是 Java 程序员,我是 Perl 程序员。Perl 没有正式的保护,这也许就是为什么我如此理解问题的原因:))

私人的

就像您想的那样,只有声明了它的才能看到它。

包私人

只能由声明它的查看和使用。这是 Java 中的默认设置(有些人认为这是错误的)。

受保护的

子类或包成员可以看到包 Private +。

上市

每个人都可以看到。

已发表

在我控制的代码之外可见。 (虽然不是 Java 语法,但对于此讨论很重要)。

C ++ 定义了一个附加级别,称为 “朋友”,您对此了解的越少越好。

什么时候应该使用什么?整个想法是封装以隐藏信息。您希望尽可能多地向用户隐藏如何完成操作的细节。为什么?因为这样您以后可以更改它们,而不会破坏任何人的代码。这样,您就可以优化,重构,重新设计和修复错误,而不必担心有人正在使用刚刚修改过的代码。

因此,经验法则是使事物仅在其必须可见的地方可见。从私有开始,仅根据需要添加更多可见性。仅公开用户必须知道的绝对必要信息,公开的每个细节都会限制您重新设计系统的能力。

如果您希望用户能够自定义行为,而不是公开内部结构以便他们可以覆盖它们,则将这些胆量推入一个对象并将该接口公开是一个更好的主意。这样,他们可以简单地插入新对象。例如,如果您正在编写 CD 播放器,并且希望可自定义 “查找有关此 CD 的信息”,而不是公开这些方法,则将所有功能放到其自己的对象中,而仅将您的对象获取 / 设置者公开。这样一来,小胆地暴露胆量就可以促进良好的构图和关注点分离

就我个人而言,我只坚持 “私有” 和 “公开”。许多 OO 语言就是这样。 “受保护” 可以派上用场,但这确实是个骗子。一旦接口不只是私有的,它就不在您的控制范围之内,您必须去寻找其他人的代码来寻找用途。

这就是 “发布” 思想的来源。更改接口(重构接口)要求您找到使用该接口的所有代码,并且也要对其进行更改。如果接口是私有的,那就没问题了。如果受保护,则必须查找所有子类。如果是公开的,则必须查找使用您的代码的所有代码。有时这是可能的,例如,如果您正在处理仅供内部使用的公司代码,则接口是否公开无关紧要。您可以从公司存储库中获取所有代码。但是,如果某个接口是 “已发布” 的,则如果控件之外有使用该接口的代码,那么您将被束缚。您必须支持该接口,否则可能会破坏代码。甚至可以将受保护的接口视为已发布(这就是为什么我不用为受保护而烦恼的原因)。

许多语言发现公共 / 受保护 / 私有的等级性质过于局限,与现实不符。为此,有一个特质类的概念,但这是另一个展示。

这是桌子的更好版本。 (带有模块列的面向未来的证明。)

Java访问修饰符

说明

  • 私有成员( i可在声明的同一个类中访问。

  • 没有访问修饰符j )的成员只能在同一包中的类内访问。

  • 受保护的成员( k )可在同一包的所有类中以及在其他包的子类中访问。

  • 所有类都可以访问公共成员( l )(除非它驻留在不导出声明其的包的模块中)。


选择哪个修饰符?

访问修饰符是一种工具,可帮助您防止意外破坏封装(*) 。问问自己,您是否希望成员成为类,包,类层次结构的内部成员或根本不是内部成员,然后相应地选择访问级别。

例子:

  • 字段long internalCounter可能应该是私有的,因为它是可变的和实现细节。
  • 仅应在工厂类(在同一包中)实例化的类应具有受包限制的构造函数,因为不可能直接从包外部调用它。
  • 应该保护在内部渲染之前调用的内部void beforeRender()方法,该方法在子类中用作钩子。
  • 从 GUI 代码调用的void saveGame(File dst)方法应该是公共的。

(*) 什么是封装?

____________________________________________________________________
                | highest precedence <---------> lowest precedence
*———————————————+———————————————+———————————+———————————————+———————
 \ xCanBeSeenBy | this          | any class | this subclass | any
  \__________   | class         | in same   | in another    | class
             \  | nonsubbed     | package   | package       |    
Modifier of x \ |               |           |               |       
————————————————*———————————————+———————————+———————————————+———————
public          |       ✔       |     ✔     |       ✔       |   ✔  
————————————————+———————————————+———————————+———————————————+———————
protected       |       ✔       |     ✔     |       ✔       |   ✘   
————————————————+———————————————+———————————+———————————————+———————
package-private |               |           |               |
(no modifier)   |       ✔       |     ✔     |       ✘       |   ✘   
————————————————+———————————————+———————————+———————————————+———————
private         |       ✔       |     ✘     |       ✘       |   ✘    
____________________________________________________________________

简单的规则。首先将所有内容声明为私有。然后,随着需求的增长和设计的需要,向公众发展。

公开成员时,请问自己是公开代表选择还是抽象选择。第一个是您要避免的事情,因为它将对实际表示而不是其可观察的行为引入过多的依赖。

作为一般规则,我尝试通过子类化来避免覆盖方法的实现。弄乱逻辑太容易了。如果打算覆盖抽象的受保护方法,请声明它。

另外,在覆盖时使用 @Override 批注,以防止重构时发生破坏。

它实际上比简单的网格显示要复杂一些。网格会告诉您是否允许访问,但是究竟是什么构成访问?此外,访问级别以复杂的方式与嵌套类和继承进行交互。

“默认” 访问权限(由缺少关键字指定)也称为package-private 。例外:在接口中,没有修饰符表示公共访问;禁止使用除 public 之外的修饰符。枚举常量始终是公共的。

摘要

是否允许使用此访问说明符访问成员?

  • 成员是private :仅当成员与调用代码在同一类中定义时。
  • 成员是包私有的:仅当调用代码在成员的直接封装中。
  • 成员protected :同一包,或者如果成员在包含调用代码的类的超类中定义,则为protected成员。
  • 成员是public :是的。

什么是访问说明符

局部变量和形式参数不能使用访问说明符。由于根据作用域规则,它们本质上是外部无法访问的,因此它们实际上是私有的。

对于顶级范围的类,仅允许使用public和 package-private。这种设计选择大概是因为protectedprivate在程序包级别是多余的(没有程序包的继承)。

所有访问说明符都可用于类成员(构造函数,方法和静态成员函数,嵌套类)。

相关: Java 类可访问性

订购

访问说明符可以严格订购

公共 > 受保护 > 包私有 > 私有

意味着public提供最多的访问权限, private提供最少的访问权限。对于包私有成员,任何对私有成员的引用都有效。对包专用成员的任何引用在受保护成员上均有效,依此类推。 (在同一软件包中将受保护的成员授予其他类的访问权限被认为是错误的。)

笔记

  • 类的方法可以访问同一类的其他对象的私有成员。更准确地说,类 C 的方法可以访问 C 的任何子类的对象上 C 的私有成员。Java 不支持仅按类限制实例的访问。 (与 Scala 比较,后者确实使用private[this]支持它。)
  • 您需要访问构造函数才能构造对象。因此,如果所有构造函数都是私有的,则只能通过该类中的代码(通常是静态工厂方法或静态变量初始化器)来构造该类。对于包私有或受保护的构造函数也是如此。
    • 仅具有私有构造函数也意味着该类不能在外部进行子类化,因为 Java 要求子类的构造函数隐式或显式调用超类构造函数。 (但是,它可以包含一个嵌套的类来对其进行子类化。)

内部班

您还必须考虑嵌套作用域,例如内部类。复杂性的一个示例是内部类具有成员,这些成员本身可以使用访问修饰符。因此,您可以与公共成员一起拥有私人内部类;可以访问该成员吗? (请参见下文。)一般规则是查看范围并进行递归思考,以查看是否可以访问每个级别。

但是,这非常复杂,有关详细信息, 请查阅 Java Language Specification 。 (是的,过去有编译器错误。)

要了解这些交互的方式,请考虑以下示例。可以 “泄漏” 私有内部类;这通常是警告:

class Test {
    public static void main(final String ... args) {
        System.out.println(Example.leakPrivateClass()); // OK
        Example.leakPrivateClass().secretMethod(); // error
    }
}

class Example {
    private static class NestedClass {
        public void secretMethod() {
            System.out.println("Hello");
        }
    }
    public static NestedClass leakPrivateClass() {
        return new NestedClass();
    }
}

编译器输出:

Test.java:4: secretMethod() in Example.NestedClass is defined in an inaccessible class or interface
        Example.leakPrivateClass().secretMethod(); // error
                                  ^
1 error

一些相关的问题:

根据经验:

  • private :类范围。
  • default (或package-private ):包范围。
  • protectedpackage scope + child (类似于包,但是我们可以从不同的包中将其子类化)。 protected 修饰符始终保持 “父子” 关系。
  • public :无处不在。

结果,如果我们将访问权限分为三个权限:

  • (D)irect (从相同类内的方法调用,或通过 “this” 语法调用)。
  • (R)引用(使用对类的引用或通过 “点” 语法调用方法)。
  • (I)继承 (通过子类化)。

那么我们有这个简单的表:

+—-———————————————+————————————+———————————+
|                 |    Same    | Different |
|                 |   Package  | Packages  |
+—————————————————+————————————+———————————+
| private         |   D        |           |
+—————————————————+————————————+———————————+
| package-private |            |           |
| (no modifier)   |   D R I    |           |
+—————————————————+————————————+———————————+
| protected       |   D R I    |       I   |
+—————————————————+————————————+———————————+
| public          |   D R I    |    R  I   |
+—————————————————+————————————+———————————+

在很短的时间内

  • public :从任何地方均可访问。
  • protected :可以由同一软件包的类以及任何软件包中的子类访问。
  • 默认值(未指定修饰符):同一包的类可以访问。
  • private :只能在同一类中访问。

Java 中最容易理解的访问修饰符protected 。我们知道它类似于默认修饰符,但有一个例外,子类可以看到它。但是如何?这是一个示例,希望可以澄清这种混淆:

  • 假设我们有 2 个班级; FatherSon ,每个人都有自己的包装:

    package fatherpackage;
    
    public class Father
    {
    
    }
    
    -------------------------------------------
    
    package sonpackage;
    
    public class Son extends Father
    {
    
    }
  • 让我们向Father添加一个受保护的方法foo()

    package fatherpackage;
    
    public class Father
    {
        protected void foo(){}
    }
  • 可以在 4 个上下文中调用foo()方法:

    1. 在一个类中,该类位于定义foo()的同一包中( fatherpackage ):

      package fatherpackage;
      
      public class SomeClass
      {
          public void someMethod(Father f, Son s)
          {
              f.foo();
              s.foo();
          }
      }
    2. 在子类内部,通过thissuper在当前实例上:

      package sonpackage;
      
      public class Son extends Father
      {
          public void sonMethod()
          {
              this.foo();
              super.foo();
          }
      }
    3. 在类型相同的引用上:

      package fatherpackage;
      
      public class Father
      {
          public void fatherMethod(Father f)
          {
              f.foo(); // valid even if foo() is private
          }
      }
      
      -------------------------------------------
      
      package sonpackage;
      
      public class Son extends Father
      {
          public void sonMethod(Son s)
          {
              s.foo();
          }
      }
    4. 上的参考,其类型是父类,它是里面 ,其中包装foo()被定义( fatherpackage )[这可以没有包括上下文内。 1]:

      package fatherpackage;
      
      public class Son extends Father
      {
          public void sonMethod(Father f)
          {
              f.foo();
          }
      }
  • 以下情况无效。

    1. 在类型为父类且位于定义foo()的包之外的引用( fatherpackage )上:

      package sonpackage;
      
      public class Son extends Father
      {
          public void sonMethod(Father f)
          {
              f.foo(); // compilation error
          }
      }
    2. 子类包中的非子类(子类从其父类继承受保护的成员,并使它们对非子类私有):

      package sonpackage;
      
      public class SomeClass
      {
          public void someMethod(Son s) throws Exception
          {
              s.foo(); // compilation error
          }
      }

私人的

  • 方法,变量和构造函数

声明为私有的方法,变量和构造函数只能在声明的类本身内访问。

  • 类和接口

专用访问修饰符是最严格的访问级别。类和接口不能是私有的。

注意

如果类中存在公共获取方法,则可以在类外部访问声明为私有的变量。在超类中声明为受保护的变量,方法和构造函数只能由其他包中的子类或受保护成员类的包中的任何类访问。


受保护的

  • 类和接口

受保护的访问修饰符不能应用于类和接口。

方法,字段可以声明为受保护,但是接口中的方法和字段不能声明为受保护。

注意

受保护的访问使子类有机会使用 helper 方法或变量,同时防止无关的类尝试使用它。


上市

可以从任何其他类访问已声明为公共的类,方法,构造函数,接口等。

因此,可以从属于 Java Universe 的任何类中访问在公共类内声明的字段,方法,块。

  • 不同包装

但是,如果我们尝试访问的公共类位于不同的包中,则仍然需要导入公共类。

由于类继承,类的所有公共方法和变量均由其子类继承。


默认 - 无关键字:

默认访问修饰符意味着我们不会为类,字段,方法等明确声明访问修饰符。

  • 在同一包装内

在没有任何访问控制修饰符的情况下声明的变量或方法可用于同一包中的任何其他类。接口中的字段隐式为 public static final,而接口中的方法默认为 public。

注意

我们无法覆盖静态字段。如果您尝试覆盖它,则不会显示任何错误,但是除了我们无法正常工作之外,其他所有内容都无法使用。

相关答案

参考链接

http://docs.oracle.com/javase/tutorial/java/javaOO/accesscontrol.html http://www.tutorialspoint.com/java/java_access_modifiers.htm