final
关键字可用于修饰类、变量和方法,final
关键字有点类似C#里的sealed关键字,用于表示它修饰的类、方法和变量不可改变。final
修饰变量时,表示该变量一旦获得了初始值就不可被改变,final
既可以修饰成员变量(包括类变量和实例变量),也可以修饰局部变量、形参。final
修饰的变量不可被改变,一旦获得了初始值,该final
变量的值就不能被重新赋值。 由于final
变量获得初始值之后不能被重新赋值,因此final
修饰成员变量和修饰局部变量时有一定的不同。
一、final成员变量
成员变量是随类初始化或对象初始化而初始化的。当类初始化时,系统会为该类的类变量分配内存,并分配默认值;当创建对象时,系统会为该对象的实例变量分配内存,并分配默认值。也就是说,当执行静态初始化块时可以对类变量赋初始值;当执行普通初始化块、构造器时可对实例变量赋初始值。因此,成员变量的初始值可以在定义该变量时指定默认值,也可以在初始化块、构造器中指定初始值。
对于final
修饰的成员变量而言,一旦有了初始值,就不能被重新赋值,如果既没有在定义成员变量时指定初始值,也没有在初始化块、构造器中为成员变量指定初始值,那么这些成员变量的值将一直是系统默认分配的0
、'\\u0000'
、false
或null
,这些成员变量也就完全失去了存在的意义。 因此Java语法规定:final
修饰的成员变量必须由程序员显式地指定初始值。
归纳起来,final
修饰的类变量、实例变量能指定初始值的地方如下: ➢ 类变量:必须在静态初始化块中指定初始值或声明该类变量时指定初始值,而且只能在两个地方的其中之一指定。 ➢ 实例变量:必须在非静态初始化块、声明该实例变量或构造器中指定初始值,而且只能在三个地方的其中之一指定。
final
修饰的实例变量,要么在定义该实例变量时指定初始值,要么在普通初始化块或构造器中为该实例变量指定初始值。 但需要注意的是,如果普通初始化块已经为某个实例变量指定了初始值,则不能再在构造器中为该实例变量指定初始值;final
修饰的类变量,要么在定义该类变量时指定初始值,要么在静态初始化块中为该类变量指定初始值。
实例变量不能在静态初始化块中指定初始值,因为静态初始化块是静态成员,不可访问实例变量——非静态成员;类变量不能在普通初始化块中指定初始值,因为类变量在类初始化阶段已经被初始化了,普通初始化块不能对其重新赋值。
final
成员变量在显式初始化之前不能直接访问,但可以通过方法来访问,这基本上可断定是Java设计的一个缺陷。按照正常逻辑,final
成员变量在显式初始化之前是不应该允许被访问的。因此,建议尽量避免在final
成员变量显式初始化之前访问它。
二、final局部变量
系统不会对局部变量进行初始化,局部变量必须由程序员显式初始化。因此使用final
修饰局部变量时,既可以在定义时指定默认值,也可以不指定默认值。
如果final
修饰的局部变量在定义时没有指定默认值,则可以在后面代码中对该final
变量赋初始值,但只能一次,不能重复赋值;如果final
修饰的局部变量在定义时已经指定默认值,则后面代码中不能再对该变量赋值。
三、final修饰基本类型变量和引用类型变量的区别
当使用final修饰基本类型变量时,不能对基本类型变量重新赋值,因此基本类型变量不能被改变。但对于引用类型变量而言,它保存的仅仅是一个引用,final
只保证这个引用类型变量所引用的地址不会改变,即一直引用同一个对象,但这个对象完全可以发生改变。
四、可执行“宏替换”的final变量
对一个final
变量来说,不管它是类变量、实例变量,还是局部变量,只要该变量满足三个条件,这个final
变量就不再是一个变量,而是相当于一个直接量。 ➢ 使用final
修饰符修饰。 ➢ 在定义该final
变量时指定了初始值。 ➢ 该初始值可以在编译时就被确定下来。
Java会使用常量池来管理曾经用过的字符串直接量,例如执行var a="java";语句之后,常量池中就会缓存一个字符串"java";如果程序再次执行varb="java";,系统将会让b直接指向常量池中的"java"字符串,因此a==b将会返回true。
对于实例变量而言,既可以在定义该变量时赋初始值,也可以在非静态初始化块、构造器中对它赋初始值,在这三个地方指定初始值的效果基本一样。但对于final
实例变量而言,只有在定义该变量时指定初始值才会有“宏变量”的效果。
五、final方法
final
修饰的方法不可被重写,如果出于某些原因,不希望子类重写父类的某个方法,则可以使用final
修饰该方法。 Java提供的Object类里就有一个final
方法:getClass()
,因为Java不希望任何类重写这个方法,所以使用final
把这个方法密封起来。但对于该类提供的toString()
和equals()
方法,都允许子类重写,因此没有使用final
修饰它们。
对于一个private方法,因为它仅在当前类中可见,其子类无法访问该方法,所以子类无法重写该方法——如果子类中定义一个与父类private方法有相同方法名、相同形参列表、相同返回值类型的方法,也不是方法重写,只是重新定义了一个新方法。因此,即使使用final修饰一个private访问权限的方法,依然可以在其子类中定义与该方法具有相同方法名、相同形参列表、相同返回值类型的方法。
final修饰的方法仅仅是不能被重写,并不是不能被重载
六、final类
final修饰的类不可以有子类,例如java.lang.Math
类就是一个final类,它不可以有子类。
当子类继承父类时,将可以访问到父类内部数据,并可通过重写父类方法来改变父类方法的实现细节,这可能导致一些不安全的因素。为了保证某个类不可被继承,则可以使用final修饰这个类。