final关键字
final修饰方法
- final修饰表示“最终的,最后的”
- final修饰方法,能够被重载(多个方法,有相同名字),但是在继承该类后,不能被覆盖重写,只有我一个。
- 能够重写继承类的方法,首先需要能够继承此方法。
如果父类中使用final修饰方法,该方法不能被子类继承,因此,也不能够重写。此时可以在子类中定义相同的方法名和参数,但不是重写,两者不一样。
final修饰类
final修饰类,不能被继承
final修饰变量
final修饰的变量,在首次初始化后,将不能再被改变。
引用变量指向的是实际的对象,但其存储的是所指向对象的地址,因此,其值不能修改并不意味着其所指向的对象不能修改 例如:
public static void main(String[] args) {
final char[] values = new char[5];
values[0] = '1';
values[1] = '2';
values[2] = '3';
values[3] = '4';
values[4] = '5';
System.out.println(new String(values)); // 输出: 12345
values[0] = '5';
System.out.println(new String(values)); // 输出: 52345 values指向值被改变
char[] newValue = new char[5];
// values = newValue; // 报错使用final修饰,不能更改
}
- 说明
Values存储的是所指向对象的地址,不能被改变,不能再被赋予新的地址,
但是对象中所处存储的值,是可以改变的。其值不能修改,但是其所指向的对象中的成员是可以修改的。
疑问:final修饰的变量,存储在内存的什么区。
String类与StringBuilder类中的final
在String类与StringBuilder类中,String类的不可变,与StringBuilder的可变性,与fianl关键字有很大关系。
String类与StringBuilder类都维护了一个数组,但是String类中该数组采用
private final char value[];
StringBuilder中采用
char[] value;
使用final修饰的不可变,但也不仅仅是这个原因,因为final修饰后只是value指向的地址不能改变,并不代表它所指向的对象中的属性不能改变,也就是说不代表value[]数组中的值不能改变。关键问题在于,String类中没有提供改变数组中值得方法,所以造就了String的不可变。
从类加载过程的准备阶段分析final修饰变量的不同
- 类加载过程,主要分为加载、验证、准备、解析,初始化,五个阶段。
- 在准备阶段会将,final修饰的变量,进行赋初始值,这里不同于一般变量,使用final修饰的变量,在该阶段直接被赋予用户指定的值,而一般变量初始值为零值。
以下为类加载过程准备阶段的说明:
- 准备,是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。
- 这时候进行内存分配的仅包括类变量、被static修饰的变量,不包括实例变量。实例变量在对象实例化时随着对象一起分配在Java堆中。
- 这里不同时机分配不同的值,就造成了被static修饰的变量与实例变量,在访问时的区别,两者的加载时机不同。
- 这里的设置类变量的初始值,“通常情况”下是数据类型的零值:
public static int value = 123;
在准备阶段过后的初始值为0,不是123。把Value赋值为123的putstatic指令是程序被编译后,存放于类构造器<clinit>()方法中,所以把value赋值为123将会在初始化阶段才会执行。
- “特殊情况”,如果类字段的属性表中存在ConstantValue属性,那在准备阶段变量Value就会被初始化ConstantValue属性所指定的值
public static final int value = 123;
- 编译时Javac会为value生成ContstantValue属性,在准备阶段虚拟机根据ContstantValue的设置将value复制为123。
这也是从类加载过程,分析final修饰变量的特殊之处。
参考
《深入理解Java虚拟机》