java中final的意義
轉載自 https://blog.csdn.net/hikvision_java_gyh/article/details/8964541
1、如果一個數據既是static又是final,那麼它會擁有一塊無法改變的儲存空間。
2、final data: 當final用於基本資料型別時,final讓其值(value)保持不變,但是當用於object reference時,final僅讓reference保持不變。也就是說當reference一旦被初始化用於代表某個物件時,便再也不能改變指向另一個物件,但物件本身的內容確實可以改變的。final對array的作用和對reference的作用一樣。參考以下例子:
public class Test1{
private final int li_int=12;
private final InClass inClass1=new InClass(5);
private final InClass inClass2=new InClass(8);
public void modifiedFinal(int a){
//下面語句出現編譯錯誤,不能修改final基本型別的值
//li_int = a;
//下面語句出現編譯錯誤,不能將已經初始化的final變數指向另一個物件
//inClass1=inClass2;
//下面語句成功,雖然引用不能改變但final變數引用的物件本身內容是可以改變的
inClass1.mod(a);
}
class InClass{
int li_a=0;
public InClass(int a){
li_a=a;
}
public int mod(int b){
li_a=b;
return li_a;
}
}
public static void main(String args[]){
Test1 test1=new Test1();
test1.modifiedFinal(100);
System.out.println(test1.inClass1.li_a);
}
}
3、blank finals:java允許將資料成員宣告為final,卻不賦初值。但是,blank finals必須在使用之前初始化,且必須在建構函式中初始化。請參考以下例子:
public class Test2{
//final變數一開始允許不賦值
private final int li_int;
public Test2(int a){
//下面語句編譯通過,對定義為空的final變數的賦值必須在構造方法中進行,而且必須要賦值,不賦值也報錯
li_int = a;
}
public int mod(int a){
//下面語句編譯出錯,對定義為空的final變數的賦值必須在構造方法中進行
//li_int = a;
return li_int;
}
}
4、final arguments: 宣告arguments為final,可以保證該argument不能再被指向它處,當argment是基本資料型別時,就意味著值不能改變。參考以下例子:
public class Test3{
private int li_int=12;
private InClass inClass1=new InClass(5);
private InClass inClass2=new InClass(8);
public void modifiedFinal(final int a,final InClass in){
//下面語句出現編譯錯誤,不能修改final基本型別的值
//a = 15;
//下面語句出現編譯錯誤,不能將已經初始化的final變數指向另一個物件
//in=inClass2;
//下面語句成功,雖然引用不能改變但final變數引用的物件本身內容是可以改變的
in.mod(a);
}
class InClass{
int li_a=0;
public InClass(int a){
li_a=a;
}
public int mod(int b){
li_a=b;
return li_a;
}
}
public static void main(String args[]){
int a=100;
//內部類初始化
Test3 test3=new Test3();
Test3.InClass in=test3.new InClass(30);
System.out.println(in.li_a);
test3.modifiedFinal(a,in);
System.out.println(in.li_a);
}
}
5、final methods: 可以鎖住該method,不讓繼承類改變其意義(不允許子類覆寫);允許編譯器對此method作為inline method呼叫。參考以下例子:
public class Test4{
private final int li_int=0;
public final int pub_fi_mod(){
return li_int;
}
protected final int pro_fi_mod(){
return li_int;
}
private final int pri_fi_mod(){
return li_int;
}
private int pri_mod(){
return li_int;
}
}
public class Test5 extends Test4{
private int li_i=100;
//下面的方法編譯出錯,不能覆蓋final方法,只針對public和protected,子類中方法與父類中private的方法名相同不是覆蓋,與父類中同方法名的方法沒有任何關係(除了名字相同)。
/*
public int pub_fi_mod(){
return li_i;
}
protected int pro_fi_mod(){
return li_i;
}*/
private final int pri_fi_mod(){
return li_i;
}
private int pri_mod(){
return li_i;
}
public static void main(String args[]){
System.out.println(new Test5().pri_mod());
}
}
6、fianl(method) vs private(method): class所有的private methods自然而然都是final,private methods僅僅是隱藏class中的某段程式程式碼而已,不能被overrid,即使子類中恰好有同名的method,也不會產生什麼效果;其中兩者的區別是在子類中可以出現與private方法有相同簽名的方法,而public或protected的final方法不能被重寫,但允許方法名相同但引數列表不同的重構方法出現。借用以上例子,將Test5修改後編譯通過:
public class Test5 extends Test4{
private int li_i=100;
//下面的方法編譯出錯,不能覆蓋final方法
/*
public int pub_fi_mod(){
return li_i;
}
protected int pro_fi_mod(){
return li_i;
}
*/
//但允許引數列表不同的重構方法出現
public int pub_fi_mod(int a){
return li_i;
}
protected int pro_fi_mod(int a){
return li_i;
}
private final int pri_fi_mod(){
return li_i;
}
private int pri_mod(){
return li_i;
}
public static void main(String args[]){
System.out.println(new Test5().pri_mod());
}
}
7、final classes: 當把一個class宣告為final時,也就決定了此class將不能被繼承(比如String類,此類為final類,具體可以參見其實現java.lang.String)。final classes的methods可以是final,也可以是非final的;其中的資料成員可以是final的也可以不是,他們將服從final data的原則。參考以下例子:
public final class Test6{
private final int li_int=0;
public int li_a=123;
public final int mod(){
return li_int;
}
public int pri_mod(){
return li_a;
}
public static void main(String args[]){
System.out.println(new Test6().pri_mod());
}
}
//Test6是final類,所以Test7不能繼承
public class Test7 extends Test6{
private int li_int=0;
}
PS:從以上可以看出,final是將一個物件的地址不變,對基本型別的值保持不變(因為基本型別變數指向的實體地址存放value而物件變數指向的實體地址存放物件內容的地址)。
PS:以前讀書時老師說java中final定義常量,只說對了一半,對基本型別是對的,對String也是對的,因為String雖然是物件,但不會出現String變數地址不變而其內容發生改變的情況(String是一個整體不能只改變其中的一個字元),所以也是對的,但對其他的物件只能保持其引用地址不變不能保證其內容不變,所以是錯的。
再補充一些內容:
1、對final屬性在宣告時就賦值,而且賦的值是常量的話,那編譯器會將所有用到此屬性的地方都替換成常量,這個請參考下面的程式碼:
package com.xx.dryr.test1;
import java.lang.reflect.Field;
public class Test1Class1{
public final int x = 100;
public int f(Test1Class1 t1c11,Test1Class1 t1c12) throws Exception{
int i = t1c11.x;
System.out.println("i's value is "+i);
changeX(t1c11);
int j = t1c12.x;
System.out.println("j's value is "+j);
return j - i;
}
public static void changeX(Test1Class1 t1c1) throws Exception{
Class clazz = t1c1.getClass();
Field fieldX = clazz.getDeclaredField("x");
fieldX.setAccessible(true);
fieldX.setInt(t1c1, 300);
System.out.println("fieldX's vlaue is "+fieldX.getInt(t1c1));
}
public int test() throws Exception{
return f(this,this);
}
public static void main(String[] args) throws Exception{
Test1Class1 t1c1 = new Test1Class1();
System.out.println(t1c1.test());
}
}
執行結果是:
i's value is 100
fieldX's vlaue is 300
j's value is 100
0
雖然在changeX方法中,已經將x的值修改為300,但因為編譯時所有使用到x的地方都使用100替換了,所以在執行時再怎麼修改x的值都不會對使用到x的地方產生影響。
2、否則,對不是在編譯時確定final屬性值的情況下,final屬性的值是可以改變的。請參考如下程式碼,對上面的程式碼稍微做了修改,讓final屬性x在構造方法中初始化:
package com.xx.dryr.test1;
import java.lang.reflect.Field;
public class Test1Class1{
public final int x ;
public Test1Class1(){
x = 100;
}
public int f(Test1Class1 t1c11,Test1Class1 t1c12) throws Exception{
int i = t1c11.x;
System.out.println("i's value is "+i);
changeX(t1c11);
int j = t1c12.x;
System.out.println("j's value is "+j);
return j - i;
}
public static void changeX(Test1Class1 t1c1) throws Exception{
Class clazz = t1c1.getClass();
Field fieldX = clazz.getDeclaredField("x");
fieldX.setAccessible(true);
fieldX.setInt(t1c1, 300);
System.out.println("fieldX's vlaue is "+fieldX.getInt(t1c1));
}
public int test() throws Exception{
return f(this,this);
}
public static void main(String[] args) throws Exception{
Test1Class1 t1c1 = new Test1Class1();
System.out.println(t1c1.test());
}
}
執行結果是:
i's value is 100
fieldX's vlaue is 300
j's value is 300
200
從上面的例子中可見,final屬性的值還是可以被改變的,但只有在特殊情況下(沒有在編譯時被替換),使用特殊的方式(像反射這樣的方式),final屬性的值才可以被改變。所以說一般情況下說final屬性的值是不允許被修改的還是可以說的,但必須得知道這些例外情況的。