《On Java 中文版 基础卷》阅读笔记 实现隐藏 117.2 Java 访问权限修饰符Java 访问权限修饰符包括 public、protected 和 private它们放在类中成员定义的前面包括字段和方法。每个访问权限修饰符仅控制对该特定定义的访问。java 规定一个 java 文件可以有多个类但只能有一个 public 修饰的类。如果不提供访问权限修饰符则表示 “包访问权限”。因此无论如何任何事物都有某些形式的访问控制。在接下来的几节里你将了解到各种访问权限。7.2.1 包访问权限本章之前的所有示例都只使用了 public 修饰符或者没有使用修饰符即默认访问权限。默认访问权限没有关键字通常称为包访问权限有时称为 “友好访问权限”。这意味着当前包中的所有其他类都可以访问该成员。对于此包之外的所有类该成员显示为 private。一个编译单元即一个 java 文件只能属于一个包所以一个编译单元中的所有类都可以通过包访问权限来相互访问。一个 java 文件是一个编译单元一个 java 文件中可以包含多个类 class只能有一个 public 修饰的类 class。包访问权限将相关的类分组到一个包中以便它们可以轻松地交互。包中的类可以访问该包里具有包访问权限的成员因此你 “拥有” 了该包中的代码。只有你拥有的代码才能对你拥有的其他代码进行包访问这是合理的。包访问权限是将类分组到一个包中的原因之一。在许多语言里你可以任意组织文件中的定义方式但在 Java 中你必须以合理的方式组织它们。另外如果有的类不应该访问当前包中定义的类的话你可能需要排除它。类控制着哪些代码可以访问其成员。来自另一个包的代码不能只是因为说了一句 “嗨我是 Bob 的朋友”就能看到 Bob 的 protected、包访问权限和 private 成员。授予成员访问权限的唯一方法如下所示。1.将成员设为 public这样任何地方的任何人都可以访问它。2.通过去掉访问权限修饰符来授予成员包访问权限并将其他类放在同一个包中这样该包中的其他类就可以访问该成员了。3.正如你在第 8 章会看到的当引入继承时**通过继承而来的类子类可以访问父类的 protected 成员以及 public 成员但不能访问 private 成员。只有当两个类在同一个包中时它才可以访问父类的包访问权限成员。**但现在不必担心继承和 protected。4.提供可以读取和更改值的访问器 (accessor) 和修改器 (mutator) 方法也称为 “get/set” 方法。7.2.2 public接口访问权限当使用 public 关键字时就意味着紧跟在 public 后面的成员声明对于所有人都是可用的尤其是对于使用该库的客户程序员。假设你定义了一个包含以下编译单元的 dessert 包// hiding/dessert/Cookie.java// (c)2021 MindView LLC: see Copyright.txt// We make no guarantees that this code is fit for any purpose.// Visit http://OnJava8.com for more book information.// Creates a librarypackagehiding.dessert;publicclassCookie{publicCookie(){System.out.println(Cookie constructor);}voidbite(){System.out.println(bite);}}记住Cookie.java 生成的类文件必须置于名为 dessert 的子目录中该子目录位于 hiding 目录下表示本书的第 7 章而 hiding 目录必须位于 CLASSPATH 指定的路径之一里。不要错误地认为 Java 会始终将当前目录视为搜索的起点之一。如果 CLASSPATH 中没有指定 “.” 作为路径之一Java 就不会在当前目录里查找。现在可以创建一个程序来使用 Cookie 了packagehiding;// hiding/Dinner.java// (c)2021 MindView LLC: see Copyright.txt// We make no guarantees that this code is fit for any purpose.// Visit http://OnJava8.com for more book information.// Uses the libraryimporthiding.dessert.*;publicclassDinner{publicstaticvoidmain(String[]args){CookiexnewCookie();//- x.bite(); // Cant access}}/* Output: Cookie constructor */你可以创建一个 Cookie 对象因为它的构造函数是 public 的而且类也是 public 的**稍后将进一步介绍 public 类的概念**。然而bite() 方法在 Dinner.java 中是不可访问的因为 bite() 只提供了在包 dessert 中的访问所以编译器会阻止你使用它。默认包你可能会惊讶地发现以下代码可以编译尽管它看起来似乎违反了规则没有指明 package。// hiding/Cake.java// (c)2021 MindView LLC: see Copyright.txt// We make no guarantees that this code is fit for any purpose.// Visit http://OnJava8.com for more book information.// Accesses a class in a separate compilation unitclassCake{publicstaticvoidmain(String[]args){PiexnewPie();x.f();}}/* Output: Pie.f() */下面的代码是处于相同目录中的第二个文件// hiding/Pie.java// (c)2021 MindView LLC: see Copyright.txt// We make no guarantees that this code is fit for any purpose.// Visit http://OnJava8.com for more book information.// The other classclassPie{voidf(){System.out.println(Pie.f());}}乍一看这两个文件好像完全不相关但 Cake 能够创建一个 Pie 对象并调用它的 f() 方法注意 CLASSPATH 中必须有 “.” 才能编译没有配置 CLASSPATH 也能编译。你通常可能会认为 Pie 和 f() 具有包访问权限因此不能用于 Cake。它们确实有包访问权——这是正确的。它们在 Cake.java 中可用是因为它们都在同一目录中并且没有明确的包名。Java 将这样的文件看作属于该目录的 “默认包” 的隐含部分因此它们为该目录中所有其他文件提供了包访问权限。7.2.3 private你无法访问它private 关键字意味着除了包含该成员的类之外其他任何类都不能访问当然在该类的方法内部也能访问。同一个包中的其他类无法访问 private 成员因此这就好像你故意将类与自己隔离。不过从另一方面来说一个包由几个人合作创建的可能性也并非不存在。使用 private 的话就可以自由更改该成员而不必担心它会影响同一个包中的另一个类。默认的包访问权限通常已经提供了足够的隐藏。记住使用该类的客户程序员是无法访问包访问权限成员的。这是比较合理的因为默认的包访问权限是你通常使用的访问权限如果你忘记添加任何访问控制则会默认获得该访问权限。因此你通常会考虑的是哪些成员需要公开访问权限来给客户程序员调用。最初你可能认为自己不会经常使用 private 关键字因为没有它好像也可以正常工作。但是坚持使用 private 是十分重要的尤其在涉及多线程的情况下正如你将在进阶卷第 5 章中看到的那样。下面是一个使用 private 的例子packagehiding;// hiding/IceCream.java// (c)2021 MindView LLC: see Copyright.txt// We make no guarantees that this code is fit for any purpose.// Visit http://OnJava8.com for more book information.// Demonstrates private keywordclassSundae{privateSundae(){}staticSundaemakeASundae(){returnnewSundae();}}publicclassIceCream{publicstaticvoidmain(String[]args){//- Sundae x new Sundae();SundaexSundae.makeASundae();}}上面的示例显示了一个 private 大显身手的场景控制对象的创建方式并防止特定的构造器或所有构造器被调用。在这个例子中你不能通过它的构造器来创建一个 Sundae 对象相反你必须调用 makeASundae() 方法来做这件事。只要能确定是类的 “辅助” 方法这个方法就可以设为 private以确保在包的其他地方不会意外地使用它从而让自己无法再更改或删除。将方法指定为 private 让你保留了这些选择。对于类中的 private 字段也是如此。除非你必须要公开底层实现可能性比你以为的要小否则就将字段设为 private。然而仅仅因为一个对象的引用在类中是 private 的并不意味着其他对象不能拥有对同一个对象的public引用。请参阅进阶卷第 2 章了解别名问题。7.2.4 protected继承访问权限要理解 protected 访问权限修饰符就需要提前做些准备。首先注意在第 8 章介绍继承之前你即使不理解这一节的内容也可以继续阅读本书。但为了完整起见这里是 protected 的一个简要说明和相关示例。protected 关键字处理的是继承的概念它利用一个现有类——我们叫作基类 (base class) ——并在不修改现有类的情况下向该类添加新成员还可以改变该类现有成员的行为。为了继承一个类你需要声明自己的新类扩展 (extends) 了现有类如下所示classFooextendsBar{类定义的其余部分看起来都是一样的。如果你创建了一个新包并需要继承另一个包中的类那么唯一可以访问的成员是这个类的 public 成员。当然如果继承同一个包中的类就可以操作所有的包访问权限成员了。有时候基类的创建者想要把特定成员的访问权限赋给子类而不是所有的类这时候 protected 就可以发挥作用了。protected 还提供了包访问权也就是说同一包中的其他类也可以访问 protected 元素。如果你回顾文件 Cookie.java就会知道下面的类不能调用包访问权限成员 bite()packagehiding;// hiding/ChocolateChip.java// (c)2021 MindView LLC: see Copyright.txt// We make no guarantees that this code is fit for any purpose.// Visit http://OnJava8.com for more book information.// Cant use package-access member from another packageimporthiding.dessert.*;publicclassChocolateChipextendsCookie{publicChocolateChip(){System.out.println(ChocolateChip constructor);}publicvoidchomp(){//- bite(); // Cant access bite}publicstaticvoidmain(String[]args){ChocolateChipxnewChocolateChip();x.chomp();}}/* Output: Cookie constructor ChocolateChip constructor */如果类 Cookie 中存在一个方法 bite()那么这个方法也存在于任何继承 Cookie 的类中。但是 bite() 只具有包访问权限并且位于另一个包中因此无法在当前包中使用它。你可以将其修改为 public但这样的话每个人就都可以访问它了这也许不是你想要的。如果按如下方式更改类 Cookie// hiding/cookie2/Cookie.java// (c)2021 MindView LLC: see Copyright.txt// We make no guarantees that this code is fit for any purpose.// Visit http://OnJava8.com for more book information.packagehiding.cookie2;publicclassCookie{publicCookie(){System.out.println(Cookie constructor);}protectedvoidbite(){System.out.println(bite);}}这样任何继承 Cookie 的类都可以访问 bite()packagehiding;// hiding/ChocolateChip2.java// (c)2021 MindView LLC: see Copyright.txt// We make no guarantees that this code is fit for any purpose.// Visit http://OnJava8.com for more book information.importhiding.cookie2.*;publicclassChocolateChip2extendsCookie{publicChocolateChip2(){System.out.println(ChocolateChip2 constructor);}publicvoidchomp(){bite();}// Protected methodpublicstaticvoidmain(String[]args){ChocolateChip2xnewChocolateChip2();x.chomp();}}/* Output: Cookie constructor ChocolateChip2 constructor bite */这时尽管 bite() 也有包访问权限但它不是 public 的。7.2.5 包访问权限与公共构造器当定义一个具有包访问权限的类时你可以给它一个 public 构造器此时编译器不会提示任何错误// hiding/packageaccess/PublicConstructor.java// (c)2021 MindView LLC: see Copyright.txt// We make no guarantees that this code is fit for any purpose.// Visit http://OnJava8.com for more book information.packagehiding.packageaccess;classPublicConstructor{publicPublicConstructor(){}}如果运行 Checkstyle 这样的工具它会指出这是虚假陈述并且从技术上讲这样定义类是错误的。你实际上无法从包外部访问这个所谓的 public 构造器packagehiding;// hiding/CreatePackageAccessObject.java// (c)2021 MindView LLC: see Copyright.txt// We make no guarantees that this code is fit for any purpose.// Visit http://OnJava8.com for more book information.// {WillNotCompile}importhiding.packageaccess.*;publicclassCreatePackageAccessObject{publicstaticvoidmain(String[]args){newPublicConstructor();}}如果手动编译此文件你将看到一个编译器错误消息因此在只有包访问权限的类中声明一个 public 构造器实际上并不会使这个构造器成为 public 的编译器应该在声明处将其标记为一个错误。