在《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 |
注解和接口(Annotation & Interface)
在Java Syntax表中可以找到12种不同的修饰符,不包括(package)
,但包含了注解(Annotation)。由于注解可以进行自定义,也不同于本文主要讨论的修饰符针对一个作用对象只能出现一次(或没有),注解可以有多个同时作用在同一个对象上,所以不对注解做详细介绍。
另一方面,Java也有一个系统类叫Modifier
,它内部定义了12种静态常量,其中11中对应着11种关键字指定的修饰符,另外一种也不是(package)
,而是interface。
[codesyntax lang=”java” lines=”normal”]
/** * The {@code int} value representing the {@code public} modifier. */ public static final int PUBLIC = 0x1; /** * The {@code int} value representing the {@code private} modifier. */ public static final int PRIVATE = 0x2; /** * The {@code int} value representing the {@code protected} modifier. */ public static final int PROTECTED = 0x4; /** * The {@code int} value representing the {@code static} modifier. */ public static final int STATIC = 0x8; /** * The {@code int} value representing the {@code final} modifier. */ public static final int FINAL = 0x10; /** * The {@code int} value representing the {@code synchronized} modifier. */ public static final int SYNCHRONIZED = 0x20; /** * The {@code int} value representing the {@code volatile} modifier. */ public static final int VOLATILE = 0x40; /** * The {@code int} value representing the {@code transient} modifier. */ public static final int TRANSIENT = 0x80; /** * The {@code int} value representing the {@code native} modifier. */ public static final int NATIVE = 0x100; /** * The {@code int} value representing the {@code interface} modifier. */ public static final int INTERFACE = 0x200; /** * The {@code int} value representing the {@code abstract} modifier. */ public static final int ABSTRACT = 0x400; /** * The {@code int} value representing the {@code strictfp} modifier. */ public static final int STRICT = 0x800;
[/codesyntax]
这些静态常量其实只是比特位标记,Java库的Class
、Method
、Field
都有getModifiers()
方法返回指定对象所拥有的修饰符。Modifier
类里还有许多静态方法来辅助检测指定的修饰符是否存在。虽然没有任何判定(package)
修饰符的方法,但其实检测对象没有public
、protected
、private
任一种修饰符,那就说明它是(package)
修饰符了。
自Java 7开始,Modifier
还提供方法返回可应用于各对象的修饰符的汇总,对于这些源码提供的信息也侧面反应出了表的内容:
[codesyntax lang=”java” lines=”normal”]
/** * Returns a mask of all the modifiers that may be applied to classes. * @since 1.7 */ public static int classModifiers() { return PUBLIC | PROTECTED | PRIVATE | ABSTRACT | STATIC | FINAL | STRICT; } /** * Returns a mask of all the modifiers that may be applied to constructors. * @since 1.7 */ public static int constructorModifiers() { return PUBLIC | PROTECTED | PRIVATE; } /** * Returns a mask of all the modifiers that may be applied to fields. * @since 1.7 */ public static int fieldModifiers() { return PUBLIC | PROTECTED | PRIVATE | STATIC | FINAL | TRANSIENT | VOLATILE; } /** * Returns a mask of all the modifiers that may be applied to interfaces. * @since 1.7 */ public static int interfaceModifiers() { return PUBLIC | PROTECTED | PRIVATE | ABSTRACT | STATIC | STRICT; } /** * Returns a mask of all the modifiers that may be applied to methods. * @since 1.7 */ public static int methodModifiers() { return PUBLIC | PROTECTED | PRIVATE | ABSTRACT | STATIC | FINAL | SYNCHRONIZED | NATIVE | STRICT; }
[/codesyntax]
对于interface
是修饰符,想必主要是为了区别类和接口。其实Modifier
还有很多非公开的常量定义,这些都不在本文讨论范围。本文的也不将interface
认定为修饰符,所以研究对象还是基于表中罗列的12种修饰符。
访问修饰符(Access Modifier)
对于访问修饰符,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(Omni-final)
几乎所有修饰符都有不能应用的对象,唯独final是可以作用在所有对象上都可以修饰,但是它们的意义不完全相同。
- 类(class): 用
final
修饰的类不能定义子类。 - 方法(Method): 用
final
修饰的方法不能被子类覆盖。 - 变量(Variable): 用
final
修饰的变量只能被初始化一次,之后变量不能再被赋值。
互斥修饰符(Mutually Exclusive Modifiers)
对一个指定的作用对象而言,可以拥有多个不同的修饰符,但不是所有修饰符都可以同时修饰一个对象的,很多修饰符之间都有互斥性。比如4种访问修饰符之间就是互斥的,只能限定一个且只有一个访问修饰符。但访问修饰符和基础修饰符之间没有任何互斥关系。
abstract
几乎和其他所有基础修饰符都有互斥关系,毕竟abstract
表示所修饰对象表示其内容将由子类决定,自身没有具体实现。唯一的例外就是在修饰类的时候,abstract
可以和strictfp
组合使用。这也可以理解,因为抽象类可以有部分方法是被实现的。
volatile
修饰符只能作用在类字段上,它基本用在多线程编程方面,用以表明线程要访问字段的值时必须获取其最新的内容。它和final
不能同时修饰一个字段,因为final
表示字段在初始化话只能读不能写,也就不用担心它有新的值。
修饰符的互斥约束基本就这些,其实也不用去记忆它们,现代的Java编译器都能在编译时就告诉开发者哪些是不合法的修饰符组合,IDE也会对这些违规写法抛出错误来提醒开发者。
修饰符的申明顺序(Declaration Order of Modifiers)
当某个对象的修饰符满足了上述的所有条件,那这些修饰符就可以合法存在并对这个对象起作用。不过之于多个修饰在申明顺序,Java编译器并没用做强制的规定。
比如想要定义一个公开的不加入到序列化的静态常量,下边在制定类中的定义方式都是有效的(即使说static
和transient
放在一起一般没什么意义):
[codesyntax lang=”java” lines=”normal”]
public static final transient int zero = 0; static transient public final int one = 1; final public transient static int two = 2; transient final static public int three = 3;
[/codesyntax]
但为了保持编写风格的一致性,以及代码的可读性,对于修饰符的申明顺序还是有要求的。其实前面Modifier提供的修饰符汇总方法就可以反映出修饰符的申明顺序。另外,在Java Language Specification的Classes一章里也对类修饰符、方法修饰符、字段修饰符的罗列顺序也就是通常申明时要遵循的顺序。归总如下(这里将注解也包括进来)就是
Annotation
public
protected
private
static
abstract
final
native
synchronized
transient
volatile
strictfp
这个顺序也不用记忆,虽然编译器不会对它们进行约束,但有很多Checkstyle工具都会帮助开发者设定代码风格限定条件,并对不符合条件的写法抛出错误指示。
3 Comments