Java 中的访问控制修饰符已经困惑笔者多时,其中较复杂的情况一直不能理解透彻。今天下定决心,系统、全面地研究 Java 中的访问控制修饰符的所有方面,并整理成这篇文章,希望有同样疑惑的读者读完后能有所收获。如果文章中出现错误,欢迎评论指出,共同交流~
说在前面:这篇文章只研究 Java 中访问控制修饰符声明类的变量/方法的情况。
先抛出结论:
成员变量/方法的访问权限 | private | default | protected | public |
---|---|---|---|---|
自己包自己类 | √ | √ | √ | √ |
自己包别的类 | √ | √ | √ | |
别的包别的类有继承关系② | √ | √ | ||
别的包别的类无继承关系 | √ |
①:子类可以继承,但是不能访问父类的成员变量/方法(一般来说,可以访问就可以继承)。
②:有继承关系说明访问对象所在的类是父类。
1.让我们来看一下 Java 中访问控制修饰符的定义。
Java 中,可以使用访问控制符来保护对类、变量、方法和构造方法的访问。
访问的形式有以下四种:
某个类的成员变量访问某个类的成员变量
某个类的成员变量访问某个类的成员方法
某个类的成员方法访问某个类的成员变量
某个类的成员方法访问某个类的成员方法
ps:以下代码均以第三种形式为例,其他形式基本一致。
根据访问对象的不同,访问的方式又可划分为两大类:
- 访问对象在同一个类,此时可以通过[成员变量/方法的名字]直接访问。
class A {
int a = 10;
void printA() {
System.out.println(a);
}
}
printA() 要访问 a ,因为它们在同一个类,所以可以通过 a 直接访问。
- 访问对象在不同类(假设访问对象在类 B),此时可以通过声明、初始化 B 的一个对象,通过[对象名.成员变量/方法的名字]进行访问。
ps:这种情况仅限于成员方法访问成员变量/方法。
class A {
void printB() {
B ob = new B();
System.out.println(ob.b);
}
}
class B {
int b = 10;
}
A 中的 printB() 要访问 B 中的 b,因为它们不在同一个类,所以可以在 printB() 中声明、初始化 B 的一个对象 ob,通过 ob.b 进行访问。
此外,当访问对象为静态变量/方法时,可以通过[访问对象所在类的类名.成员变量/方法的名字]进行访问。
class A {
static int a = 10;
int doubleA = A.a * 2;
void printB() {
System.out.println(B.b);
}
}
class B {
static int b = 10;
}
doubleA 要访问 a ,由于 a 为静态变量,因此可以通过 A.a 进行访问。
A 中的 printB() 要访问 B 中的 b,由于 b 为静态变量,因此可以通过 B.b 进行访问。
2.结论中提到了包,我们来看一下 Java 中包的定义和作用。
为了更好地组织类,Java 提供了包机制,用于区别类名的命名空间。
包的作用
- 把功能相似或相关的类或接口组织在同一个包中,方便类的查找和使用。
- 如同文件夹一样,包也采用了树形目录的存储方式。同一个包中的类名字是不同的,不同的包中的类的名字是可以相同的,当同时调用两个不同包中相同类名的类时,应该加上包名加以区别。因此,包可以避免名字冲突。
- 包也限定了访问权限,拥有包访问权限的类才能访问某个包中的类。
Java 使用包(package)这种机制是为了防止命名冲突,访问控制,提供搜索和定位类(class)、接口、枚举(enumerations)和注释(annotation)等。
关于包的使用方法,请参考 Java 教程包(package),在此不详细赘述。
值得注意的是,import 关键字引入的是 class 文件,而非 java 文件。
3.结论中还提到了继承,我们来看一下 Java 中继承的定义。
继承是java面向对象编程技术的一块基石,因为它允许创建分等级层次的类。继承可以理解为一个对象从另一个对象获取属性的过程。
关于继承的细节,请参照 Java 教程 继承。,在此不详细赘述。
需要理解的是,子类继承父类的成员变量/方法时,是先访问再继承。因此上面访问权限的规则同样适用于继承。
在同一个包里,如果父类的某个成员变量/方法可以被访问,则该成员变量/方法可以被继承。即如果在子类成员方法中,声明、初始化父类的一个对象后,可以通过[对象名.成员变量/方法 a]访问 a ,则声明、初始化子类的一个对象后,也一定可以通过[对象名.成员变量/方法 a]访问 a 。
class A extends B {
void printB() {
B ob = new B();
System.out.println(ob.b);
A ob2 = new A();
System.out.println(ob2.b);
}
}
class B {
int b = 10;
}
A 继承 B,因此 A 继承 B 的成员变量 b。由于 A 在 printB() 中,声明、初始化 B 的一个对象 ob 后,可以通过 ob.b 访问 b,则声明、初始化A的一个对象 ob2 后,可能通过 ob2.b 访问 b。(可以访问则可以继承)。
然而,在不同包里,子类继承父类时,子类只能访问父类的 public 型成员变量/方法,却能继承父类的 protected 和 public 型成员变量/方法。(请看下面的例子)
值得注意的是,子类继承父类的成员变量/方法,并不意味着这些成员变量/方法存在于子类,因此不能通过[成员变量/方法的名字]直接访问。可以理解为继承而来的成员变量/方法进入了子类的异次元(雾)。
当然,如果继承而来的成员变量/方法被重写,这些成员变量/方法就存在于子类了,此时可以通过[成员变量/方法的名字]直接访问。
此处不讨论多态的情况,请参照 Java 教程 多态。
回到结论,让我们来一层层地验证 Java 中的访问控制修饰符。
/* Stark.java */
package winter.is.coming;
public class Stark {
private boolean ned;
boolean robb;
protected boolean sansa;
public boolean arya;
void howIsNed() {
System.out.println(ned);
}
}
class Snow {
void whoseBastard() {
Stark stark = new Stark();
// System.out.println(stark.ned); 不可访问
System.out.println(stark.robb);
}
}
/* Greyjoy.java */
import winter.is.coming.Stark;
public class Greyjoy extends Stark {
void betray() {
Stark stark = new Stark();
// System.out.println(stark.robb); 不可访问
// System.out.println(stark.sansa); 不可访问
Greyjoy greyjoy = new Greyjoy();
// System.out.println(greyjoy.robb); 不可访问
System.out.println(greyjoy.sansa);
}
}
/* Bolton.java */
import winter.is.coming.Stark;
public class Bolton {
void flay() {
Stark stark = new Stark();
System.out.println(stark.arya);
}
}
① 自己包自己类 – private 可访问
Stark 中的 howIsNed() 可以访问 Stark 中 private 型的 ned 。
② 自己包别的类 – default 可访问
Snow 中的 whoseBastard() 可以访问 Stark 中 default 型的 robb,不可以访问 Stark 中 private 型的 ned。
③ 别的包别的类有继承关系 – protected 可继承,不可访问
Greyjoy 中的 betray() 可以继承 Stark 中 protected 型的 sansa,不可以访问 Stark 中 protected 型的 sansa,也不可以继承和访问 Stark 中 default 型的 robb。
④ 别的包别的类无继承关系 – public 可访问
Bolton 中的 flay() 可以访问 Stark 中 public 型的 arya。