最最重要的一点:
对于final修饰的基本类型, 一旦被赋值后不能再次赋值;
对于fianl修饰的引用类型, 表明该引用指向的地址不变, 也就是保持指向同一对象, 对象本身的属性可以修改。
Then, final可修饰类、方法、变量。
一、修饰变量
1.类变量
初始化规则: 在定义时直接指定初始值
or 在静态初始化块中指定初始值
static final int b;
// 在静态块中初始化
static {
b = 4;
}
2.实例变量
初始化规则: 在定义时指定初始值
or 普通初始化块中赋值
or 构造方法中初始化
final int a;
// 普通方法块中初始化
{
a =5;
}
3.局部变量
- 修饰方法参数
不能再对该参数进行赋值, 在调用该方法时已经对参数完成了初始化.
public void testFinal(final String a){
a = "hello"; // 编译报错
}
- 修饰局部变量
可以定义时赋值, 也可以先定义后面再赋值.
final int a;
int b = 4;
a = 3; // No problem
4.fianl 变量的意义
final修饰符一个重要的用途就是定义常量, 也可以说直接量。
在满足以下条件时final修饰的变量可称为直接量:
- 使用final修饰符修饰
- 定义final变量指定了初始值
- 该初始值在编译期能确定
什么是直接量? for example:
public void test() {
final int a = 5;
System.out.println(a);
}
当执行代码System.out.println(a)
时, a变量相当于不存在, 代码转换为System.out.println(5)
.让我们摆事实, 反编译一波。为了更明显, 使用普通变量做对比.
// 代码一
int a = 5;
System.out.println(a);
// 代码二
final int a = 5;
System.out.println(a);
左边为代码一的反编译
istore_1: 将栈顶int类型值保存到局部变量1中。
iconst_5: 5(int)值入栈。
See? 右图的反编译并没有局部变量出现, 直接将5入栈, 然后调用方法。
String a = "abc";
final String b = "a";
String c = b + "bc";
System.out.println(a == c);// So, 现在你知道了为结果是true了吧。
二、修饰方法
1.final修饰的方法不可被重写
public class Test {
public final void test1(){ }
final void test2(){}
protected final void test3(){}
private final void test4(){}
}
class Test2 extends Test{
public void test1(){ } // 编译报错
void test2(){ } // 编译报错
void test3(){ } // 编译报错
void test4(){ } // OK
}
2.final修饰的方法可重载
class Test3{
// 编译OK
final void test5(){}
final void test5(String str){}
}
三、修饰类
fianl修饰的类不可被继承。
1.创建一个不可变类—— 类的实例不能修改
要遵循哪些规则?
(1) 成员变量使用private final
修饰
(2) 提供获取实例的方法。
(3) 为成员变量提供get
方法, 但不提供set
方法
(4) 如有必要, 重写equals
方法
注意: 若不可变类中有引用类型的成员变量, 需要设定一些措施保证该引用变量不被修改(引用传递!引用传递!)。
2.缓存实例的不可变类
不可变类实例不能修改, 可方便的被其它对象共享。如程序中经常要用到相同的不可变类实例, 则可以考虑缓存这个不可变类实例。
Integer
就是缓存实例不可变类的经典实践, 直接get源码:
public static Integer valueOf(int i) {
assert IntegerCache.high >= 127;
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)]; //关键
return new Integer(i);
}
See? 如果传入的值符合条件, 那么它直接返回缓存数组的实例。