国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > Java > 正文

深入解析Java編程中final關鍵字的作用

2019-11-26 14:11:25
字體:
來源:轉載
供稿:網友

final class
當一個類被定義成final class,表示該類的不能被其他類繼承,即不能用在extends之后。否則在編譯期間就會得到錯誤。

package com.iderzheng.finalkeyword; public final class FinalClass {} // Error: cannot inherit from finalclass PackageClass extends FinalClass {}

Java支持把class定義成final,似乎違背了面向對象編程的基本原則,但在另一方面,封閉的類也保證了該類的所有方法都是固定不變的,不會有子類的覆蓋方法需要去動態加載。這給編譯器做優化時提供了更多的可能,最好的例子是String,它就是final類,Java編譯器就可以把字符串常量(那些包含在雙引號中的內容)直接變成String對象,同時對運算符+的操作直接優化成新的常量,因為final修飾保證了不會有子類對拼接操作返回不同的值。
對于所有不同的類定義―頂層類(全局或包可見)、嵌套類(內部類或靜態嵌套類)都可以用final來修飾。但是一般來說final多用來修飾在被定義成全局(public)的類上,因為對于非全局類,訪問修飾符已經將他們限制了它們的也可見性,想要繼承這些類已經很困難,就不用再加一層final限制。

另外要提到的是匿名類(Anonymous Class)雖然說同樣不能被繼承,但它們并沒有被編譯器限制成final。

import java.lang.reflect.Modifier; public class Main {   public static void main(String[] args) {    Runnable anonymous = new Runnable() {      @Override      public void run() {      }    };     System.out.println(Modifier.isFinal(anonymous.getClass().getModifiers()));  }}

 輸出:

 false

final Method
跟繼承觀念密切相關是多態(Polymorphism),其中牽扯到了覆蓋(Overriding)和隱藏(Hiding)的概念區別(為方便起見,以下對這兩個概念統一稱為“重寫”)。但不同于C++中方法定義是否有加virtual關鍵字會影響子類相同方法簽名的方法是覆蓋還是隱藏,在Java里子類用相同方法簽名重寫父類方法,對于類方法(靜態方法)會形成隱藏,而對象方法(非靜態方法)只發生覆蓋。由于Java允許通過對象直接訪問類方法,也使得Java不允許在同一個類中類方法和對象方法有相同的簽名。

final類限定了整個類不能被繼承,進而也表示該類里的所有方法都不能被子類所覆蓋和隱藏。當類不被final修飾時,依然可以對部分方法使用final進行修飾來防止這些方法被子類重寫。

同樣的,這樣的設計破壞了面向對象的多態性,但是final方法可以保證其執行的確定性,從而確保了方法調用的穩定性。在一些框架設計中就會經常見到抽象類的一些已實現方法的方法被限制成final,因為在框架中一些驅動代碼會依賴這些方法的實現了完成既定的目標,所以不希望有子類對它進行覆蓋。

下邊的例子展示了final修飾在不同類型的方法中起到的作用:

