重溫Java中的構造方法

今天寫代碼突然發現Java的構造方法也有不少說法呢,閒來無事,總結一下:
構造方法和實例方法的區別:
一、主要的區別在於三個方面:修飾符、返回值、命名
1、和實例方法一樣,構造器可以有任何訪問的修飾符,public、private、protected或者沒有修飾符   ,都可以對構造方法進行修飾。不同於實例方法的是構造方法不能有任何非訪問性質的修飾符修飾,例如static、final、synchronized、abstract等都不能修飾構造方法。
解釋:構造方法用於初始化一個實例對象,所以static修飾是沒有任何意義的;多個線程不會同時創建內存地址相同的同一個對象,所以synchronized修飾沒有意義;
構造方法不能被子類繼承,所以final和abstract修飾沒有意義。
2、返回類型是非常重要的,實例方法可以返回任何類型的值或者是無返回值(void),而構造方法是沒有返回類型的,void也不行。
3、至於命名就是構造方法與類名相同,當然了實例方法也可以與類名相同,但是習慣上我們爲實例方法命名的時候通常是小寫的,另一方面也是與構造方法區分開。
而構造方法與類名相同,所以首字母一般大寫。
下面看幾個例子熟悉一下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Sample {
      
    private int x;
  
    public Sample() { // 不帶參數的構造方法
        this(1);
    }
      
    public Sample(int x) { //帶參數的構造方法
        this.x=x;
    }
      
    public int Sample(int x) { //不是構造方法
        return x++;
    }
  
}
上面的例子即使不通過註釋我們也很容易能區分開的,再看下面一個例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Mystery {
    private String s;
      
    public void Mystery() { //不是構造方法
    s = "constructor";
    }
      
    void go() {
    System.out.println(s);
    }
      
    public static void main(String[] args) {
    Mystery m = new Mystery();
    m.go();
    }
}
程序執行的結果爲null,雖然說Mystery m = new Mystery();調用了Mystery 類的構造方法,但是public void Mystery()並不是構造方法,他只是一個普通的實例方法而已,那該類的構造方法哪去了呢?

二、說到這就得說一下java的默認構造方法
我們知道,java語言中規定每個類至少要有一個構造方法,爲了保證這一點,當用戶沒有給java類定義明確的構造方法的時候,java爲我們提供了一個默認的構造方法,這個構造方法沒有參數,修飾符是public並且方法體爲空。
其實默認的構造方法還分爲兩種,一種就是剛剛說過的隱藏的構造方法,另一種就是顯示定義的默認構造方法.
如果一個類中定義了一個或者多個構造方法,並且每一個構造方法都是帶有參數形式的,那麼這個類就沒有默認的構造方法,看下面的例子。
1
2
3
4
5
6
7
8
9
public class Sample1{}
  
public class Sample2{
    public Sample2(int a){System.out.println("My Constructor");}
}
  
public class Sample3{
    public Sample3(){System.out.println("My Default Constructor");}
}
上面的三個類中Sample1有一個隱式的默認構造方法,下列語句Sample1 s1=new Sample()合法;
Sample2沒有默認的構造方法,下列語句Sample2 s2=new Sample2()不合法,執行會編譯錯誤
Sample3有一個顯示的默認構造方法,所以以下語句Sample3  s3=new Sample3();合法。

三、實例方法和構造方法中this、super的使用.
"this"的用法
實例方法中可以使用this關鍵字,它指向正在執行方法的類的實例對象,當然static方法中是不可以使用this對象的,因爲靜態方法不屬於類的實例對象;而構造方法中同樣可以使用this關鍵字,構造器中的this是指向同一個對象中不同參數的另一個構造器。讓我們來看下面的一段代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Platypus {
    String name;
  
    Platypus(String input) {
        name = input;
    }
  
    Platypus() {
        this("John/Mary Doe");
    }
  
    public static void main(String args[]) {
        Platypus p1 = new Platypus("digger");
        Platypus p2 = new Platypus();
        System.out.println(p1.name + "----" + p2.name);
    }
}
上面的代碼中 類有兩個構造器,第一個構造器給類的成員name賦值,第二個構造器調用第一個構造器給類的成員name一個初始值Jonn/Mary Doe
所以程序執行結果:digger----John/Mary Doe
需要注意的兩個地方是:
1、構造方法中通過this關鍵字調用其他構造方法時,那麼這句代碼必須放在第一行,否則會編譯錯誤。
2、構造方法中只能通過this調用一次其他的構造方法。

"super"的用法:
實例方法和構造方法中的super關鍵字都用於去指向父類,實例方法中的super關鍵字是去調用父類當中的某個方法,看下面的代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class getBirthInfo {
    void getBirthInfo() {
        System.out.println("born alive.");
    }
}
  
class Platypus1 extends getBirthInfo
{
    void getBirthInfo() {
           System.out.println("hatch from eggs");
           System.out.println("a mammal normally is ");
           super.getBirthInfo();
      }
}
  
public class test1 {
    public static void main(String[] args) {
        Platypus1 p1=new Platypus1();
        p1.getBirthInfo();
    }
}
面的例子使用super.getBirthInfo();調用了它的父類的void getBirthInfo()方法。
構造器中使用super關鍵字調用父類中的構造器,看下面的代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class getBirthInfo {
    getBirthInfo(){
        System.out.println("auto");
    }
    void aa() {
        System.out.println("born alive.");
    }
}
  
