Java面向對象之final、abstract抽象、和變量生命週期java
final是最終、不可修改的意思, 在Java中它能夠修飾非抽象類,非抽象方法和變量。可是須要注意的是:構造方法不能使用final修飾,由於構造方法不可以被繼承。下面,我們就來一一看看吧!算法
使用final關鍵字修飾類segmentfault
先考慮下圖的代碼例子:設計模式
代碼顯示錯誤,沒法從SuperClass繼承,編譯器提示刪除final關鍵字;刪除final關鍵字後,代碼正確無誤。安全
由此可得出:final修飾的類:,表示最終的類,,即該類不能再有子類,不能再被繼承。只要知足如下條件就能夠考慮把一個類設計成final類:工具
java裏final修飾的類有不少,好比八大基本數據類型的包裝類(Byte,Character、Short、Integer、Long、Float、Double、Boolean)和String等。spa
// Byte public final class Byte extends Number implements Comparable<Byte> { } // Character public final class Character implements java.io.Serializable, Comparable<Character> { } // Short public final class Short extends Number implements Comparable<Short> { } // Integer public final class Integer extends Number implements Comparable<Integer> { } // Long public final class Long extends Number implements Comparable<Long> { } // Float public final class Float extends Number implements Comparable<Float> { } // Double public final class Double extends Number implements Comparable<Double> { } // Boolean public final class Boolean implements java.io.Serializable, Comparable<Boolean> { } // String public final class String implements java.io.Serializable, Comparable<String>, CharSequence { }
使用final關鍵字修飾方法設計
若是用final關鍵字修飾方法呢?先考慮如下的代碼:code
如果用final修飾方法,繼承該方法時會報編譯錯誤;刪除該關鍵字後,doWork()可被繼承,代碼編譯經過;final修飾的方法爲最終的方法,該方法不能被子類覆蓋,故也不能使用方法重寫。那麼什麼樣的狀況下方法須要使用final修飾呢?對象
注意: final修飾的方法了,子類能夠調用,可是不能覆蓋(重寫)。
類常量:使用final關鍵字修飾的字段
常量分類:
經過上述代碼,不難看出,final關鍵字修飾的字段沒法被修改。一般開發中,咱們建議final修飾的常量名用大寫字母表示,多個單詞之間使用下劃線(_)鏈接:如:
public static final String USER_NAME = "用戶名";
且在Java中多個修飾符之間是沒有前後關係的,如下的三種修飾符排列順序都是ok的:
public static final 或者 public final static 亦或者 final static public
final修飾的變量是最終的變量,常量;該變量只能賦值一次,也只能在聲明時被初始化一次,不能被修改。在使用時需注意:
final修飾的引用類型變量到底表示引用的地址不能改變,仍是其存儲的數據不能改變
何時使用常量:
爲什麼要使用final修飾符呢?在繼承關係中最大弊端就是會破壞封裝,子類能訪問父類的實現細節,,並且能夠經過方法重寫(方法覆蓋)的方式修改方法的實現細節。且 final仍是是惟一能夠修飾局部變量的修飾符。
考慮以下的案例:求圓(Circle)、矩形(rectangle)的面積
上述代碼設計是存在問題的:
案例:求圓(Circle)、矩形(rectangle)的面積 引入抽象的設計
抽象方法
使用abstract關鍵字修飾且沒有方法體的方法,稱爲抽象方法。其特色是:
通常會把abstract寫在方法修飾符最前面,一看就知道是抽象方法;固然若是不這樣寫也沒錯。
抽象類
使用abstract關鍵字修飾的類,稱爲抽象類。其特色是:
抽象類在命名時,通常使用Abstract做爲前綴,讓調用者見名知義,看類名就知道其是抽象類。
抽象類中能夠不存在抽象方法,這樣作雖然沒有太大的意義,可是能夠防止外界建立其對象,因此咱們會發現有些工具類沒有抽象方法,但倒是使用abstract來修飾類的。
普通類有的成員(方法、字段、構造器),抽象類本質上也是一個類,故其都有。抽象類不能建立對象,但抽象類中是能夠包含普通方法的。
程序中的變量是用來存儲數據的,其又分爲常量和變量兩種,關於變量的詳情能夠查看個人另外一篇文章:[JAVA] Java 變量、表達式和數據類型詳解。定義變量的語法:
數據類型 變量名 = 值;
變量根據在類中定義位置的不一樣,分紅兩大類:
成員變量: 全局變量/字段(Field),是定義在類中,方法做用域外的變量;能夠先使用後定義(使用在前,定義在後)。
局部變量:變量除了成員變量,其餘都是局部變量,主要體如今方法內,方法參數,代碼塊內;局部變量必須先定義然後才能使用。
變量的初始值:變量只有在初始化後纔會在內存中開闢空間。
成員變量: 默認是有初始值的。
局部變量: 沒有初始值。因此必須先初始化才能使用,並且其初始化是在方法執行開始時才進行的。
變量的做用域:變量根據定義的位置不一樣,也決定了各自的做用域是不一樣的,最直觀的就是看變量所在的那對花括號{},也就是離得最近的那對{}。成員變量的做用域在整個類中都有效。局部變量的做用域在開始定義的位置開始,到緊跟着結束的花括號爲止。
變量的生命週期
變量的做用域指的是變量的可以使用的範圍,只有在這個範圍內,程序代碼才能訪問它。當一個變量被定義時,它的做用域就肯定了。變量的做用域決定了變量的生命週期,做用域不一樣,生命週期就不同。
變量的生命週期指的是一個變量被建立並分配內存空間開始,到該變量被銷燬並清除其所佔內存空間的過程。
在開發中,一個項目會有成百上千個Java文件,若是全部的Java文件都在一個目錄中,那麼管理起來就會很痛苦,很難想象這樣的項目會是什麼樣子。在Java中,引入了稱之爲包(package)的概念。即:關鍵字:package ,專門用來給當前Java文件設置包名(也就是命名空間)。其語法格式以下:
package 包名.子包名.子包名;
必須把package語句做爲Java文件中的第一行代碼,在全部代碼以前。
package 語句和java編譯
在編譯java文件時的編譯命令爲:
javac -d . Hello.java
若是此時Hello.java文件中沒有使用package語句,表示在當前目錄中生成字節碼文件。運行時也不須要考慮包名。
若是此時Hello.java文件中使用了package語句,此時表示在當前目錄中先生成包名目錄,再在包名目錄中生成字節碼文件。運行命令以下:
java 包名.類名;
package命名:
package命名格式:
package 域名倒寫.模塊名.組件名;
1.package下的類名:
2.建議:先定義package名稱,再在定義的package內定義類。
當A類和B類不在同一個包中,若A類須要使用到B類中的功能,此時就得讓A類中去引入B類。使用import語句,把某個包下的類導入到當前類中。
語法格式: import 須要導入類的全限定名;
引入後在當前類中,只須要使用類的簡單名稱便可訪問。
若是咱們須要引入包中的多個類,咱們還得使用多個import語句,要寫不少次;此時可使用通配符(*)。
注意:編譯器會默認導入java.lang包下的類,可是並不會導入java.lang的子包下的類。好比:java.lang.reflect.Method類,此時咱們也得使用import java.lang.reflect.Method;來導入Method類。
靜態import
靜態import,靜態導入,是指將經過import static導入其餘類的靜態成員。如下代碼實例:
package demo.importdir; public class StaticDemo { public static final int COUNT = 10; } package demo.dir; import static demo.importdir.StaticDemo.COUNT; public class StaticImportDemo { public static void main(String[] args) { System.out.println(COUNT); } }
而後咱們對StaticImportDemo反編譯,觀察JVM是如何處理靜態導入的:
import java.io.PrintStream; import demo.importdir.StaticDemo; public class StaticImportDemo{ public StaticImportDemo() { } public static void main(String args[]) { System.out.println(StaticDemo.COUNT); } }
經過上述的反編譯代碼,不難發現,其實所謂的靜態導入也是一個語法糖/編譯器級別的新特性,其實在底層也是類名.靜態成員去訪問的。
因此在企業項目開始中不建議使用靜態導入,容易引發字段名,方法名混淆,不利於項目維護。
經過對象調用字段,在編譯時期就已經決定了調用哪一塊內存空間的數據。因此字段不存在覆蓋的概念,也就是字段不會有多態特徵,在運行時期體現的也會是子類特徵。
public class FieldDemo { public static void main(String[] args) { SubClass subClass = new SubClass(); System.out.println(subClass.name); } } class SuperClass { protected String name = "SuperClass.name"; } class SubClass { protected String name= "SubClass.name"; } // 運行結果:SubClass.name
經過運行上述代碼,不難發現,當子類和父類存在相同的字段的時候,不管修飾符是什麼(即便是private),都會在各自的內存空間中存儲數據,字段並無體現出多態;
其實經過方法重寫字面意思也能發現其是針對方法的。因此只有方法纔有覆蓋的概念,而字段並不會被覆蓋。
什麼是代碼塊:在類或者在方法中,直接使用"{}"括起來的一段代碼,表示一塊代碼區域,咱們將其稱爲代碼塊。代碼塊裏變量屬於局部變量,只在本身所在的做用域(所在的{})內有效。根據代碼塊定義的位置的不一樣,咱們又分紅三種形式:
1.局部代碼塊:直接定義在方法內部的代碼塊;通常不會直接使用局部代碼塊,而是會結合if,while,for,try等關鍵字配合使用,還有匿名內部類,表示一塊代碼區域。示例以下:
if (true) { ...... }
2.初始化代碼塊(構造代碼塊):定義在類中,每次建立對象的時候都會執行,而且是在構造器調用以前先執行本類中的初始化代碼塊。但其實JVM在處理初始化代碼塊時是將其移動到構造器中的最前面,從而達到先執行初始化代碼塊,再執行構造器的功能。
在實際開發中,不多使用初始化代碼塊;初始化操做會在構造器中進行,若是作初始化操做的代碼比較複雜,能夠另外定義一個方法作初始化操做,而後再在構造器中調用。
3.靜態代碼塊:使用static修飾的初始化代碼塊。格式以下:
class StaticDemo { static { ...... } }
靜態代碼塊會在主方法(main方法)執行以前執行,並且只執行一次。在Java中,main方法是程序的入口,靜態代碼塊優先於main方法執行;是由於靜態成員是隨着字節碼的加載而進入JVM中的,但此時此時main方法還沒執行,由於main方法須要JVM調用方能執行。
如下是一個代碼塊的示例:
public class CodeBlockDemo { { System.out.println("執行初始化代碼塊"); } public CodeBlockDemo() { System.out.println("執行無參構造器"); } static { System.out.println("執行靜態代碼塊"); } public static void main(String[] args) { new CodeBlockDemo(); new CodeBlockDemo(); new CodeBlockDemo(); } }
其運行結果爲:
執行靜態代碼塊 執行初始化代碼塊 執行無參構造器 執行初始化代碼塊 執行無參構造器 執行初始化代碼 塊執行無參構造器
不難發現,調用順序依次爲:靜態代碼塊--》初始化代碼塊--》構造器,且靜態代碼塊只執行一次。而後再對上述示例代碼作反編譯:
import java.io.PrintStream; public class CodeBlockDemo{ public CodeBlockDemo() { System.out.println("執行初始化代碼塊"); System.out.println("執行無參構造器"); } public static void main(String args[]) { new CodeBlockDemo(); new CodeBlockDemo(); new CodeBlockDemo(); } static { System.out.println("執行靜態代碼塊"); } }
經過反編譯結果,發現JVM在處理初始化代碼塊時是將初始化代碼塊的代碼移動到構造器中的最前面,從而達到先執行初始化代碼塊,再執行構造器的功能。
完結。老夫雖不正經,但老夫一身的才華