Java編程思想——初始化與清理

PS:最近一直忙於項目開發..因此一直沒有寫博客..趁着空閒期間來一發..html

 

學習內容:java

1.初始化算法

2.清理編程

 

1.初始化安全

  雖然本身的Java基礎仍是比較良好的..可是在解讀編程思想的時候仍是發現了許多的細節問題本身並無徹底的掌握.既然是研磨,那麼就應該更加的細緻.函數

  i.構造方法的重載.學習

  首先說明一下,爲何構造方法須要重載.測試

  須要重載的一個重要緣由就是,由於咱們的構造器只能有一個名字,也就是和類名相同.可是若是咱們須要經過不一樣的方式去構造一個對象的時候咱們該如何是好?那麼這裏就須要經過對構造器的重載來實現.這樣就須要多個構造器來實現.首先就是須要一個默認的構造器,而後其餘的構造器就須要經過在重載構造器的方式來構造不一樣的構造器(針對不一樣的參數).this

class Darker{
    private String darker;
    public Darker() {
        // TODO Auto-generated constructor stub
        System.out.println("Default Constructor: "+darker);
    }
    
    public Darker(String darker){
        this.darker = darker;
        System.out.println("Overload Constructor: "+darker);
    }
}

public class Main {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Darker darker = new Darker();
        Darker darker2 = new Darker("darker");
    }
}

  這樣經過重載構造器.咱們就能夠經過使用不一樣的方法對對象進行初始化.那麼重載的方法.Java如何知道要走哪一個方法去構造一個對象呢?其實不難理解,因爲方法名稱相同,那麼無非就是經過參數的不一樣來識別.甚至經過參數的順序也可以調用不一樣的構造器.不過通常是不推薦這樣使用的.
spa

  ii.默認構造器.

  默認構造器被稱爲無參構造器,做用就是建立一個默認的對象.若是咱們的類中沒有定義一個默認構造器,那麼編譯器會自動爲咱們建立一個默認的構造器.不難發現.咱們在寫一個class的時候,即便不書寫默認構造器,咱們仍然能夠建立一個普通的對象.

class Darker{
    public String getDarker() {
        return darker;
    }
    public void setDarker(String darker) {
        this.darker = darker;
    }
    private String darker;
}

public class Main {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Darker darker = new Darker();
        darker.setDarker("darker");
        System.out.println(darker.getDarker());
    }
}
/**
 *OutPut
 *darker 
 */

  咱們能夠看到,咱們即便不去定義一個默認構造器去建立對象仍然是沒有任何問題的.由於編譯器會自動爲咱們加上一個默認構造器.可是這裏有一個陷阱..

package com.thinking.in.java;

class Darker{
    
    private String darker;
    
    public Darker(String darker){
        this.darker = darker;
    }
    
//    public Darker(){
//    
//    }
    
    public String getDarker() {
        return darker;
    }

    public void setDarker(String darker) {
        this.darker = darker;
    }
    
}

public class Main {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Darker darker = new Darker();
        darker.setDarker("darker");
        System.out.println(darker.getDarker());
    }
}

  這個代碼和上面的那個區別在於咱們定義了一個有參構造器.可是正是因爲這個定義,咱們就沒法使用經過無參構造器去建立一個對象.Darker darker = new Darker(); 這句話連編譯都沒法經過.直接報錯 The constructor Darker() is undefined .也就是咱們的無參構造器沒有定義.這是什麼緣由呢?這裏的問題就取決於Java的機制.若是咱們沒有去書寫任何一個構造器,那麼Java會自動爲咱們添加上一個默認的構造器,咱們能夠直接構造對象.可是若是咱們定義了一個構造器(注意:有參構造器).那麼Java就會明白:你已經定義了一個構造器了,因此你知道本身在作什麼.只是忽略掉了默認構造器.這樣Java就不會爲咱們添加默認構造器了.前面一直在說有參,若是咱們把默認構造器的註釋拿掉,那麼咱們 Darker darker = new Darker()這句話就不會出錯了.

 iii.涉及基本類型的重載

 基本類型的重載涉及的東西並非不少.只是涉及了兩個概念..擴輾轉型和窄化轉型.

 擴輾轉型:

