1. 程式人生 > >開發筆記之詳述 JAVA 建構函式和程式碼塊本身及其執行細節

開發筆記之詳述 JAVA 建構函式和程式碼塊本身及其執行細節

今天在JAVA的研究學習當中發現了建構函式這個神奇但是麻煩的東西, 他在給我感覺很像OC語言中的initWith..., 但是在細節上有很多的不同, 而程式碼塊這個東西更是讓我這個敲iOS的眼前一亮, 後來針對程式碼塊這個東西的功能和執行的順序深究了一番.


首先說說建構函式

開頭說道這個東西給我的感覺很像initWith...甚至可以這麼去理解, 但是你深究他的寫法和功能你就會發現他跟initWith還是有很多的區別的.

什麼是建構函式(構造器)

主要用來在建立物件時初始化物件, 即為物件成員變數賦初始值,總與new運算子一起使用在建立物件的語句中。

所以, 建構函式是用來建立時初始化物件所用, 由於他本身可以傳遞引數的語法, 他可以在初始化的時候直接給你的物件賦值(所以我說這東西很想initWith), 但是在寫法和用法上要注意.

建構函式的優點

在開發中分析類的時候,發現類具備某些特徵,就可以將這些特徵定義在建構函式中,在使用時不僅十分的方便而且快捷,建構函式本身十分符合面向物件的程式設計思想。

我們來看看建構函式的程式碼

為了演示, 我們首先建立一個很簡單的Person類, 然後給他建立幾個成員變數.

class Person{
    String name;
    int age;
    boolean sex;
    double weight;
}