package com.iderzheng.other; public class FinalMethods {  public static void publicStaticMethod() {   }   public final void publicFinalMethod() {  }   public static final void publicStaticFinalMethod() {  }   protected final void protectedFinalMethod() {  }   protected static final void protectedStaticFinalMethod() {  }   final void finalMethod() {  }   static final void staticFinalMethod() {  }   private static final void privateStaticFinalMethod() {  }   private final void privateFinalMethod() {  }}package com.iderzheng.finalkeyword; import com.iderzheng.other.FinalMethods; public class Methods extends FinalMethods {   public static void publicStaticMethod() {  }   // Error: cannot override  public final void publicFinalMethod() {  }   // Error: cannot override  public static final void publicStaticFinalMethod() {  }   // Error: cannot override  protected final void protectedFinalMethod() {  }   // Error: cannot override  protected static final void protectedStaticFinalMethod() {  }   final void finalMethod() {  }   static final void staticFinalMethod() {  }   private static final void privateStaticFinalMethod() {  }   private final void privateFinalMethod() {  }}

首先注意上邊的例子里,FinalMethods和Methods是定義在不同的包(package)下。對于第一個publicStaticMethod,子類成功重寫了父類的靜態方法,但因為是靜態方法所以發生的其實是“隱藏”。具體表現為調用Methods.publicStaticMethod()會執行Methods類中的實現,調用FinalMethods.publicStaticMethod()時執行并不會發生多態加載子類的實現,而是直接使用FinalMethods的實現。所以在用子類去訪問方法時,會隱藏了父類相同方法簽名的方法的可見性。
對于全局方法publicFinalMethod就像final修飾方法描述的那樣禁止子類定義相同的方法去覆蓋它,在編譯時就會拋出異常。不過在子類定義方法名字一樣但是帶有個參數,比如:publicFinalMethod(String x)是可以的,因為這是同步的方法簽名。

在Intellij里,IDE對publicStaticFinalMethod顯示了一個警告:'static' method declared 'final'。在它看來這是多余的,但從實例中可以看出final同樣禁止了子類定義相同的靜態方法去隱藏它。在實際開發中,子類和父類定義相同的靜態方法的行為是極為不推薦的,因為隱藏方法需要開發者注意使用不同類名限定會有不同的效果,就很容易帶來錯誤。而且在類的內部是可以不使用類名限定直接調用靜態方法,開發者再度做繼承時可能沒有注意到隱藏的存在默認在使用父類的方法時就會發現不是預期的結果。所以對靜態方法應該默認已經是final而不該去隱藏他們,也因此IDE覺得是多余的修飾。

父類中protected修飾和public修飾的方法對于子類都是可見的,所以final修飾protected方法的情況和public方法是一樣的。想提到的是在實際開發中一般很少定義protected靜態方法,因為這樣的方法實用性太低。

對于父類package方法,處在不同的package下的子類是不可見的,private方法已經定制了只有父類自己可訪問。所以編譯器允許子類去定義相同的方法。但這不形成覆蓋或隱藏,因為父類已經通過修飾符來隱藏了這些方法,而非子類的重寫造成的。當然如果子類和父類在同一package下,那么情況也和之前的public、protected一樣了。

final方法為何會高效:

final方法會在編譯的過程中利用內嵌機制進行inline優化。inline優化是指:在編譯的時候直接調用函數代碼替換,而不是在運行時調用函數。inline需要在編譯的時候就知道最后要用哪個函數, 顯然,非final是不行的。非final方法可能在子類中被重寫,由于可能出現多態的情況,編譯器在編譯階段并不能確定將來調用方法的對象的真正類型,也就無法確定到底調用哪個方法。

final Variable
簡單說,Java里的final變量只能且必須被初始化一次,之后該變量就與該值綁定。但該次賦值不一定要在變量被定義時被立刻初始化,Java也支持通過條件語句給final變量不同的結果,只是無論如何該變量都只能變賦值一次。

不過Java的final變量并非絕對的常量,因為Java的對象變量只是引用值,所以final只是表示該引用不能改變,而對象的內容依然可以修改。對比C/C++的指針,它更像是type * const variable而非type const * variable。

Java的變量可以分為兩類:局部變量(Local Variable)和類成員變量(Class Field)。下邊還是用代碼來分別介紹它們的初始化情況。

Local Variable

局部變量主要指定義在方法中的變量,出了方法它們就會消失不可訪問。其中有可分出一種特殊情況:函數參數。對于這種情況,其初始化與函數被調用時傳入的參數綁定。

對于其他的局部變量,它們被定義在方法中,其值就可以被有條件的初始化:

public String method(final boolean finalParam) {  // Error: final parameter finalParam may not be assigned  // finalParam = true;   final Object finalLocal = finalParam ? new Object() : null;   final int finalVar;  if (finalLocal != null) {    finalVar = 21;  } else {    finalVar = 7;  }   // Error: variable finalVar might already have been assigned  // finalVar = 80;   final String finalRet;  switch (finalVar) {    case 21:      finalRet = "me";      break;    case 7:      finalRet = "she";      break;    default:      finalRet = null;  }   return finalRet;}

從上述例子中可以看出被final修飾的函數參數無法被賦予新的值,但是其他final的局部變量則可以在條件語句中被賦值。這樣也給final提供了一定的靈活性。
當然條件語句中的所有條件里都應該包含對final局部變量的賦值,否則就會得到變量可能未被初始化的錯誤

public String method(final Object finalParam) {  final int finalVar;  if (finalParam != null) {    finalVar = 21;  }   final String finalRet;   // Error: variable finalVar might not have been initialized  switch (finalVar) {    case 21:      finalRet = "me";      break;    case 7:      finalRet = "she";      break;  }   // Error: variable finalRet might not have been initialized  return finalRet;}

理論上局部變量沒有被定義成final的必要,合理設計的方法應該可以很好的維護局部變量。只是在Java方法中使用匿名函數做閉包時,Java要求被引用的局部變量必須被定義為final:

public Runnable method(String string) {  int integer = 12;  return new Runnable() {    @Override    public void run() {      // ERROR: needs to be declared final      System.out.println(string);      // ERROR: needs to be declared final      System.out.println(integer);    }  };}

Class Field

類成員變量其實也能分成兩種:靜態和非靜態。對于靜態類成員變量,因為它們與類相關,所以除了在定義時直接初始化,還可以放在static block中,而使用后者可以執行更多復雜的語句:

package com.iderzheng.finalkeyword; import java.util.HashSet;import java.util.LinkedHashSet;import java.util.Set; public class StaticFinalFields {  static final int STATIC_FINAL_INIT_INLINE = 7;  static final Set<Integer> STATIC_FINAL_INIT_STATIC_BLOCK;   /** Static Block **/  static {    if (System.currentTimeMillis() % 2 == 0) {      STATIC_FINAL_INIT_STATIC_BLOCK = new HashSet<>();    } else {      STATIC_FINAL_INIT_STATIC_BLOCK = new LinkedHashSet<>();    }    STATIC_FINAL_INIT_STATIC_BLOCK.add(7);    STATIC_FINAL_INIT_STATIC_BLOCK.add(21);  }}

 Java中也有非靜態的block可以對非靜態的成員變量進行初始化,但是對于這些變量,更多的時候還是放在構造函數(constructor)里進行初始化。當然必須保證每個final變量在構造函數里都有被初始化一次,如果通過this()調用了其他的構造函數,則這些final變量不能再在該構造函數里被賦值了。

package com.iderzheng.finalkeyword; public class FinalFields {   final long FINAL_INIT_INLINE = System.currentTimeMillis();  final long FINAL_INIT_BLOCK;  final long FINAL_INIT_CONSTRUCTOR;   /** Initial Block **/  {    FINAL_INIT_BLOCK = System.nanoTime();  }   FinalFields() {    this(217);  }   FinalFields(boolean bool) {    FINAL_INIT_CONSTRUCTOR = 721;  }   FinalFields(long init) {    FINAL_INIT_CONSTRUCTOR = init;  }}

當final用來修飾類(Class) 和方法(Method)時,它主要影響面向對象的繼承性,沒有了繼承性就沒有了子類對父類的代碼依賴,所以在維護時修改代碼就不用考慮會不會破壞子類的實現,就顯得更加方便。而當它用在變量(Variable)上時,Java保證了變量值不會修改,更進一步設計保證類的成員也不能修改的話,那么整個變量就可以變成常量使用,對于多線程編程是非常有利的。所以final對于代碼維護有非常好的作用。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 仪陇县| 姚安县| 林周县| 大同县| 京山县| 卢湾区| 靖江市| 丹阳市| 博兴县| 同江市| 重庆市| 维西| 淮南市| 安阳市| 内黄县| 黑河市| 康乐县| 赤壁市| 安顺市| 迭部县| 博野县| 南阳市| 深水埗区| 惠州市| 黑龙江省| 托克逊县| 灵川县| 延川县| 五峰| 德令哈市| 邵阳市| 尚义县| 南木林县| 紫阳县| 苍山县| 乌拉特后旗| 镇赉县| 韶关市| 孝感市| 孝感市| 铜陵市|