在《Java关键字分类解析》一文里已经对Java的所有关键字进行了分类归组,并对部分关键字做了一些简单的介绍分析。不过对于修饰符这部分值得更详细的探讨,所以本文就来讲述下这些修饰符在Java中的功能及应用。
Java的关键字里总共有11种修饰符,但实际上还有一种 访问修饰符(Access Modifier) ,那就是“没有修饰”的修饰符,也就是不加任何修饰符在作用对象上。这种修饰符没有固定名称,以下都是出现过的的名字:“默认(default)”、“无修饰(No Modifier)”、“包私有(Package-Private)”、“包可见(Package)”。本文将以 (package)
来表示该隐形的修饰符,然后针对一共12种修饰符来作阐述。
对于所有Java的概念,可以应用修饰符的对象有三种:类(Class)、方法(Method)、变量(Variable)。进一步考虑,Java可以在类的定义里定义另一个类,所以对于类定义的位置又分出: 顶层类(Top-level Class) ,即直接定义在文件包下的类;和 嵌套类(Nested Class) 。对于变量,根据其是定义在类中还是方法中,可分别定义为:类字段(Class Field)和局部变量(Local Variable)。
再进一步分类的话,嵌套类还可以分成静态嵌套类(Static Nested Class)和内部类(Inner Class),不过这只是static修饰符起的效果,所以不进一步区分。同样的对于方法也不区分静态方法和对象方法,对字段也不分静态字段(Static Field)和实例变量(Instance Variable)。对于局部变量,其实还可以细分出方法参数(Method Parameter),但它的效果基本跟方法内直接定义的变量效果一致,所以不做区分。这里也不对接口(interface)进行讨论,因为它基本相当于是完全抽象类(abstract class)。
这样就得到了5种基本的修饰符作用对象,但不是所有的修饰符都可以作用在每一种对象上,所以把12种修饰符在Java中实际可作用的对象总结成下表:
Modifier | Class | Method | Variable | ||
---|---|---|---|---|---|
Top-Level Class | Nested Class | Class Field | Local Variable | ||
private | NO | YES | YES | YES | NO |
protected | NO | YES | YES | YES | NO |
public | YES | YES | YES | YES | NO |
(package) | YES | YES | YES | YES | – |
abstract | YES | YES | YES | NO | NO |
final | YES | YES | YES | YES | YES |
native | NO | NO | YES | NO | NO |
static | NO | YES | YES | YES | NO |
strictfp | YES | YES | YES | NO | NO |
synchronized | NO | NO | YES | NO | NO |
transient | NO | NO | NO | YES | NO |
volatile | NO | NO | NO | YES | NO |
在 Java Syntax 表中可以找到12种不同的修饰符,不包括 (package)
,但包含了 注解(Annotation) 。由于注解可以进行自定义,也不同于本文主要讨论的 修饰符针对一个作用对象只能出现一次(或没有) ,注解可以有多个同时作用在同一个对象上,所以不对注解做详细介绍。
另一方面,Java也有一个系统类叫 Modifier
,它内部定义了12种静态常量,其中11中对应着11种关键字指定的修饰符,另外一种也不是 (package)
,而是 interface 。
Source code |
这些静态常量其实只是比特位标记,Java库的 Class
、 Method
、 Field
都有 getModifiers()
方法返回指定对象所拥有的修饰符。 Modifier
类里还有许多静态方法来辅助检测指定的修饰符是否存在。虽然没有任何判定 (package)
修饰符的方法,但其实检测对象没有 public
、 protected
、 private
任一种修饰符,那就说明它是 (package)
修饰符了。
自Java 7开始, Modifier
还提供方法返回可应用于各对象的修饰符的汇总,对于这些源码提供的信息也侧面反应出了表的内容:
Source code |
对于 interface
是修饰符,想必主要是为了区别类和接口。其实 Modifier
还有很多非公开的常量定义,这些都不在本文讨论范围。本文的也不将 interface
认定为修饰符,所以研究对象还是基于表中罗列的12种修饰符。
对于访问修饰符,Java开发者都不会陌生,它们的作用主要是限制类自身和其成员的可访问域。下表就是对于访问限制的总结:
Modifier | Class | Package | Subclass | World |
---|---|---|---|---|
public | YES | YES | YES | YES |
protected | YES | YES | YES | NO |
(package) | YES | YES | NO | NO |
private | YES | NO | NO | NO |
(想起大学刚学Java的时候,我还是 将这些修饰符跟现实生活来对照 进行记忆,而现在对它们的理解则已经成了条件反射式。)
回看之前的表格可以看到对于顶层类(Top-level Class),只有 public
和 (package)
,所以这里也提一条对于类定义的限制:
一个文件只能有一个(可以没有)公开顶层类,且该类必须和文件同名。
几乎所有修饰符都有不能应用的对象,唯独 final 是可以作用在所有对象上都可以修饰,但是它们的意义不完全相同。
final
修饰的类不能定义子类。 final
修饰的方法不能被子类覆盖。 final
修饰的变量只能被初始化一次,之后变量不能再被赋值。 对一个指定的作用对象而已,可以应用多个不同的修饰符,但不是所有修饰符都可以同时修饰一个对象的,很多修饰符之间都有互斥性。比如4种访问修饰符之间就是互斥的,只能限定一个且只有一个访问修饰符。但访问修饰符和基础修饰符之间没有任何互斥关系。
abstract
几乎和其他所有基础修饰符都有互斥关系,毕竟 abstract
表示所修饰对象表示其内容将由子类决定,自身没有具体实现。唯一的例外就是在修饰类的时候, abstract
可以和 strictfp
组合使用。这也可以理解,因为抽象类可以又部分方法是被实现的。
volatile
修饰符只能作用在类字段上,它基本用在多线程编程方面,用以表明线程要访问字段的值时必须获取其最新的内容。它和 final
不能同时修饰一个字段,因为 final
表示字段在初始化话只能读不能写,也就不用担心它有新的值。
修饰符的互斥约束基本就这些,其实也不用去记忆它们,现代的Java编译器都能在编译时就告诉开发者哪些是不合法的修饰符组合,IDE也会对这些违规写法抛出错误来提醒开发者。
当某个对象的修饰符满足了上述的所有条件,那这些修饰符就可以合法存在并对这个对象起作用。不过之于多个修饰在申明顺序,Java编译器并没用做强制的规定。
比如想要定义一个公开的不加入到序列化的静态常量,下边在制定类中的定义方式都是有效的(即使说 static
和 transient
放在一起一般没什么意义):
Source code |
但为了保持编写风格的一致性,以及代码的可读性,对于修饰符的申明顺序还是有要求的。其实前面Modifier提供的修饰符汇总方法就可以反映出修饰符的申明顺序。另外,在 Java Language Specification的Classes一章 里也对 类修饰符 、 方法修饰符 、 字段修饰符 的罗列顺序也就是通常申明时要遵循的顺序。归总如下(这里将注解也包括进来)就是
Annotation
public
protected
private
static
abstract
final
native
synchronized
transient
volatile
strictfp
这个顺序也不用记忆,虽然编译器不会对它们进行约束,但有很多 Checkstyle 工具都会帮助开发者设定代码风格限定条件,并对不符合条件的写法抛出错误指示。