首先来一张图:
对于一个类而言访问控制符只有一个public和默认无修饰符。其他的几个访问修饰符对于变量和方法都可以使用。
下面介绍具体的使用。
1. 公有访问控制符(public)
Java的类是通过包的概念来组织的,包是类的一个松散的集合。处于同一个包中的类可以不需要任何说明二方便地相互访问和引用,而对于不同包中的类,则不行。
但如果一个类被声明为public时,它就具有了被其他包中的类访问的可能性,只要这些其他包中的类在程序中使用了import语句引入了public类,就可以访问和引用这个类。
每个Java程序的主类必须是public类,也是基于相同的原因。
用public修饰的类变量称为公共变量。如果公共变量属于公共类,则它能被所有的其他类所引用。public修饰符会造成安全性的数据封装性下降,所以一般减少public域的使用。
2. 私有访问控制符(private)
用private修饰的变量或方法只能被该类自身所访问和修改,而且不能被其他任何类(包括该类的子类)来获取和引用。private修饰符用来声明那些类的私有成员,它提供了最高的保护级别。
3. 保护访问控制符(protected)
用protected修饰的成员变量可以被3种类所引用:该类自身、与它在同一个包中的其他类、在其他包中该类的子类。使用protected修饰符的主要作用是允许其他包中该类的子类来访问父类的特定属性。
4. 默认访问控制符
默认访问控制权规定,该类只能被同一个包中的类访问和引用,而不可以被其他包中的类使用,这种访问特性又称为包访问性。
同样道理,类内的变量或方法如果没有访问控制符来规定,也就是具有包访问性。简单地说,定义在同一个程序中的所有类属于一个包。
5. 总结
简单总结一下,按它们访问范围由大到小排列如下:
public:任何地方均可访问
protected:同一包和子类可见
默认:同一包中可见
private:仅该类部可见
6. 问题
6.1 com.tuhooo和com.tuhooo.test这两个包有什么关系么?
目测没啥关系,因为包起到的是命名空间的作用,似乎并没有父包和子包的概念。
6.2 实际用protect这个访问控制符我用得比较少,倒是看见很多框架源码中用的是这个protected。
6.3 java中的访问控制符还是挺好理解的,那么在字节码层面是如何实现访问控制符的呢?
7. 访问控制符的底层探索
这里用一个比较简单的类Student.java作为示例,在这个类中四种访问控制符都用到了。
package com.tuhooo.demo.test; public class Student {
private int age;
protected double salary;
public String name;
char sex;
}
通过如下两种命令,先编译然后得到底层的字节码。
javac Student.java
javap -verbose Student.class > info.log
Classfile /C:/Users/tuhooo/IdeaProjects/demo/src/com/tuhooo/demo/test/Student.class
Last modified 2018-5-16; size 302 bytes
MD5 checksum 92caa220b2c7efcdda2a68e7e53c63e7
Compiled from "Student.java"
public class com.tuhooo.demo.test.Student
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #3.#18 // java/lang/Object."<init>":()V
#2 = Class #19 // com/tuhooo/demo/test/Student
#3 = Class #20 // java/lang/Object
#4 = Utf8 age
#5 = Utf8 I
#6 = Utf8 salary
#7 = Utf8 D
#8 = Utf8 name
#9 = Utf8 Ljava/lang/String;
#10 = Utf8 sex
#11 = Utf8 C
#12 = Utf8 <init>
#13 = Utf8 ()V
#14 = Utf8 Code
#15 = Utf8 LineNumberTable
#16 = Utf8 SourceFile
#17 = Utf8 Student.java
#18 = NameAndType #12:#13 // "<init>":()V
#19 = Utf8 com/tuhooo/demo/test/Student
#20 = Utf8 java/lang/Object
{
protected double salary;
descriptor: D
flags: ACC_PROTECTED public java.lang.String name;
descriptor: Ljava/lang/String;
flags: ACC_PUBLIC char sex;
descriptor: C
flags: public com.tuhooo.demo.test.Student();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
}
SourceFile: "Student.java"
主版本号(major)下面有两个字节代表访问标志(access_flags),这个标志用于识别一些类或接口层次的访问信息,包括:这个Class是类还是接口;是否定义为public类型;是否定义为abstract类型;如果是类的话是否声明为final等。具体的标志位以及标志位的含义如下表:
access_flags中一共有16个标志位可以使用,当前只定义了其中的8个,没有使用到的标志位要求一律为0。以当前的Student类为例,这是一个普通的类,不是接口、枚举或者注解,被public关键字修饰但并没被声明为fiana和abstract,并且用了JDK1.2之后的编译器进行编译,因此它的ACC_PUBLIC、ACC_SUPER标志应当为真,而上图中的其他8个标志为假,所以它的access_flags的值应该为:0x0001|0x0020=0x0021。
在大括号中分别描述了除了private之外的3中访问控制符对应的字段,就是flags对应的。话说为啥要用flags呢,不是一个字段只有一种访问控制符么?
这里只是趁着熟悉访问控制符偷看了一下字节码,不过感觉不深入。还是有几个问题:
1. 反射的时候为私有属性设置值的时候是怎么做到的?
2. 运行时怎么进行访问控制符的检查的呢?