Java访问权限修饰符共有四种:public、protected、private、没有修饰符(默认访问权限(default accsess))。
访问权限修饰符可以位于定义的类名,属性名和方法名之前。每个访问权限修饰符只能控制它所修饰的对象。如果不提供访问修饰符,就意味着“包访问权限”。所以无论如何,万物都有某种形式的访问控制权。
包访问权限
对于类、类中的方法和属性,如果没有使用修饰符(默认访问权限(default accsess)), 默认访问权限没有关键字,通常被称为包访问权限(packageaccess)(有时也称为friendly)。
这意味着当前包中的所有其他类都可以访问那个成员。对于这个包之外的类,这个成员看上去是private 的。
由于一个编译单元(即一个文件)只能隶属于一个包,所以通过包访问权限,位于同一编译单元中的所有类彼此之间
都是可访问的。包访问权限可以把相关类聚到一个包下,以便它们能轻易地相互访问。包里的类赋予了它们包访问权限的成员相互访问的权限,所以你“拥有”了包内的程序代码。
只能通过你所拥有的代码去访问你所拥有的其他代码,这样规定很有意义。构建包访问权限机制是将类聚集在包中的重要原因之一。在许多语言中,在文件中组织定义的方式是任意的,但是在Java中你被强制以一种合理的方式组织它们。另外,你可能会将不应该对当前包中的类具有访问权限的类排除在包外。
类控制着哪些代码有权访问自己的成员。其他包中的代码不能一上来就说“嗨,我是Bob的朋友!”,然后想看到Bob的protected、包访问权限和private成员。取得对成员的访问权的唯一方式是:
- 使成员成为public。那么无论是谁,无论在哪,都可以访问它。
- 赋予成员默认包访问权限,不用加任何访问修饰符,然后将其他类放在相同的包内。这样,其他类就可以访问该成员。
- 继承的类既可以访问public成员,也可以访问protected成员(但不能访问private 成员)。只有当两个类处于同一个包内,它才可以访问包访问权限的成员。
- 提供访问器(accessor) 和修改器(mutator) 方法(有时也称为“get/set"方法),从而读取和改变值。
public:接口访问权限
当你使用关键字public,就意味着紧随public后声明的成员对于每个人都是可用的,尤其是使用类库的客户端程序员更是如此。
看个例子。假设定义了一个包含下面编译单元的dessert包:
// hiding/dessert/Cookie.java
// Creates a library
package hiding.dessert;
public class Cookie {
public Cookie() {
System.out.println("Cookie constructor");
}
void bite() {
System.out.println("bite");
}
}
记住,Cookie.java 文件产生的类文件必须位于名为 dessert 的子目录中,该子目录在 hiding 包下,它必须在 CLASSPATH 的几个目录之下。不要错误地认为 Java 总是会将当前目录视作查找行为的起点之一。如果你的CLASSPATH 中没有 .,Java 就不会查找当前目录。
现在,使用 Cookie 创建一个程序,在hiding包下的Dinner类中使用hiding.dessert包下的Cookie类。
// hiding/Dinner.java
// Uses the library
import hiding.dessert.*;
public class Dinner {
public static void main(String[] args) {
Cookie x = new Cookie();
// -x.bite(); // Can't access
}
}
-------------------------------
//输出:
Cookie constructor
你可以创建一个 Cookie 对象,因为它构造器和类都是 public 的。但是,在 Dinner.java 中无法访问到 Cookie 对象中的 bite() 方法,因为 bite() 只提供了包访问权限,因而在 dessert 包之外无法访问,编译器禁止你使用它。
默认包
默认包,也就是包访问权限,看这个例子。在同一目录下有Cake、Pie两个类。
// hiding/Cake.java
// Accesses a class in a separate compilation unit
class Cake {
public static void main(String[] args) {
Pie x = new Pie();
x.f();
}
}
---------
//输出:
Pie.f()
// hiding/Pie.java
// The other class
class Pie {
void f() {
System.out.println("Pie.f()");
}
}
最初看上去这两个文件毫不相关,但在 Cake 中可以创建一个 Pie 对象并调用它的 f() 方法。(注意,你的 CLASSPATH 中一定得有 .,这样文件才能编译)通常会认为 Pie 和 f() 具有包访问权限,因此不能被 Cake 访问。它们的确具有包访问权限,这是部分正确。Cake.java 可以访问它们是因为它们在相同的目录中且没有给自己设定明确的包名。Java 把这样的文件看作是隶属于该目录的默认包中,因此它们为该目录中所有的其他文件都提供了包访问权限。
private:你无法访问
关键字 private 意味着除了包含该成员的类,其他任何类都无法访问这个成员。同一包中的其他类无法访问 private 成员,因此这等于说是自己隔离自己。另一方面,让许多人合作创建一个包也是有可能的。使用private,你可以自由地修改那个被修饰的成员,无需担心会影响同一包下的其他类。
默认的包访问权限通常提供了足够的隐藏措施;记住,使用类的客户端程序员无法访问包访问权限成员。这样做很好,因为默认访问权限是一种我们常用的权限(同时也是一种在忘记添加任何访问权限时自动得到的权限)。因此,通常考虑的是把哪些成声明成 public 供客户端程序员使用。所以,最初不常使用关键字 private,因为程序没有它也可以照常工作。然而,使用 private 是非常重要的,尤其是在多线程环境中。
// hiding/IceCream.java
// Demonstrates "private" keyword
class Sundae {
private Sundae() {}
static Sundae makeASundae() {
return new Sundae();
}
}
public class IceCream {
public static void main(String[] args) {
//- Sundae x = new Sundae();
Sundae x = Sundae.makeASundae();
}
}
以上展示了 private 的用武之地:控制如何创建对象,防止别人直接访问某个特定的构造器(或全部构造器)。例子中,你无法通过构造器创建一个 Sundae 对象,而必须调用 makeASundae() 方法创建对象。
任何可以肯定只是该类的 “助手” 方法,都可以声明为 private,以确保不会在包中的其他地方误用它,也防止了你会去改变或删除它。将方法声明为 private 确保了你拥有这种选择权。
对于类中的 private 属性也是一样。除非必须公开底层实现(这种情况很少见),否则就将属性声明为 private。然而,不能因为类中某个对象的引用是 private,就认为其他对象也无法拥有该对象的 public 引用。
protected:继承访问权限
关键字 protected 处理的是继承的概念,通过继承可以利用一个现有的类——我们称之为基类,然后添加新成员到现有类中而不必碰现有类。我们还可以改变类的现有成员的行为。为了从一个类中继承,需要声明新类 extends 一个现有类,像这样:
class Foo extends Bar {}
类定义的其他部分看起来是一样的。
如果你创建了一个新包,并从另一个包继承类,那么唯一能访问的就是被继承类的public 成员。(如果在同一个包中继承,就可以操作所有的包访问权限的成员。)有时,基类的创建者会希望某个特定成员能被继承类访问,但不能被其他类访问。这时就需要使用 protected。protected 也提供包访问权限,也就是说,相同包内的其他类可以访问 protected 元素。
看个例子。
// hiding/cookie2/Cookie.java
package hiding.cookie2;
public class Cookie {
public Cookie() {
System.out.println("Cookie constructor");
}
protected void bite() {
System.out.println("bite");
}
}
// hiding/ChocolateChip2.java
import hiding.cookie2.*;
public class ChocolateChip2 extends Cookie {
public ChocoalteChip2() {
System.out.println("ChocolateChip2 constructor");
}
public void chomp() {
bite(); // Protected method
}
public static void main(String[] args) {
ChocolateChip2 x = new ChocolateChip2();
x.chomp();
}
}
---------------------
//输出:
Cookie constructor
ChocolateChip2 constructor
bite
被protected修饰的bite() 对于所有继承 Cookie 的类,都是可访问的,尽管 bite() 也具有包访问权限,但它不是 public 的。
总结
使用一个类,可以分为这几种情况吧。在同包下使用、在包外使用、使用这个类中的成员。
- public:对于public修饰的类,你可以在任意位置使用这个类,对于这个类里的成员,还是要看成员前面的修饰符。
- 包访问权限:在同包下(同一目录下)可以访问。一个类里的成员如果没有修饰符,那也是包访问权限,只有同包下的程序中才能访问这个成员。
- private:除了包含该成员的类,其他任何类都无法访问这个成员。同一包中的其他类无法访问private修饰的成员。只能在自己类中使用。
- protected:处理的是继承的概念,被protected修饰的东西,可以被其子类访问,同时protected 也提供包访问权限,也就是说,相同包内的其他类也可以访问 protected 元素。
所以,对于类、类中的成员,判断是否可以访问,先判断是否可以访问到这个类,在判断是否可以访问这个类中的成员。
我们要注意一种情况:包访问权限 Vs Public构造器
包访问权限 Vs Public构造器
当你定义一个具有包访问权限的类时,你可以在类中定义一个 public 构造器,编译器不会报错:
// hiding/packageaccess/PublicConstructor.java
package hiding.packageaccess;
class PublicConstructor {
public PublicConstructor() {}
}
有一个 Checkstyle 工具,你可以运行命令 gradlew hiding:checkstyleMain 使用它,它会指出这种写法是虚假的,而且从技术上来说是错误的。实际上你不能从包外访问到这个 public 构造器:
// hiding/CreatePackageAccessObject.java
// {WillNotCompile}
import hiding.packageaccess.*;
public class CreatePackageAcessObject {
public static void main(String[] args) {
new PublicConstructor();
}
}
如果你编译下这个类,会得到编译错误信息:
CreatePackageAccessObject.java:6:error:
PublicConstructor is not public in hiding.packageaccess;
cannot be accessed from outside package
new PublicConstructor();
^
1 error
因此,在一个具有包访问权限的类中定义一个 public 的构造器并不能真的使这个构造器成为 public,在声明的时候就应该标记为编译时错误。