转载

Java修饰符及其作用对象

在《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 。

Source code Java修饰符及其作用对象   Java修饰符及其作用对象   Java修饰符及其作用对象  
  1. /**
  2.  * The {@code int} value representing the {@code public} modifier.
  3.  */
  4. public static final int PUBLIC = 0x1 ;
  5.  
  6. /**
  7.  * The {@code int} value representing the {@code private} modifier.
  8.  */
  9. public static final int PRIVATE = 0x2 ;
  10.  
  11. /**
  12.  * The {@code int} value representing the {@code protected} modifier.
  13.  */
  14. public static final int PROTECTED = 0x4 ;
  15.  
  16. /**
  17.  * The {@code int} value representing the {@code static} modifier.
  18.  */
  19. public static final int STATIC = 0x8 ;
  20.  
  21. /**
  22.  * The {@code int} value representing the {@code final} modifier.
  23.  */
  24. public static final int FINAL = 0x10 ;
  25.  
  26. /**
  27.  * The {@code int} value representing the {@code synchronized} modifier.
  28.  */
  29. public static final int SYNCHRONIZED = 0x20 ;
  30.  
  31. /**
  32.  * The {@code int} value representing the {@code volatile} modifier.
  33.  */
  34. public static final int VOLATILE = 0x40 ;
  35.  
  36. /**
  37.  * The {@code int} value representing the {@code transient} modifier.
  38.  */
  39. public static final int TRANSIENT = 0x80 ;
  40.  
  41. /**
  42.  * The {@code int} value representing the {@code native} modifier.
  43.  */
  44. public static final int NATIVE = 0x100 ;
  45.  
  46. /**
  47.  * The {@code int} value representing the {@code interface} modifier.
  48.  */
  49. public static final int INTERFACE = 0x200 ;
  50.  
  51. /**
  52.  * The {@code int} value representing the {@code abstract} modifier.
  53.  */
  54. public static final int ABSTRACT = 0x400 ;
  55.  
  56. /**
  57.  * The {@code int} value representing the {@code strictfp} modifier.
  58.  */
  59. public static final int STRICT = 0x800 ;

这些静态常量其实只是比特位标记,Java库的 ClassMethodField 都有 getModifiers() 方法返回指定对象所拥有的修饰符。 Modifier 类里还有许多静态方法来辅助检测指定的修饰符是否存在。虽然没有任何判定 (package) 修饰符的方法,但其实检测对象没有 publicprotectedprivate 任一种修饰符,那就说明它是 (package) 修饰符了。

自Java 7开始, Modifier 还提供方法返回可应用于各对象的修饰符的汇总,对于这些源码提供的信息也侧面反应出了表的内容:

Source code Java修饰符及其作用对象   Java修饰符及其作用对象   Java修饰符及其作用对象  
  1. /**
  2.  * Returns a mask of all the modifiers that may be applied to classes.
  3.  * @since 1.7
  4.  */
  5. public static int classModifiers ( ) {
  6. return PUBLIC | PROTECTED | PRIVATE | ABSTRACT | STATIC | FINAL | STRICT ;
  7. }
  8.  
  9. /**
  10.  * Returns a mask of all the modifiers that may be applied to constructors.
  11.  * @since 1.7
  12.  */
  13. public static int constructorModifiers ( ) {
  14. return PUBLIC | PROTECTED | PRIVATE ;
  15. }
  16.  
  17. /**
  18.  * Returns a mask of all the modifiers that may be applied to fields.
  19.  * @since 1.7
  20.  */
  21. public static int fieldModifiers ( ) {
  22. return PUBLIC | PROTECTED | PRIVATE | STATIC | FINAL | TRANSIENT | VOLATILE ;
  23. }
  24.  
  25. /**
  26.  * Returns a mask of all the modifiers that may be applied to interfaces.
  27.  * @since 1.7
  28.  */
  29. public static int interfaceModifiers ( ) {
  30. return PUBLIC | PROTECTED | PRIVATE | ABSTRACT | STATIC | STRICT ;
  31. }
  32.  
  33. /**
  34.  * Returns a mask of all the modifiers that may be applied to methods.
  35.  * @since 1.7
  36.  */
  37. public static int methodModifiers ( ) {
  38. return PUBLIC | PROTECTED | PRIVATE | ABSTRACT | STATIC | FINAL | SYNCHRONIZED | NATIVE | STRICT ;
  39. }

对于 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编译器并没用做强制的规定。

比如想要定义一个公开的不加入到序列化的静态常量,下边在制定类中的定义方式都是有效的(即使说 statictransient 放在一起一般没什么意义):

Source code Java修饰符及其作用对象   Java修饰符及其作用对象   Java修饰符及其作用对象  
  1. public static final transient int zero = 0 ;
  2. static transient public final int one = 1 ;
  3. final public transient static int two = 2 ;
  4. transient final static public int three = 3 ;

但为了保持编写风格的一致性,以及代码的可读性,对于修饰符的申明顺序还是有要求的。其实前面Modifier提供的修饰符汇总方法就可以反映出修饰符的申明顺序。另外,在 Java Language Specification的Classes一章 里也对 类修饰符 、 方法修饰符 、 字段修饰符 的罗列顺序也就是通常申明时要遵循的顺序。归总如下(这里将注解也包括进来)就是

  • Annotation
  • public
  • protected
  • private
  • static
  • abstract
  • final
  • native
  • synchronized
  • transient
  • volatile
  • strictfp

这个顺序也不用记忆,虽然编译器不会对它们进行约束,但有很多 Checkstyle 工具都会帮助开发者设定代码风格限定条件,并对不符合条件的写法抛出错误指示。

正文到此结束
Loading...