/

Java final 修饰符

final 修饰符

final 修饰符表示不可变。类似 C 中的 constant。用于修饰变量表示不可变的变量。用于修饰方法表示不可被重写。用于修饰类表示不可被继承。

final 的成员变量

成员变量随着类或者实例的初始化而初始化。在类初始化时,静态变量就会被分配内存并初始化。对于实例变量,系统会在实例初始化的时候初始化这些变量。

由于成员变量会被系统隐式的初始化。如果程序员不显式的初始化它们,那他们会变成 0,false,null 这样的值。失去了意义。

所以 final 修饰的成员变量必须显式的初始化。

  • 类变量:必须在静态初始化块,或者在声明的时候初始化
  • 实例变量:必须在非静态初始化块,声明时,或者构造函数中初始化。

final 的局部变量

对于形参:在方法内部无法对其进行赋值。
方法体内的 final 局部变量:只能赋值一次。
final 的基本类型变量和引用类型变量

基本类型变量:值不可变。
引用类型变量:指针不可变,但是指向的内存区域可变。

public class FinalVariableTest {
public static void main(String[] agrs){
final int[] arr = {1,23,4,5};
System.out.println(Arrays.toString(arr));
arr[2] = 20;
System.out.print(Arrays.toString(arr));
}
}

//Console
//[1, 23, 4, 5]
//[1, 23, 20, 5]

final 的宏替换

对于 final 的变量来说,不管是类变量,实例变量还是局部变量。只要在编译的时候可以确定,那么编译器就会把它看成一个直接量而不是变量。
比如:

public class FinalVar{
public static void main(String[] args){
final int a =5;
System.out.println(a);
}
}

实际编译器在执行 System.out.println(a);等同于执行 System.out.println(5);
什么叫做编译时可以确定
下面这个例子。
str1 在编译的时候就可以确定。而 str2 需要调用 valueOf() 方法才能得到后面一个字符串。在编译的时候无法确定。从结果也可以看出来。

final String str1 = "hello world"+2016;
final String str2 = "hello world"+String.valueOf(2016);

System.out.println(str1=="hello world2016");
System.out.println(str2=="hello world2016");

再看这个

String str1 = "hello world";
String str2 = "hello " + "world";

String s1 = "hello ";
String s2 = "world";

String str3 = s1 + s2;

System.out.println(str1 == str2);
System.out.println(str1 == str3);

str1 == str3 返回是 false。因为 str3 在编译是无法确定。所以不会被当作直接量。但是如 s1 和 s2 都加上 final。编译器会进行宏替换。str3 就等同于”hello “ + ”world”。

final 的方法

final 的方法不会被重写。

final 的类

final 的类不能被继承。

不可变类

Java 提供的基础变量类型的包装类和 String 类都是不可变类。如果要自己创建不可变的类需要注意几点:

  • 使用 private 和 final 修饰成员变量。
  • 提供带参数的构造器,用于根据需要传入参数来初始化类里的成员变量。
  • 仅提供 getter 方法,不提供 setter 方法。
  • 如果有必要重写 hashCode() 和 equals() 方法。还要保证两个用 equals 方法判断相等的对象 hasdCode() 也相等。
public class Address {
private final String detail;
private final String postCode;
public Address(){
this.detail="";
this.postCode="";
}
public Address(String detail,String postCode){
this.detail=detail;
this.postCode=postCode;
}
public String getDetail(){
return this.detail;
}
public String getPoetCode(){
return this.postCode;
}
public boolean equals(Object obj){
if(this == obj){
return true;
}
if(obj!=null&&obj.getClass()==Address.class){
Address ad = (Address)obj;
if(this.getClass().equals(ad.getDetail())&&this.getPoetCode().equals(ad.getPoetCode())){
return true;
}
}
return false;
}
public int hashCode(){
return this.detail.hashCode()+this.postCode.hashCode();
}
}

编写不可变类的时候,只需要提供 getter 方法,不提供 setter 方法就可以了。但是对引用传递一定要注意。因为引用传递传的是尼玛指针。下面就是一个失败的例子。

public class Person {
public final Name name;
public Person(Name name){
this.name = name;
}
public Name getName(){
return this.name;
}
public static void main(String[] agrs){
Name n = new Name("Brady""Gao");
Person p = new Person(n);
System.out.println(p.getName().getFirstName());
n.setFirstName("heihei");
System.out.println(p.getName().getFirstName());
}
}
class Name {
private String firstName;
private String lastName;
public Name(){}
public Name(String firstName,String lastName){
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName(){
return this.firstName;
}
public void setFirstName(String firstName){
this.firstName = firstName;
}
public String getLastName(){
return this.lastName;
}
public void setLastName(String lastName){
this.lastName = lastName;
}
}

要想解决这个,就要做到,避免引用传递带来的指针的直接传递。
重写 Person 的构造方法。

public Person (Name name){
this.name = new Name(name.getFirstName(),name.getLastName());
}

References

  • 番茄博客-final 修饰符(原链接失效)