class Platypus1 extends getBirthInfo
{
      Platypus1() {
        super();
        System.out.println("hatch from eggs");
        System.out.println("a mammal normally is ");
      }
}
  
public class test1 {
    public static void main(String[] args) {
        Platypus1 p1=new Platypus1();
    }
}
執行了代碼我們就會看到構造器中的super調用了父類的構造方法。

類的繼承機制使得子類可以調用父類的功能,下面介紹類在繼承關係的初始化順序問題

請看實例1:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class SuperClass
{
    SuperClass()
    {
        System.out.println("SuperClass constructor");
    }
}
public class SubClass extends SuperClass {
    SubClass()
    {
        System.out.println("SubClass constructor");
    }
    public static void main(String[] args) {
        SubClass sub = new SubClass();
    }
}
執行結果:SuperClass constructor
    SubClass constructor
代碼中我們只實例化子類一個對象,但從執行結果上看程序一開始並不是運行子類的構造方法,而是先執行父類的默認構造方法,然後再執行子類的構造方法.所以我們在實例化子類對象時,程序會先調用父類的默認構造方法,然後再執行子類的構造方法。

再看實例2:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class SuperClass
{
    SuperClass(String str)
    {
    System.out.println("Super with a string.");
    }
}
public class SubClass extends SuperClass
{
    SubClass(String str)
    {
    System.out.println("Sub with a string.");
    }
      
    public static void main(String[] args)
    {
    SubClass sub = new SubClass("sub");
    }
}
此程序在JDK下不能編譯成功,因爲我們在實例化子類對象的時候會先調用其父類默認的構造方法,但是它的父類沒有默認的構造方法,所以不能編譯成功。
解決辦法:
1、在父類中加一個顯示的默認構造方法
2、在子類的構造方法中加一句super(str)並且必須在構造器的第一句。
兩個辦法都可以解決程序編譯的問題,但是執行結果是不一樣的.
第一種執行結果爲:Sub with a string.
第二種執行結果爲:Super with a string. 
                      Sub with a string.     第二種方法即使父類中有顯示的默認構造方法也不會被調用。

再看實例三:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class One
{
    One(String str)
    {
    System.out.println(str);
    }
}
class Two
{
    One one_1 = new One("one-1");
    One one_2 = new One("one-2");
    One one_3 = new One("one-3");
    Two(String str)
    {
    System.out.println(str);
    }
}
public class Test
{
    public static void main(String[] args)
    {
    System.out.println("Test main() start");
    Two two = new Two("two");
    }
}
執行結果:
Test main() start
one-1
one-2
one-3
two
我們在main方法中實例了一個Two的對象,但是程序在實例Two對象時並沒有先調用Two的構造方法,而是先初始化Two類的成員變量,Two類中有三個成員變量,他們都是One類的對象,所以要依次執行One類的構造方法,然後再初始化Two類的對象。
既在實例化類的對象時,類中的成員變量會首先進行初始化,如果其中的成員變量有對象,那麼它們也會按照順序執行初始化工作。在所有類成員初始化完成後,才調用對象所在類的構造方法創建對象。構造方法作用就是初始化。

再看實例四:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class One
    {
    One(String str)
    {
    System.out.println(str);
    }
}
class Two
{
    One one_1 = new One("one-1");
    One one_2 = new One("one-2");
    static One one_3 = new One("one-3");
    Two(String str)
    {
    System.out.println(str);
    }
}
public class Test
{
    public static void main(String[] args)
    {
    System.out.println("Test main() start");
    Two two_1 = new Two("two-1");
    System.out.println("------------");
    Two two_2 = new Two("two-2");
    }
}
執行結果:
Test main() start
one-3
one-1
one-2
two-1
------------
one-1
one-2
two-2
結論:如果一個類中有靜態對象,那麼他會在非靜態對象初始化前進行初始化,但只初始化一次。而非靜態對象每次調用時都要初始化。

再看實例五:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class One
{
    One(String str)
    {
    System.out.println(str);
    }
}
class Two
{
    One one_1 = new One("one-1");
    One one_2 = new One("one-2");
    static One one_3 = new One("one-3");
    Two(String str)
    {
    System.out.println(str);
    }
}
public class Test
{
    static Two two_3 = new Two("two-3");
    public static void main(String[] args)
    {
    System.out.println("Test main() start");
    Two two_1 = new Two("two-1");
    System.out.println("------------");
    Two two_2 = new Two("two-2");
    }
}
執行結果:
one-3
one-1
one-2
two-3
Test main() start
one-1
one-2
two-1
------------
one-1
one-2
two-2
結論:程序中主類的靜態變量會在main()方法執行前初始化。結果中只輸出了一次one-3,這也說明:如果一個類中有靜態對象,那麼它會在非靜態對象前初始化,但只初始化一次。非靜態對象每次調用時都要初始化。

總結初始化順序:
  1.主類的靜態成員首先初始化。 
  2.主類的父類的構造方法被調用。 
  3.主類的非靜態對象(變量)初始化。 
  4.調用主類的構造方法。 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章