final修饰变量时,表示该变量一旦获得了初始值就不可改变,
由于final变量获得初始值之后不能被重新赋值,因此final修饰成员变量和修饰局部变量时有一定的不同。
5.4.1final成员变量
成员变量是随类初始化或对象初始化而初始化的,
当类初始化时,系统会为该类的类变量分配内存,并分配默认值;
当创建对象时,系统会为该对象的实例变量分配内存,并分配默认值。
java语法规定:final修饰的成员变量必须由程序员显式地指定初始值
归纳:
final修饰的类变量,实例变量 能指定初始值的地方:
类变量:
- 在静态初始化块中指定初始值
final static int;
static{
i = 10;
}
- 声明该类变量时指定初始值
final static int i = 10;
实例变量:
- 非静态初始化块
final int i;
{
i = 10;
}
- 声明该实例变量
final int i = 10;
- 构造器中指定初始值
public class FinalVariableTest{
final int i;
public FinalVariableTest(){
i = 10;
//如果在初始化块已经赋值,就不能在构造器中重新赋值
}
}
实例变量不能在静态初始化块中指定初始值,因为静态初始化块是静态成员,不可访问实例变量——非静态成员;类变量不能在普通初始化块中指定初始值,因为类变量在类初始化阶段已经被初始化了,普通初始化块不能对其重新赋值。
如果打算在构造器、初始化块中对final成员变量进行初始化,则不要在初始化之前就访问成员变量的值。
package code;
public class FinalErrorTest{
final int age;
{
//System.out.println(age);
age = 6;
System.out.println(age);
}
public static void main(String [] args){
new FinalErrorTest();
}
}
I:>javac -d . FinalErrorTest.java
FinalErrorTest.java:5: 错误: 可能尚未初始化变量age
System.out.println(age);
^
1 个错误
5.4.2 final局部变量
package code;
public class FinalLocalVariable{
public void test(final int a){
//不能对final修饰的形参赋值,下面语句非法
//a = 5;
}
public static void main(String[] args){
//定义final局部变量时指定默认值,则str变量无法重新赋值
final String str = "hello";
//str = "java";
final double d;
d = 44.5;
//d = 443.3;
}
}
I:>javac -d . FinalLocalVariable.java
FinalLocalVariable.java:10: 错误: 无法为最终变量str分配值
str = “java”;
^
1 个错误
I:>javac -d . FinalLocalVariable.java
FinalLocalVariable.java:5: 错误: 不能分配最终参数a
a = 5;
^
FinalLocalVariable.java:13: 错误: 可能已分配变量d
d = 443.3;
^
2 个错误
因为形参在调用该方法,由系统根据传入的参数来完成初始化,因此使用final修饰的形参不能被赋值。
5.4.3 final修饰基本类型变量和引用类型变量的区别
package code;
import java.util.Arrays;
class Person{
private int age;
public Person(){}
public Person(int age){
this.age = age;
}
public void setAge(int age){
this.age = age;
}
public int getAge(){
return age;
}
}
public class FinalReferenceTest{
public static void main(String[]args){
final int[] iArr = {5,6,12,8};
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 = null;
final Person p = new Person(45);
p.setAge(21);
System.out.println(p.getAge());
//p = null;
}
}
[5, 6, 12, 8]
[5, 6, 8, 12]
[5, 6, -8, 12]
21
使用final修饰的引用类型变量不能被重新赋值,但可以改变引用类型变量所引用对象的内容。
5.4.4 可执行“宏替换”的final变量
对一个final变量,不管它是类变量,实例变量,还是局部变量,只要该变量满足三个条件,这个final变量就不再是一个变量,而是相当于一个直接量。
- 使用final修饰符修饰
- 在定义该final变量时指定了初始值
- 该初始值可以在编译时就被确定下来
package code;
public class FinalReplaceTest{
public static void main(String[]args){
final int a = 5+3;
final double b = 1.2/3;
final String str = "疯狂" + "Java";
final String book = "疯狂Java讲义:" + 99.0;
final String book2 = "疯狂Java讲义" + String.valueOf(99.0);
System.out.println(book == "疯狂Java讲义:99.0");
System.out.println(book2 == "疯狂Java讲义:99.0");
}
}
true
false
由于定义book2变量时显式将数值99.0转换为字符串,但由于该变量的值需要调用String类的方法,因此编译器无法在编译时确定book2的值,book2不会被当成“宏变量”处理。
package code;
public class StringJoinTest{
public static void main(String[] args){
String str1 = "疯狂Java";
String str2 = "疯狂" + "Java";
String str3 = str1 + str2;
System.out.println(str1 == str2);
System.out.println(str1 == str3);
}
}
true
false
由于编译器可以在编译阶段就确定str2的值为“疯狂Java”,所以系统会让s2直接指向常量池中缓存的“疯狂Java”字符串,因此str1 = str2.
对于final实例变量而言,只有在定义该变量时指定初始值才会有“宏变量”的效果
5.4.5 final方法
final修饰的方法不可被重写,如果出于某些原因,不希望子类重写父类的某个方法,则可以使用final方法。
对于一个private方法,因为它仅在当前类中可见,其子类无法访问该方法,所以子类无法重写该方法—–如果子类中定义一个与父类private方法有相同方法名,相同形参列表,相同返回值类型的方法,也不是方法重写,只是重新定义了一个新方法。
package code;
public class PrivateFinalMethodTest{
private final void test(){}
}
class Sub extends PrivateFinalMethodTest{
public void test(){}
}
class FinalOverload{
//final修饰的方法只是不能被重写,完全可以被重载
public final void test(){}
public final void test(String agr){}
}
5.4.6 final类
final修饰的类不可以有子类,例如java.lang.Math类就是一个final类,它不可以有子类