然後該建立Person的構造函數了, 但是先讓我們注意一下建構函式的語法和語義規則(語法, 語義有什麼區別, 具體指什麼, 

  • 建構函式的命名必須與類名相同 ---- 因為建構函式的功能是用來初始化類的例項物件的.
  • 它沒有返回值,也不能用void來修飾 ---- 所以這就是你不需要擔心他會返回什麼, 而且根本不能有任何選擇, 而且他的無返回值不是他自帶void, 而是他在語法規則上講就沒有.
  • 建構函式不能被直接呼叫,必須通過new運算子在建立物件時才會呼叫 ---- 就算你不寫他, 其實在你建立類的時候系統就自動給你寫好了一個建構函式, 以Person類為例
    Person(){
    }
    這個就是編譯器為你自動寫好的函式. 而一般的方法不具備這個功能, 但是這個建構函式不允許按照上文程式碼一樣去重寫, 因為他會無限的呼叫其本身, 產生比死迴圈更嚴重的遞迴.
  • 構造器可以擁有一個或者多個引數 ---- 這點我要著重講一下.

因為建構函式可以擁有一個或者多個引數, 並且他還是在建立時初始化物件所用到的一個方法, 所以不免多個引數的情況出現, 這樣為了程式的安全性, 按照上面的類為例子, 我就需要這麼寫建構函式.

Person(){
        this(null, 0, true, 0);
    }
    Person(String name){
        this(name, 0, true, 0);
    }
    Person(String name, int age){
        this(name, age, true, 0);
    }
    Person(String name, int age, boolean sex){
        this(name, age, sex, 0);
    }
    Person(String name, int age, boolean sex, double weight){
        this.name = name;
        this.age = age;
        this.sex = sex;
        this.weight = weight;
    }

這麼寫建構函式可以十分有效的避免出現你並不需要附加全部的引數, 但是容易出現程式上面的不安全的情況.


用static靜態修飾成員變數會在構造器中產生警報

經過上面的講解, 相信大家已經對建構函式有了具體而完整的認識, 下面我介紹一種我在寫建構函式的時候遇到的特殊情況, 這個問題可能也是很多同學都會遇到的坑, 那就是用static靜態修飾一個成員變數會出現的一個情況, 例如.

 static String name;
 int age;
 boolean sex;
 double weight;

在這裡我用static修飾了name, 現在name是一個靜態成員變量了, 而產生的結果就是

這裡的this.name產生了警告

後來經過我的研究發現這裡是由於程式碼, 或者說是由於static修飾的原因, name這個成員變數"不屬於方法, 屬於類的名下了", 因為this這個關鍵字指的是, 本身類的物件, 由於這個成員變數"屬於類的名下", 所以單純的用this. 這個成員變數才會產生警告, 我對this進行了修改, 改成Person這個類.

可以看到用Person.name警告消失了


程式碼塊

程式碼塊這個東西光是定義就很有意思

{}

沒錯, 你沒有看錯, 就兩個大括號, 這就是程式碼塊.
程式碼塊和建構函式聯動, 先於構造器來呼叫, 建構函式每被執行一次, 程式碼塊就被執行一次, 而因為這個特性, 通常做所有構造器呼叫的準備工作.

程式碼塊還能用static來修飾

static{
 }

而這個東西有幾個重點需要記住:

  • 修飾之後就是靜態程式碼塊, 這個是針對靜態成員變數的程式碼塊, 會對靜態變數做一些初始化操作
  • 他先於程式碼塊執行, 只在類被載入的時候才會被執行, 無論建立多少個物件只調用一次, 也隨著類的銷燬而銷燬.

對程式碼塊和建構函式有一定了解之後很明顯的就會產生了一個疑問, 程式碼的執行順序, 或者說編譯的順序是什麼樣的?

經過了一些神祕小軟體的幫助, 成功的找到了編譯的結果.
經過對多個編譯結果的研究發現程式碼的執行順序是顯式 ----> 靜態程式碼塊 ----> 程式碼塊 -----> 構造器


綜合上文可以得到一個結論

建立物件的流程

  1. 開闢記憶體
  2. **靜態變數的顯式初始化
  3. 靜態程式碼塊(如果有多個的情況, 按照程式碼書寫的順序執行)
  4. 成員變數的顯式子初始化(反編譯結果, 新增到了構造器中)
  5. 程式碼塊(反編譯結果, 新增到了構造器中)
  6. 構造器

但是經過我的除錯研究, 又發現了一個細節.

    static {
        System.out.println("靜態程式碼塊");
    }

如果你在靜態程式碼塊中什麼都沒寫的情況下, 編譯程式, 其結果顯而易見.

反編譯結果

可以看到跟結果一樣, 但是如果你在靜態程式碼塊中呼叫靜態成員變數的話, 你就會看到編譯結果有了一個變化.

呼叫靜態變數

    static {
        System.out.println(name);
        System.out.println("靜態程式碼塊");
    }

編譯結果有了一個顯著的變化

由此我可以得出第二條結論需要新增一個條件.

  • 靜態變數的顯式初始化(反編譯結果, 如果在靜態程式碼塊中呼叫顯式初始化的成員變數就會編譯在靜態程式碼塊中)

程式碼的執行順序或者細節是在程式開發中很重要的事情, 老手們都知道這個東西是多重要多好用, 做出多少文章就看各位的功力了.


此文只是一家之言, 而知識的分享總會增值, 非常期待與各位看官切磋想法, 交流心得.

加Java架構師進階交流群獲取Java工程化、高效能及分散式、高效能、深入淺出。高架構。
效能調優、Spring,MyBatis,Netty原始碼分析和大資料等多個知識點高階進階乾貨的直播免費學習許可權 
都是大牛帶飛 讓你少走很多的彎路的 群號是:  558787436 對了 小白勿進 最好是有開發經驗 

注:加群要求

1、具有工作經驗的,面對目前流行的技術不知從何下手,需要突破技術瓶頸的可以加。

2、在公司待久了,過得很安逸,但跳槽時面試碰壁。需要在短時間內進修、跳槽拿高薪的可以加。

3、如果沒有工作經驗,但基礎非常紮實,對java工作機制,常用設計思想,常用java開發框架掌握熟練的,可以加。

4、覺得自己很牛B,一般需求都能搞定。但是所學的知識點沒有系統化,很難在技術領域繼續突破的可以加。

5.阿里Java高階大牛直播講解知識點,分享知識,多年工作經驗的梳理和總結,
帶著大家全面、科學地建立自己的技術體系和技術認知!