package com.thinking.in.java;

public class Main {
    void f1(char x) {System.out.print("f1(char) ");}
    void f1(byte x) {System.out.print("f1(byte) ");}
    void f1(short x) {System.out.print("f1(short) ");}
    void f1(int x) {System.out.print("f1(int) ");}
    void f1(long x) {System.out.print("f1(long) ");}
    void f1(float x) {System.out.print("f1(float) ");}
    void f1(double x) {System.out.print("f1(double) ");}

    void f2(byte x) {System.out.print("f2(byte) ");}
    void f2(short x) {System.out.print("f2(short) ");}
    void f2(int x) {System.out.print("f2(int) ");}
    void f2(long x) {System.out.print("f2(long) ");}
    void f2(float x) {System.out.print("f2(float) ");}
    void f2(double x) {System.out.print("f2(double) ");}

    void f3(short x) {System.out.print("f3(short) ");}
    void f3(int x) {System.out.print("f3(int) ");}
    void f3(long x) {System.out.print("f3(long) ");}
    void f3(float x) {System.out.print("f3(float) ");}
    void f3(double x) {System.out.print("f3(double) ");}
    
    void f4(int x) {System.out.print("f4(int) ");}
    void f4(long x) {System.out.print("f4(long) ");}
    void f4(float x) {System.out.print("f4(float) ");}
    void f4(double x) {System.out.print("f4(double) ");}

    void f5(long x) {System.out.print("f5(long) ");}
    void f5(float x) {System.out.print("f5(float) ");}
    void f5(double x) {System.out.print("f5(double) ");}

    void f6(float x) {System.out.print("f6(float) ");}
    void f6(double x) {System.out.print("f6(double) ");}

    void f7(double x) {System.out.print("f7(double) ");}
    
    void testConstVal() {
        System.out.print("5: ");
        f1(5);f2(5);f3(5);f4(5);f5(5);f6(5);f7(5);
    }

    void testChar() {
        char x = 'x';
        System.out.print("char: ");
        f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);
    }

    void testByte() {
        byte x = 0;
        System.out.print("byte: ");
        f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);
    }

    void testShort() {
        short x = 0;
        System.out.print("short: ");
        f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);
    }

    void testInt() {
        int x = 0;
        System.out.print("int: ");
        f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);
    }

    void testLong() {
        long x = 0;
        System.out.print("long: ");
        f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);
    }

    void testFloat() {
        float x = 0;
        System.out.print("float: ");
        f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);
    }

    void testDouble() {
        double x = 0;
        System.out.print("double: ");
        f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);
    }
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Main m = new Main();
        m.testConstVal();m.testChar();m.testByte();m.testShort();
        m.testInt();m.testLong();m.testFloat();m.testDouble();
    }
}
/* Output:
5: f1(int) f2(int) f3(int) f4(int) f5(long) f6(float) f7(double)
char: f1(char) f2(int) f3(int) f4(int) f5(long) f6(float) f7(double)
byte: f1(byte) f2(byte) f3(short) f4(int) f5(long) f6(float) f7(double)
short: f1(short) f2(short) f3(short) f4(int) f5(long) f6(float) f7(double)
int: f1(int) f2(int) f3(int) f4(int) f5(long) f6(float) f7(double)
long: f1(long) f2(long) f3(long) f4(long) f5(long) f6(float) f7(double)
float: f1(float) f2(float) f3(float) f4(float) f5(float) f6(float) f7(double)
double: f1(double) f2(double) f3(double) f4(double) f5(double) f6(double) f7(double)
*///:~

  上面這段代碼其實就涉及到了擴輾轉型,擴輾轉型:將存儲數據信息量小的類型,轉換成存儲數據信息量較大的類型.從testConstVal()函數中就能夠看出來了.咱們傳遞的int = 5..是個int值常量.在f1()-f4()中都可以找到能夠接收int的參數.可是在f5()-f7()中就沒法找到可以接收int類型的函數,那麼這裏就會使用到擴輾轉型.將int = 5提高爲long,float,double.由於擴輾轉型是不存在數據信息丟失的問題.所以這種轉化是相對安全的.這裏有一個特例,針對char類型,若是沒有找到與char類型匹配的函數,會直接將char轉化成int類型.

 窄化轉型:

public class Main {
    
     void f1(char x) { System.out.print("f1(char)"); }
     void f1(byte x) { System.out.print("f1(byte)"); }
     void f1(short x) { System.out.print("f1(short)"); }
     void f1(int x) { System.out.print("f1(int)"); }
     void f1(long x) { System.out.print("f1(long)"); }
     void f1(float x) { System.out.print("f1(float)"); }
     void f1(double x) { System.out.print("f1(double)"); }

     void f2(char x) { System.out.print("f2(char)"); }
     void f2(byte x) { System.out.print("f2(byte)"); }
     void f2(short x) { System.out.print("f2(short)"); }
     void f2(int x) { System.out.print("f2(int)"); }
     void f2(long x) { System.out.print("f2(long)"); }
     void f2(float x) { System.out.print("f2(float)"); }

     void f3(char x) { System.out.print("f3(char)"); }
     void f3(byte x) { System.out.print("f3(byte)"); }
     void f3(short x) { System.out.print("f3(short)"); }
     void f3(int x) { System.out.print("f3(int)"); }
     void f3(long x) { System.out.print("f3(long)"); }

     void f4(char x) { System.out.print("f4(char)"); }
     void f4(byte x) { System.out.print("f4(byte)"); }
     void f4(short x) { System.out.print("f4(short)"); }
     void f4(int x) { System.out.print("f4(int)"); }

     void f5(char x) { System.out.print("f5(char)"); }
     void f5(byte x) { System.out.print("f5(byte)"); }
     void f5(short x) { System.out.print("f5(short)"); }

     void f6(char x) { System.out.print("f6(char)"); }
     void f6(byte x) { System.out.print("f6(byte)"); }

     void f7(char x) { System.out.print("f7(char)"); }

     void testDouble() {
       double x = 0;
       System.out.print("double argument:");
       f1(x);f2((float)x);f3((long)x);f4((int)x);
       f5((short)x);f6((byte)x);f7((char)x);
     }
     
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Main m = new Main();
        m.testDouble();
    }
}
/* Output:
double argument:f1(double)f2(float)f3(long)f4(int)f5(short)f6(byte)f7(char)*///:~

  咱們能夠看到,咱們此次針對double = 0 這個常量進行測試,從f2()-f7()就沒法找到與double請求參數所匹配的函數.這裏就涉及到了窄化轉型.因爲沒有合適的類型,所以只能將double進行窄化轉型,轉爲float,long,int以此類推.由於咱們若是想要函數正常運行,就必須使用窄化轉型,不然就會報錯.可是窄化轉型會將存儲數據信息量大的類型轉化成存儲數據信息小的類型.這樣就很容易致使數據信息丟失的狀況.所以通常狀況下是不推薦的.

 iv.成員初始化

 成員初始化沒有什麼過多可說的,咱們只須要知道.在咱們定義局部變量的時候,在定義的同時須要進行初始化操做,不然咱們是沒法使用當前的局部變量的.可是若是咱們在一個類中定義了成員變量,那麼咱們能夠在定義的時候不去進行初始化操做,Java會自動的幫咱們執行初始化的操做.

//函數中的局部變量若是在定義的時候沒被初始化,會出現異常.
void f(){
   int i;
   i++;  //Error  
}
//類中的成員變量,在被定義的時候就被初始化了.
public class Main {
    private int dint;
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        System.out.println(new Main().dint);  //init 0 
    }
}

  v.靜態數據的初始化

 針對靜態數據,仍是有一些東西須要注意的,咱們都知道靜態數據在內存單元中只佔用一塊存儲區域,不管有多少個對象建立.是一個做用於域範圍的變量.沒法當作局部變量去看待.而且當靜態數據一旦被初始化以後,後續就不會再次執行初始化的操做.

class Static{
    public Static() {
        // TODO Auto-generated constructor stub
    }
    
