1 类访问权限
1.1 四种访问权限解析
Java
有四种访问权限, 其中三种有访问权限修饰符,分别为private,public和protected
,还有一种不带任何修饰符
四种访问权限:
-
private
:Java
语言中对访问权限限制的最窄的修饰符,一般称之为私有的
。被其修饰的类、属性以及方法只能被该类的对象访问,其子类不能访问,更不能允许跨包访问。 -
default
:即不加任何访问修饰符,通常称为默认访问模式
。该模式下,只允许在同一个包中进行访问。 -
protect
: 介于public 和 private 之间的一种访问修饰符,一般称之为保护形
。被其修饰的类、属性以及方法只能被类本身的方法及子类访问,即使子类在不同的包中也可以访问。 -
public
:Java
语言中访问限制最宽的修饰符,一般称之为公共的
。被其修饰的类、属性以及方法不仅可以跨类访问,而且允许跨包(package
)访问。
下面用表格的形式来展示四种访问权限之间的异同点,这样会更加形象。表格如下所示:
同一个类 | 同一个包 | 不同包的子类 | 不同包的非子类 | |
---|---|---|---|---|
Private | √ | |||
Default | √ | √ | ||
Protected | √ | √ | √ | |
Public | √ | √ | √ | √ |
1.2 Protected分析
假设在包accesscontrol
下面有AccessControlDemo
和 Base
两个类,其中protected double price;
是Base类
的成员变量,因为两个类在同一个包中,所以在AccessControlDemo
类中可以直接访问System.out.println(base.price);
具体实例如下:
accesscontrol.AccessControlDemo
package accesscontrol;
public class AccessControlDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
Base base=new Base("123-1",120.1);
System.out.println(base.price);
}
}
accesscontrol.Base
package accesscontrol;
public class Base {
private String isbn;
protected double price;
//默认构造函数
public Base() {}
//构造函数,如果只定义带参数的构造函数而不定义默认构造函数,那么Base的子类必须定义显式构造函数
public Base(String isbn, double price) {
this.isbn = isbn;
this.price = price;
}
public String getIsbn() {
return isbn;
}
public void setIsbn(String isbn) {
this.isbn = isbn;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
}
但是假如我们将AccessControlDemo
这个类移到test
这个包中,我们会发现eclipse
中提示错误,编译无法通过,因为在test
包中对protected
类型的成员变量不可见。
假如我们在test
包中创建一个Base
类的子类Bulk
,也就是说Bulk
是Base
类不同包的子类。那么在Bulk
类中能够直接访问protected double price;
这个基层自Base
类的成员变量,实例如下:
test.AccessControlDemo
package test;
public class AccessControlDemo {
public static void main(String[] args) {
Bulk bulk=new Bulk("123-1",120.1);
bulk.print();
}
}
test.Bulk
package test;
import accesscontrol.Base;
public class Bulk extends Base {
public Bulk() {
super();
}
public Bulk(String isbn, double price) {
super(isbn, price);
}
public void print()
{
System.out.println(this.price);
}
}
1.3 private失效情况
在Java
编程中,使用private
关键字修饰了某个成员,只有这个成员所在的类和这个类的方法可以使用,其他的类都无法访问到这个private
成员。
1.3.1 Java内部类
在Java
中相信很多人都用过内部类,Java
允许在一个类里面定义另一个类,类里面的类就是内部类,也叫做嵌套类。一个简单的内部类实现可以如下
class OuterClass {
class InnerClass{
}
}
一个我们在编程中经常用到的场景,就是在一个内部类里面访问外部类的private
成员变量或者方法,这是可以的。如下面的代码实现。
public class OuterClass {
private String language = "en";
private String region = "US";
public class InnerClass {
public void printOuterClassPrivateFields() {
String fields = "language=" + language + ";region=" + region;
System.out.println(fields);
}
}
public static void main(String[] args) {
OuterClass outer = new OuterClass();
OuterClass.InnerClass inner = outer.new InnerClass();
inner.printOuterClassPrivateFields();
}
}
这是为什么呢,不是private
修饰的成员只能被成员所述的类才能访问么?难道private
真的失效了么?
使用javap
命令查看一下生成的两个class
文件OuterClass
的反编译结果
$ javap -c OuterClass
Compiled from "OuterClass.java"
public class OuterClass extends java.lang.Object{
public OuterClass();
Code:
0: aload_0
1: invokespecial #11; //Method java/lang/Object."<init>":()V
4: aload_0
5: ldc #13; //String en
7: putfield #15; //Field language:Ljava/lang/String;
10: aload_0
11: ldc #17; //String US
13: putfield #19; //Field region:Ljava/lang/String;
16: return
public static void main(java.lang.String[]);
Code:
0: new #1; //class OuterClass
3: dup
4: invokespecial #27; //Method "<init>":()V
7: astore_1
8: new #28; //class OuterClass$InnerClass
11: dup
12: aload_1
13: dup
14: invokevirtual #30; //Method java/lang/Object.getClass:()Ljava/lang/Class;
17: pop
18: invokespecial #34; //Method OuterClass$InnerClass."<init>":(LOuterClass;)V
21: astore_2
22: aload_2
23: invokevirtual #37; //Method OuterClass$InnerClass.printOuterClassPrivateFields:()V
26: return
static java.lang.String access$0(OuterClass);
Code:
0: aload_0
1: getfield #15; //Field language:Ljava/lang/String;
4: areturn
static java.lang.String access$1(OuterClass);
Code:
0: aload_0
1: getfield #19; //Field region:Ljava/lang/String;
4: areturn
}
在OuterClass
中我们并没有定义这两个方法
static java.lang.String access$0(OuterClass);
Code:
0: aload_0
1: getfield #15; //Field language:Ljava/lang/String;
4: areturn
static java.lang.String access$1(OuterClass);
Code:
0: aload_0
1: getfield #19; //Field region:Ljava/lang/String;
4: areturn
}
从给出来的注释来看,access$0
返回outerClass
的language
属性;access$1
返回outerClass
的region
属性。并且这两个方法都接受OuterClass
的实例作为参数。这两个方法为什么生成呢,有什么作用呢?我们看一下内部类的反编译结果就知道了。OuterClass$InnerClass
的反编译结果
$ javap -c OuterClass\$InnerClass
Compiled from "OuterClass.java"
public class OuterClass$InnerClass extends java.lang.Object{
final OuterClass this$0;
public OuterClass$InnerClass(OuterClass);
Code:
0: aload_0
1: aload_1
2: putfield #10; //Field this$0:LOuterClass;
5: aload_0
6: invokespecial #12; //Method java/lang/Object."<init>":()V
9: return
public void printOuterClassPrivateFields();
Code:
0: new #20; //class java/lang/StringBuilder
3: dup
4: ldc #22; //String language=
6: invokespecial #24; //Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
9: aload_0
10: getfield #10; //Field this$0:LOuterClass;
13: invokestatic #27; //Method OuterClass.access$0:(LOuterClass;)Ljava/lang/String;
16: invokevirtual #33; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: ldc #37; //String ;region=
21: invokevirtual #33; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: aload_0
25: getfield #10; //Field this$0:LOuterClass;
28: invokestatic #39; //Method OuterClass.access$1:(LOuterClass;)Ljava/lang/String;
31: invokevirtual #33; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
34: invokevirtual #42; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
37: astore_1
38: getstatic #46; //Field java/lang/System.out:Ljava/io/PrintStream;
41: aload_1
42: invokevirtual #52; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
45: return
}
下面代码调用access$0
的代码,其目的是得到OuterClass
的language
私有属性。
13: invokestatic #27; //Method OuterClass.access$0:(LOuterClass;)Ljava/lang/String;
下面代码调用了access$1
的代码,其目的是得到OutherClass
的region
私有属性。
28: invokestatic #39; //Method OuterClass.access$1:(LOuterClass;)Ljava/lang/String;
注意
:在内部类构造的时候,会将外部类的引用传递进来,并且作为内部类的一个属性,所以内部类会持有一个其外部类的引用。this$0
就是内部类持有的外部类引用,通过构造方法传递引用并赋值。
final OuterClass this$0;
public OuterClass$InnerClass(OuterClass);
Code:
0: aload_0
1: aload_1
2: putfield #10; //Field this$0:LOuterClass;
5: aload_0
6: invokespecial #12; //Method java/lang/Object."<init>":()V
9: return
这部分private
看上去失效,可实际上并没有失效,因为当内部类调用外部类的私有属性时,其真正的执行是调用了编译器生成的属性的静态方法
(即acess$0
,access$1
等)来获取这些属性值。这一切都是编译器的特殊处理。
如果说上面的写法很常用,那么这样的写法是不是很少接触,但是却可以运行。
public class AnotherOuterClass {
public static void main(String[] args) {
InnerClass inner = new AnotherOuterClass().new InnerClass();
System.out.println("InnerClass Filed = " + inner.x);
}
class InnerClass {
private int x = 10;
}
}
和上面一样,使用javap
反编译看一下。不过这次我们先看一下InnerClass
的结果
16:03 $ javap -c AnotherOuterClass\$InnerClass
Compiled from "AnotherOuterClass.java"
class AnotherOuterClass$InnerClass extends java.lang.Object{
final AnotherOuterClass this$0;
AnotherOuterClass$InnerClass(AnotherOuterClass);
Code:
0: aload_0
1: aload_1
2: putfield #12; //Field this$0:LAnotherOuterClass;
5: aload_0
6: invokespecial #14; //Method java/lang/Object."<init>":()V
9: aload_0
10: bipush 10
12: putfield #17; //Field x:I
15: return
static int access$0(AnotherOuterClass$InnerClass);
Code:
0: aload_0
1: getfield #17; //Field x:I
4: ireturn
}
又出现了,编译器自动生成了一个获取私有属性的后门方法access$0
一次来获取x
的值。
AnotherOuterClass.class的反编译结果
16:08 $ javap -c AnotherOuterClass
Compiled from "AnotherOuterClass.java"
public class AnotherOuterClass extends java.lang.Object{
public AnotherOuterClass();
Code:
0: aload_0
1: invokespecial #8; //Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: new #16; //class AnotherOuterClass$InnerClass
3: dup
4: new #1; //class AnotherOuterClass
7: dup
8: invokespecial #18; //Method "<init>":()V
11: dup
12: invokevirtual #19; //Method java/lang/Object.getClass:()Ljava/lang/Class;
15: pop
16: invokespecial #23; //Method AnotherOuterClass$InnerClass."<init>":(LAnotherOuterClass;)V
19: astore_1
20: getstatic #26; //Field java/lang/System.out:Ljava/io/PrintStream;
23: new #32; //class java/lang/StringBuilder
26: dup
27: ldc #34; //String InnerClass Filed =
29: invokespecial #36; //Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
32: aload_1
33: invokestatic #39; //Method AnotherOuterClass$InnerClass.access$0:(LAnotherOuterClass$InnerClass;)I
36: invokevirtual #43; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
39: invokevirtual #47; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
42: invokevirtual #51; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
45: return
}
其中这句调用就是外部类通过内部类的实例获取私有属性x的操作
33: invokestatic #39; //Method AnotherOuterClass$InnerClass.access$0:(LAnotherOuterClass$InnerClass;)I
java
官方文档 有这样一句话
if the member or constructor is declared private, then access is permitted if and only if it occurs within the body of the top level class (§7.6) that encloses the declaration of the member or constructor.
意思是 如果(内部类的)成员和构造方法设定成了私有修饰符,当且仅当其外部类访问时是允许的。
那么如何让内部类私有成员不被外部访问,那就是使用匿名内部类。
如下,由于mRunnable
对象的类型为Runnable
,而不是匿名内部类的类型(我们无法正常拿到),而Runanble
中没有x这个属性,所以mRunnable.x
是不被允许的。
public class PrivateToOuter {
Runnable mRunnable = new Runnable(){
private int x=10;
@Override
public void run() {
System.out.println(x);
}
};
public static void main(String[] args){
PrivateToOuter p = new PrivateToOuter();
//System.out.println("anonymous class private filed= "+ p.mRunnable.x); //not allowed
p.mRunnable.run(); // allowed
}
}
最后总结private
表面上看上去失效了,但实际上是没有的,而是在调用时通过间接的方法来获取私有的属性。