1. 程式人生 > >java中final的意義

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屬性的值是不允許被修改的還是可以說的,但必須得知道這些例外情況的。