    public void Print(){
        System.out.println("Static");
    }
}
class TestStatic{
    static Static s = new Static(); 
    public TestStatic() {
        // TODO Auto-generated constructor stub
        s.Print();
        s1.Print();
    }
    static Static s1 = new Static();
}
public class Main {
        
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        new TestStatic();
    }
}

  總結一下對象初始化的過程.好比說有一個類.那麼對象被建立以前,JVM首先會找到類的指定路徑.定位.class文件(字節碼文件).而後JVM將.class文件裝載.這時有關當前類的靜態初始化操做將所有完成.(靜態初始化只在Class對象首次加載的時候執行一次).當咱們去new對象的時候,JVM會在堆區中開闢一塊內存,內存首先被清0,而後將類中全部的變量進行初始化.最後執行構造方法.

 

2.清理/垃圾回收

  初始化涉及的內容並非特別的多,而且相對而言也比較的簡單,清理纔是須要重點掌握的地方.

  爲何須要垃圾回收,垃圾回收是針對內存而言的,之因此進行垃圾回收是針對某塊內存已經再也不進行使用的時候,那麼這塊內存中的數據就要被回收,也就被稱爲所謂的"垃圾".可是回收並非時時刻刻都在調用的.由於GC的使用也會耗費必定的資源.而且咱們還須要明確一點就是,垃圾回收並不能保證必定會被運行.由於它所針對的是內存空間是否充足.

  i.finalize()函數.

  在讀編程思想的時候看到了這個方法,雖然知道,可是一直也不明確這個方法具體用在什麼地方.何時調用.簡單的說一下.

  首先finalize()函數是針對一種"特殊"的方式爲某個對象申請了一塊特殊的內存空間.因爲GC只知道釋放由new方式建立的對象.所以若是咱們使用了一種特殊的方式爲某個對象申請了一塊特殊的內存空間.就須要使用finalize()函數執行清理.

  咱們都知道Java是使用new去建立對象的,那麼這種特殊的方式究竟是怎樣的?這種特殊的方式多是在分配內存的時候使用了C語言的方式爲對象分配了內存,而不是Java一般的方式,這種方式發生在使用"本地方法"的時候發生的,也就是Java中的native方法,經過JNI與C/C++進行交互,那麼本地方法就由C/C++來執行了,那麼C語言分配內存的方式是經過使用malloc()方法來分配內存的.那麼使用malloc()方法分配內存空間以後,在不使用的時候須要使用free來進行釋放,若是咱們沒有去調用free函數去釋放內存,那麼這塊內存將一直不會被釋放,也就致使了內存泄漏,由於free是C/C++中才有的方法,所以咱們若是想使用free就須要經過使用finalize()中用本地方法進行調用.

  所以在垃圾清理的時候咱們是不能期望使用finalize()函數的.那麼垃圾回收就須要使用到咱們熟悉的東西了.

  ii.Garbage Collection(GC)

  垃圾回收器.概念相比你們都很是熟悉,在這裏不進行多餘的說.具體要說的是它的工做原理.

  GC的工做原理:

  對於工做原理就不得不說說引用計數法:

  好比說沒個對象都有一個計數器,當對象被建立的時候,計數器的數值設置爲1,當咱們再也不使用這個對象的時候,對象已經離開了做用域或者是null的時候,計數器的數值設置爲0,而後垃圾回收期在全部的對象列表上進行遍歷,而後將計數器爲0的對象進行回收,是否是感受這樣的設計仍是比較合理的呢?可是其實這種設計是有很大的缺陷的.若是咱們的對象之間存在循環調用.那麼就會出現,對象應該被回收,可是計數器卻不爲0的狀況.針對這種狀況須要具體說一下了.

  引用計數法雖然經常使用在解釋垃圾收集的方式,可是沒有一個JVM是使用這種算法的.

public class GcDemo {

    public static void main(String[] args) {
        GcObject obj1 = new GcObject(); //Step 1
        GcObject obj2 = new GcObject(); //Step 2

        obj1.instance = obj2; //Step 3
        obj2.instance = obj1; //Step 4

        obj1 = null; //Step 5
        obj2 = null; //Step 6
    }
}

