1. final 成员变量
final 关键字修饰类、变量和方法不可改变。(一旦获得了初始值)
一旦获得了初始值就不能重新被赋值。
final 修饰的成员变量必须由程序员显示地指定初始值。
final 修饰的类变量、实例变量能指定初始值的地方如下:
- 类变量: 必须在静态初始化块中指定初始值或声明该类变量时指定初始值,而且只能在两个地方的其中之一指定。
 - 实例变量: 必须在非静态初始化块、声明该实例变量或构造器中指定初始值,而且只能在三个地方的其中之一指定。
 
final 成员变量在显示初始化之前不能直接访问,但可以通过方法来访问。—》设计缺陷
避免在final成员变量显示初始化之前访问。
public class FinalVariableTest{// 定义成员变量时指定 默认值,合法final int a = 6;// 下面变量将在构造器或初始化块中分配初始值final String str;final int c;final static double d;// 既没有指定默认值,又没有在初始化块、构造器中指定初始值// 下面定义的ch实例变量是不合法的// final char ch;// 初始化块,可对没有指定默认值的实例变量指定初始值{//在初始化块中为实例变量指定初始值,合法str = "Hello";// 定义a实例变量时已经指定了默认值// 不能为a重新赋值,因此 a = 9 ; 非法}// 静态初始化块,可对没有指定默认值的类变量指定初始值static{// 在静态初始化块中为类变量指定初始值,合法d = 5.6;}// 构造器,可对既没有指定默认值,又没有在初始化块中// 指定初始值的实例变量指定初始值
2. final 局部变量
系统不会对局部变量进行初始化,局部变量必须由程序员显示初始化。
因此使用final修饰局部变量时,即可以在定义时指定默认值,也可以不指定默认值。
public class FinalLocalVariableTest{public void test(final int a){// 不能对final 修饰的形参赋值// a = 5 ; 非法// final修饰形参,形参在调用该方法时,由系统根据传入的参数来完成初始化// 因此使用final修饰的形参不能被赋值}public static void main(String[] args){// 定义final局部变量时指定默认值,则str变量无法重新赋值final var str = "hello";final double d;d = 5.6;// 不可再次赋值}
3. final 修饰基本类型变量和引用类型变量的区别
基本类型变量,不能重新赋值
引用变量仅仅保存一个引用
final 只保证这个引用类型变量所引用的地址不会改变
即一直引用同一个对象,但这个对象完全可以发生改变
class Person{private int age;public Person(){}// 有参数的构造器public Person(int age){this.age = age;}}public class FinalReferenceTest{public static void mian(String[] args){// final 修饰数组变量,iArr 是一个引用变量final int[] iArr = (5,6,12,9);System.out.println ( Arrays.toString ( iArr ));// 对数组元素进行排序,合法Arrays.sort(iArr);System.out.println(Arrays.toString(iArr));// 对数组元素赋值,合法iArr[2] = -8;System.out.println(Arrays.toString(iArr));// 下面语句对iArr重新赋值,非法// iArr = null;// final 修饰Person变量,p是一个引用变量final var p = new Person(45);// 改变person对象的age实例变量,合法p.setAge(23);System.out.println(p.getAge());// 下面语句对p重新赋值,非法// p = null;}}
使用final 修饰的引用类型变量不能被重新赋值,但可以改变引用类型变量所引用对象的内容。
4. 可执行“宏替换”的final 变量
满足一下三个条件,相当于一个直接量。
- 使用final 修饰符修饰
 - 在定义该final 变量时指定了初始值
 - 该初始值可以在编译时就被确定下来
 
final 变量的实质就是“宏变量”
编译器会把程序中所有用到该变量的地方替换成该变量的值。
5. final 方法
- final 修饰的方法不可以被重写( 不希望子类重写父类的某个方法,可以使用)但可以被重载。
 - 对于一个private 方法,因为它仅在当前类中可见,其子类无法访问该方法,所以子类无法重写该方法。
 
即使使用final 修饰一个private 访问权限的方法,依然可以在其子类中定义与该方法具有相同方法名、相同形参列表、相同返回值类型的方法。
6. final 类
final 修饰的类不可以有子类
7.不可变类
不可变类的意思是创建该类的实例后,该实例的实例变量是不可改变的。
8. 缓存实例的不可变类
class CacheImmutale{private static int MAX_SIZE = 10;// 使用数组来缓存已有的实例private static CacheImmulate[] cache = new CacheImmutale[MAX_SIZE];// 记录缓存实例在缓存中的位置,cache[pos-1] 是最新缓存的实例private static int pos = 0;private final String name;private CacheImmutale(String name){this.name = name;}public String getName(){return name;}public static CacheImmutale valueOf(String name){// 遍历已经缓存的对象for (var i = 0; i < MAX_SIZE ; i++){// 如果已有相同实例,则直接返回该缓存的实例if (cache[i] != null && cache[i].getName().equals(name)){return cache[i];}}// 如果缓存池已满if ( pos == MAX_SIZE){// 把缓存的第一个对象覆盖,即把刚刚生成的对象放在缓存池的最开始位置cache[0] = new CacheImmutale(name);// 把pos设为1pos = 1;}else{// 把新创建的对象缓存起来,pos 加1cache[pos++] = new CacheImmutale(name);}return cache[pos-1];}public boolean equals(Object obj){if (this == obj){return true;}if (obj != null && obj.getClass() == CacheImmutale.class){var ci = (CacheImmutale) obj;return name.equals(ci.getName());}return false;}public int hashCode(){return name.hashCode();}}public class CacheImmutaleTest{public static void main(String[] args){var c1 = CacheImmutale.valueOf("hello");var c2 = CacheImmutale.valueOf("helo");// 下面代码将输出trueSystem.out.ptintln(c1 == c2);}}
