12. final修饰符
一、final修饰符概述
1. final可以修饰类、变量和方法
2. final修饰的类、变量和方法不可改变
3. 不允许为final变量重新赋值,子类不允许覆盖父类的final方法,final类不能派生子类
4. 通过使用final关键字,允许Java实现不可变类,不可变类会让系统更加安全
二、final成员变量
1. 对于final修饰的成员变量而言,一旦有了初始值,就不能被重新赋值
2. 由于成员变量不一定需要显式初始化,故那些既没有在定义时指定初始值,也没有在初始化块、构造器中指定初始值的final成员变量的值将一直是系统默认分配的0、'\u0000'、false或null,这些final成员变量也就完全失去了存在的意义,因此Java规定:final修饰的成员变量必须由程序员显式地指定初始值
- final类变量:必须在静态初始化块中指定初始值或声明该类变量时指定初始值,而且只能在两个地方的其中之一指定
- final实例变量:必须在非静态初始化块、声明该实例变量或构造器中指定初始值,而且只能在三个地方的其中之一指定
// 定义final成员变量时指定默认值 final int a = 6; // 下面final成员变量将在构造器或初始化块中分配初始值 final String str; final int c; final static double d; // 下面定义的ch实例变量是不合法的,因为没有显式指定初始值 // final char ch; // 初始化块,可对没有指定默认值的实例变量指定初始值 { str = "Hello"; } // 静态初始化块,可对没有指定默认值的类变量指定初始值 static { d = 3.14; } // 构造器,可对既没有指定默认值,又没有在初始化块中指定初始值的实例变量指定初始值 public FinalVariableTest() { c = 5; }
三、final局部变量
1. 相比于成员变量,系统不会对局部变量进行初始化,局部变量必须由程序员显式初始化,因此使用final修饰局部变量时,既可以在定义时指定默认值,也可以不指定默认值
- 如果在定义时指定了默认值,则后面代码中不能再对该变量赋值
- 如果在定义时未指定默认值,则可以在后面代码中对该final变量赋初始值,但只能一次,不能重复赋值
四、final变量→宏变量
1. 当某个final变量满足以下两个条件时,它就不再是一个变量,而是相当于一个直接量(即宏变量)
- 在该final变量定义时指定了初始值
- 该初始值可以在编译时就被确定下来
2. 除了为final变量赋值时赋直接量的情况外,如果被赋的表达式只是基本的算术表达式或字符串连接运算,没有访问普通变量,也没有调用方法,Java编译器同样会将这种final变量当成“宏变量”处理
// ex:下面定义了4个final“宏变量” final int a = 5; final int b = 3 + 2; final double d = 1.2 / 3; final String str = "我爱" + "China"; // 下面的ss变量的值因为调用了方法,所以无法在编译时被确定下来,ss也就不会被当成“宏变量”处理 final String ss = "我爱China" + String.valueOf(1314);
五、final方法
1. final修饰的方法不可被重写,但完全可以被重载
2. 由于子类无法重写父类的private方法(如果子类中定义一个与父类private方法有相同方法名、相同形参列表、相同返回值类型的方法,也不是方法重写,只是重新定义了一个新方法),因此即使使用final修饰一个private方法,依然可以在其子类中定义与该方法具有相同方法名、相同形参列表、相同返回值类型的方法
public class PrivateFinalMethodTest { private final void test() {} } class sub extends PrivateFinalMethodTest { // 下面的方法定义不会出现问题 public void test() {} }
注:虽然子类和父类都包含了同名的void test()方法,但子类并不是重写父类的方法,因此即使父类的void test()方法使用了final修饰,子类中依然可以定义void test()方法。
六、final类
1. final修饰的类不可以有子类,所以若某个类不想被继承,则可以使用final修饰该类
public final class FinalClass { } // 下面的类定义将出现编译错误 class Sub extends FinalClass { }
七、不可变类
1. 不可变类是指创建该类的实例后,该实例的实例变量是不可改变的
2. Java提供的8个包装类和java.lang.String类都是不可变类
3. 如果要创建自定义的不可变类,可遵守如下规则:
- 使用private和final修饰符来修饰该类的成员变量
- 提供带参数构造器,用于根据传入参数来初始化类里的成员变量
- 仅为该类的成员变量提供getter方法,不要为该类的成员变量提供setter方法
- 如果有必要,重写Object类的hashCode()和equals()方法
/* * 定义一个不可变类Address */ public class Address { // 不可变类的实例的实例变量不可改变 private final String detail; private final String postCode; public Address() { detail = ""; postCode = ""; } public Address(String detail, String postCode) { this.detail = detail; this.postCode = postCode; } public String getDetail() { return this.detail; } public String getPostCode() { return this.postCode; } // 重写equals()方法,判断两个对象是否相等 public boolean equals(Object obj) { // ... } public int hashCode() { return detail.hashCode() + postCode.hashCode() * 31; } }
补:对于上面的Address类,当程序创建了Address实例后,将无法修改该Address实例的detail和postCode实例变量。
4. 不可变类的实例在整个生命周期中永远处于初始化状态,它的实例变量不可改变,因此对不可变类的实例的控制将更加简单
- 不可变类的实例状态不可改变,可以很方便地被多个对象所共享