class GcObject{
    public Object instance = null;
}

  咱們來看一下上面這個例子,若是使用引用計數法會致使什麼問題.

 

  1:GcObject實例1的引用計數加1,實例1的引用計數=1;

  2:GcObject實例2的引用計數加1,實例2的引用計數=1;

  3:GcObject實例2的引用計數再加1,實例2的引用計數=2;

  4:GcObject實例1的引用計數再加1,實例1的引用計數=2;執行到4,則GcObject實例1和實例2的引用計數都等於2。

  5:棧幀中obj1再也不指向Java堆,GcObject實例1的引用計數減1,結果爲1;

  6:棧幀中obj2再也不指向Java堆,GcObject實例2的引用計數減1,結果爲1。到此,發現GcObject實例1和實例2的計數引用都不爲0,那麼若是採用的引用計數算法的話,那麼這兩個實例所佔的內存將得不到釋放,這便產生了內存泄露。

  執行完5-6步的結果以下.

  這樣引用計數算法在JVM是沒有辦法獲得應用的.所以JVM採用另外一種模式的算法來解決這樣狀況的發生.

   可達性算法:

   現現在的Hotspot 中的minor GC就是使用這種算法.其中的核心就是圖論.圖中能夠到達的對象就是活對象,沒法到達的對象就應該被回收.這種算法有點相似於BFS算法(即:廣度優先搜索遍歷).從根節點出發.遍歷整個圖中的全部節點.可以到達的對象即構成連通的,不能到達的地方爲不通的.知道BFS算法的應該都特別的清楚.不過minor GC使用的是Cheney算法的變種.根節點爲GC Roots而且能夠有多個.這些節點被存儲在一個隊列當中.而後開始遍歷整個圖.遍歷到的對象就是活的.遍歷不到的就須要回收.

   GC Roots可使本地方法棧中JNI所引用的對象,虛擬機棧的棧幀局部變量所引用的對象,以及方法區中靜態變量或者常量所引用的對象.

 

  reference1->對象實例1;reference2->對象實例2;reference3-> 對象實例4;reference3->; 對象實例4 ->對象實例6;

  能夠得出對象實例一、二、四、6都具備GC Roots可達性,也就是存活對象,不能被GC回收的對象。而對於對象實例三、5直接雖然連通,但並無任何一個GC Roots與之相連,這即是GC Roots不可達的對象,這就是GC須要回收的垃圾對象。回過頭來看看最前面的實例,GcObject實例1和實例2雖然從引用計數雖然都不爲0,但從可達性算法來看,都是GC Roots不可達的對象。總之,對於對象之間循環引用的狀況,引用計數算法,則GC沒法回收這兩個對象,而可達性算法則能夠正確回收。   

  最後介紹一下堆區:由於對象的內存分配都是在堆區當中的.堆區的結構以下:

 

  堆區的結構如上圖.全部經過new建立的對象的內存都在堆中分配,堆被劃分爲新生代和舊生代,新生代又被進一步劃分爲Eden和Survivor區,最後Survivor由FromSpace和ToSpace組成,結構圖新生代。新建的對象都是用新生代分配內存,Eden空間不足的時候,會把存活的對象轉移到Survivor中,新生代大小能夠由-Xmn來控制,也能夠用-XX:SurvivorRatio來控制Eden和Survivor的比例。舊生代用於存放新生代中通過屢次垃圾回收 (也即Minor GC) 仍然存活的對象.也就是通過Cheney變種算法從From區拷貝到堆區的對象.這就是GC的垃圾回收機制.

  Java的垃圾回收器被稱爲自適應的,分帶的,中止-複製,標記-清掃式垃圾回收器.其緣由在於他的工做方式,所謂的自適應是它能夠根據不一樣方式切換到不一樣的工做狀態.分表明示在執行中止-複製狀態的時候,不一樣的內存塊會有不一樣的代數來判斷當前的對象是否存活.所謂的自適應的狀態就表示中止-複製和標記-清掃的狀態的切換.至於什麼是中止-複製,清掃-標記這兩個概念我就不進行多說了,Java編程思想上給了明確的概念.

相關文章
相關標籤/搜索