在 java 中 final 关键字的含义为最终的,不可变的意思,final 关键字可以用来修饰类、方法、变量(属性、局部变量、形参)。下文进行详细说明,如有不正之处,欢迎批评指正。
修饰类
用final修饰的类不能被继承,即不能拥有自己的子类。比如 java.lang.String、sun.misc.Unsafe等都是 final 修饰的类。
public final class Animal {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
如果试图继承 final 修饰的类,则在编译期间会发生错误。
public class User extends Animal {
private String sex;
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
错误提示:Cannot inherit from final "org.learn.finalkey.Animal"
。
修饰方法
final 修饰的方法不能被重写(override),可以被重载(overload)。final 修饰的方法的访问权限应该设置为 private。
final修饰的方法为什么不能被重写?
如果父类中的方法被 final 修饰,同时该方法的访问权限应该设置为 private,这样子类无法继承该方法,就不存在重写的可能。在子类中可以定义与父类同名、同参数的方法,该方法只是子类定义的新的方法,与父类无关,与重写无关。
示例一:父类中的方法(getName)没有被 final 修饰
public class Animal {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
子类正常继承父类的方法,并重写该方法。
public class User extends Animal {
public String getName() {
System.out.println("before getting");
return super.getName();
}
public void setName(String name) {
System.out.println("before setting");
super.setName(name);
System.out.println("after setting");
}
}
示例二:父类中的方法(getName ) 被 public final 修饰
public class Animal {
private String name;
public final String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
子类正常继承父类的方法。
public class User extends Animal {
public String getName() {
System.out.println("before getting");
return super.getName();
}
public void setName(String name) {
System.out.println("before setting");
super.setName(name);
System.out.println("after setting");
}
}
此时编译器会报错,错误提示:'getName()' cannot be override 'getName()' in 'org.learn.finalkey.Animal': overridden method is final
。
示例三:父类中的方法(getName ) 被 private final 修饰
public class Animal {
private String name;
private final String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
子类正常继承父类的方法。
public class User extends Animal {
public String getName() {
System.out.println("before getting");
return "name";
}
public void setName(String name) {
System.out.println("before setting");
super.setName(name);
System.out.println("after setting");
}
}
子类中的 getName 方法只是子类定义的新的方法,与父类无关,与重写无关。因此可以推断,final 方法只在类内部使用。
修饰变量
final 修饰的变量值在被首次初始化后是不可被改变的。Java语法规定:final修饰的属性、变量必须要进行显示初始化。
初始化的方式
- 定义的时候初始化;
- 构造函数中初始化;
- 静态块中初始化;
修饰的数据类型
- 基本数据类型:final 修饰基本数据类型,该变量的值是无法改变的;
- 引用数据类型:final 修饰引用数据类型,引用变量指向的地址是无法改变,该地址指向的对象是可以被修改的。
在内部类的方法中,如果想要访问外部类的变量,则该变量需要被 final 修饰。(点击查看原因)
宏变量替换
宏变量是指在编译阶段某些变量能够直接被自身的值进行替换的变量。在 Java 语言中没有定义宏变量的关键字,在某些情况下 final 修饰的变量,在编译阶段会被进行类似宏变量替换的操作。
示例一: 没有使用 final 变量修饰 name 、sex。
public class Macro {
public static void main(String[] args) {
String name = "zhibo";
String sex = "male";
String userInfo1 = name + sex;
String userInfo2 = "zhibo" + "male";
System.out.println("args1 = [" + userInfo1 + "]");
System.out.println("args2 = [" + userInfo2 + "]");
}
}
编译器编译后的代码,通过反编译工具再进行还原:
public class Macro {
public Macro() {
}
public static void main(String[] args) {
String name = "zhibo";
String sex = "male";
String userInfo1 = name + sex;
String userInfo2 = "zhibomale";
System.out.println("args1 = [" + userInfo1 + "]");
System.out.println("args2 = [" + userInfo2 + "]");
}
}
userInfo1 没有进行宏变量替换,这是由于 name、sex都是普通的变量,在编译阶段无法确定 name、sex 的值,因而无法进行宏替换。
而 userInfo2 进行了宏变量替换,由于"zhibo"、"male"是两个字符串的常量,因此在编译阶段进行了宏替换。
示例二:使用 final 变量修饰 name 、sex。
public class Macro {
public static void main(String[] args) {
final String name = "zhibo";
final String sex = "male";
String userInfo1 = name + sex;
String userInfo2 = "zhibo" + "male";
System.out.println("args1 = [" + userInfo1 + "]");
System.out.println("args2 = [" + userInfo2 + "]");
}
}
编译器编译后的代码,通过反编译工具再进行还原:
public class Macro {
public Macro() {
}
public static void main(String[] args) {
String name = "zhibo";
String sex = "male";
String userInfo1 = "zhibomale";
String userInfo2 = "zhibomale";
System.out.println("args1 = [" + userInfo1 + "]");
System.out.println("args2 = [" + userInfo2 + "]");
}
}
userInfo1 进行了宏变量替换,这是由于使用 final 关键字修饰 name、sex变量,在编译阶段可以确定 name、sex 的具体值,因而进行宏替换。
总结:final关键字修饰的变量,在编译阶段能直接确定其值,且此值不可变。在编译过程中,可以直接将其变量替换成其本身的值。