深刻理解 Java 方法

方法(有的人喜歡叫函數)是一段可重用的代碼段。html

:notebook: 本文已歸檔到:「blogjava

:keyboard: 本文中的示例代碼已歸檔到:「javacoregit

方法的使用

方法定義

方法定義語法格式:github

[修飾符] 返回值類型 方法名([參數類型 參數名]){
    ...
    方法體
    ...
    return 返回值;
}
複製代碼

示例:算法

public static void main(String[] args) {
    System.out.println("Hello World");
}
複製代碼

方法包含一個方法頭和一個方法體。下面是一個方法的全部部分:編程

  • 修飾符 - 修飾符是可選的,它告訴編譯器如何調用該方法。定義了該方法的訪問類型。
  • 返回值類型 - 返回值類型表示方法執行結束後,返回結果的數據類型。若是沒有返回值,應設爲 void。
  • 方法名 - 是方法的實際名稱。方法名和參數表共同構成方法簽名。
  • 參數類型 - 參數像是一個佔位符。當方法被調用時,傳遞值給參數。參數列表是指方法的參數類型、順序和參數的個數。參數是可選的,方法能夠不包含任何參數。
  • 方法體 - 方法體包含具體的語句,定義該方法的功能。
  • return - 必須返回聲明方法時返回值類型相同的數據類型。在 void 方法中,return 語句無關緊要,若是要寫 return,則只能是 return; 這種形式。

方法的調用

當程序調用一個方法時,程序的控制權交給了被調用的方法。當被調用方法的返回語句執行或者到達方法體閉括號時候交還控制權給程序。設計模式

Java 支持兩種調用方法的方式,根據方法是否有返回值來選擇。bash

  • 有返回值方法 - 有返回值方法一般被用來給一個變量賦值或代入到運算表達式中進行計算。
int larger = max(30, 40);
複製代碼
  • 無返回值方法 - 無返回值方法只能是一條語句。
System.out.println("Hello World");
複製代碼

遞歸調用

Java 支持方法的遞歸調用(即方法調用自身)。併發

注意:編程語言

  • 遞歸方法必須有明確的結束條件。
  • 儘可能避免使用遞歸調用。由於遞歸調用若是處理不當,可能致使棧溢出。

斐波那契數列(一個典型的遞歸算法)示例:

public class RecursionMethodDemo {
    public static int fib(int num) {
        if (num == 1 || num == 2) {
            return 1;
        } else {
            return fib(num - 2) + fib(num - 1);
        }
    }

    public static void main(String[] args) {
        for (int i = 1; i < 10; i++) {
            System.out.print(fib(i) + "\t");
        }
    }
}
複製代碼

方法參數

在 C/C++ 等編程語言中,方法的參數傳遞通常有兩種形式:

  • 值傳遞 - 值傳遞的參數被稱爲形參。值傳遞時,傳入的參數,在方法中的修改,不會在方法外部生效。
  • 引用傳遞 - 引用傳遞的參數被稱爲實參。引用傳遞時,傳入的參數,在方法中的修改,會在方法外部生效。

那麼,Java 中是怎樣的呢?

Java 中只有值傳遞。

示例一:

public class MethodParamDemo {
    public static void method(int value) {
        value =  value + 1;
    }
    public static void main(String[] args) {
        int num = 0;
        method(num);
        System.out.println("num = [" + num + "]");
        method(num);
        System.out.println("num = [" + num + "]");
    }
}
// Output:
// num = [0]
// num = [0]
複製代碼

示例二:

public class MethodParamDemo2 {
    public static void method(StringBuilder sb) {
        sb = new StringBuilder("B");
    }

    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder("A");
        System.out.println("sb = [" + sb.toString() + "]");
        method(sb);
        System.out.println("sb = [" + sb.toString() + "]");
        sb = new StringBuilder("C");
        System.out.println("sb = [" + sb.toString() + "]");
    }
}
// Output:
// sb = [A]
// sb = [A]
// sb = [C]
複製代碼

說明:

以上兩個示例,不管向方法中傳入的是基礎數據類型,仍是引用類型,在方法中修改的值,在外部都未生效。

Java 對於基本數據類型,會直接拷貝值傳遞到方法中;對於引用數據類型,拷貝當前對象的引用地址,而後把該地址傳遞過去,因此也是值傳遞。

擴展閱讀:

圖解 Java 中的參數傳遞

方法修飾符

前面提到了,Java 方法的修飾符是可選的,它告訴編譯器如何調用該方法。定義了該方法的訪問類型。

Java 方法有好幾個修飾符,讓咱們一一來認識一下:

訪問控制修飾符

訪問權限控制的等級,從最大權限到最小權限依次爲:

public > protected > 包訪問權限(沒有任何關鍵字)> private
複製代碼
  • public - 表示任何類均可以訪問;
  • 包訪問權限 - 包訪問權限,沒有任何關鍵字。它表示當前包中的全部其餘類均可以訪問,可是其它包的類沒法訪問。
  • protected - 表示子類能夠訪問,此外,同一個包內的其餘類也能夠訪問,即便這些類不是子類。
  • private - 表示其它任何類都沒法訪問。

static

static 修飾的方法被稱爲靜態方法。

靜態方法相比於普通的實例方法,主要有如下區別:

  • 在外部調用靜態方法時,可使用 類名.方法名 的方式,也可使用 對象名.方法名 的方式。而實例方法只有後面這種方式。也就是說,調用靜態方法能夠無需建立對象

  • 靜態方法在訪問本類的成員時,只容許訪問靜態成員(即靜態成員變量和靜態方法),而不容許訪問實例成員變量和實例方法;實例方法則無此限制。

靜態方法常被用於各類工具類、工廠方法類。

final

final 修飾的方法不能被子類覆寫(Override)。

final 方法示例:

public class FinalMethodDemo {
    static class Father {
        protected final void print() {
            System.out.println("call Father print()");
        };
    }

    static class Son extends Father {
        @Override
        protected void print() {
            System.out.println("call print()");
        }
    }

    public static void main(String[] args) {
        Father demo = new Son();
        demo.print();
    }
}
// 編譯時會報錯
複製代碼

說明:

上面示例中,父類 Father 中定義了一個 final 方法 print(),則其子類不能 Override 這個 final 方法,不然會編譯報錯。

default

JDK8 開始,支持在接口 Interface 中定義 default 方法。default 方法只能出如今接口 Interface

接口中被 default 修飾的方法被稱爲默認方法,實現此接口的類若是沒 Override 此方法,則直接繼承這個方法,再也不強制必須實現此方法。

default 方法語法的出現,是爲了既有的成千上萬的 Java 類庫的類增長新的功能, 且沒必要對這些類從新進行設計。 舉例來講,JDK8 中 Collection 類中有一個很是方便的 stream() 方法,就是被修飾爲 default,Collection 的一大堆 List、Set 子類就直接繼承了這個方法 I,沒必要再爲每一個子類都注意添加這個方法。

default 方法示例:

public class DefaultMethodDemo {
    interface MyInterface {
        default void print() {
            System.out.println("Hello World");
        }
    }


    static class MyClass implements MyInterface {}

    public static void main(String[] args) {
        MyInterface obj = new MyClass();
        obj.print();
    }
}
// Output:
// Hello World
複製代碼

abstract

abstract 修飾的方法被稱爲抽象方法,方法不能有實體。抽象方法只能出現抽象類中。

抽象方法示例:

public class AbstractMethodDemo {
    static abstract class AbstractClass {
        abstract void print();
    }

    static class ConcreteClass extends AbstractClass {
        @Override
        void print() {
            System.out.println("call print()");
        }
    }

    public static void main(String[] args) {
        AbstractClass demo = new ConcreteClass();
        demo.print();
    }

}
// Outpu:
// call print()
複製代碼

synchronized

synchronized 用於併發編程。synchronized 修飾的方法在一個時刻,只容許一個線程執行。

在 Java 的同步容器(Vector、Stack、HashTable)中,你會見到大量的 synchronized 方法。不過,請記住:在 Java 併發編程中,synchronized 方法並非一個好的選擇,大多數狀況下,咱們會選擇更加輕量級的鎖 。

特殊方法

Java 中,有一些較爲特殊的方法,分別使用於特殊的場景。

main 方法

Java 中的 main 方法是一種特殊的靜態方法,由於全部的 Java 程序都是由 public static void main(String[] args) 方法開始執行。

有不少新手雖然一直用 main 方法,殊不知道 main 方法中的 args 有什麼用。實際上,這是用來接收接收命令行輸入參數的。

示例:

public class MainMethodDemo {
    public static void main(String[] args) {
        for (String arg : args) {
            System.out.println("arg = [" + arg + "]");
        }
    }
}
複製代碼

依次執行

javac MainMethodDemo.java
java MainMethodDemo A B C
複製代碼

控制檯會打印輸出參數:

arg = [A]
arg = [B]
arg = [C]
複製代碼

構造方法

任何類都有構造方法,構造方法的做用就是在初始化類實例時,設置實例的狀態。

每一個類都有構造方法。若是沒有顯式地爲類定義任何構造方法,Java 編譯器將會爲該類提供一個默認構造方法。

在建立一個對象的時候,至少要調用一個構造方法。構造方法的名稱必須與類同名,一個類能夠有多個構造方法。

public class ConstructorMethodDemo {

    static class Person {
        private String name;

        public Person(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }

    public static void main(String[] args) {
        Person person = new Person("jack");
        System.out.println("person name is " + person.getName());
    }
}
複製代碼

注意,構造方法除了使用 public,也可使用 private 修飾,這種狀況下,類沒法調用此構造方法去實例化對象,這經常用於設計模式中的單例模式。

變參方法

JDK5 開始,Java 支持傳遞同類型的可變參數給一個方法。在方法聲明中,在指定參數類型後加一個省略號 ...。一個方法中只能指定一個可變參數,它必須是方法的最後一個參數。任何普通的參數必須在它以前聲明。

變參方法示例:

public class VarargsDemo {
    public static void method(String... params) {
        System.out.println("params.length = " + params.length);
        for (String param : params) {
            System.out.println("params = [" + param + "]");
        }
    }

    public static void main(String[] args) {
        method("red");
        method("red", "yellow");
        method("red", "yellow", "blue");
    }
}
// Output:
// params.length = 1
// params = [red]
// params.length = 2
// params = [red]
// params = [yellow]
// params.length = 3
// params = [red]
// params = [yellow]
// params = [blue]
複製代碼

finalize() 方法

finalize 在對象被垃圾收集器析構(回收)以前調用,用來清除回收對象。

finalize 是在 java.lang.Object 裏定義的,也就是說每個對象都有這麼個方法。這個方法在 GC 啓動,該對象被回收的時候被調用。

finalizer() 一般是不可預測的,也是很危險的,通常狀況下是沒必要要的。使用終結方法會致使行爲不穩定、下降性能,以及可移植性問題。

請記住:應該儘可能避免使用 finalizer()。千萬不要把它當成是 C/C++ 中的析構函數來用。緣由是:Finalizer 線程會和咱們的主線程進行競爭,不過因爲它的優先級較低,獲取到的 CPU 時間較少,所以它永遠也趕不上主線程的步伐。因此最後可能會發生 OutOfMemoryError 異常。

擴展閱讀:

下面兩篇文章比較詳細的講述了 finalizer() 可能會形成的問題及緣由。

覆寫和重載

覆寫(Override)是指子類定義了與父類中同名的方法,可是在方法覆寫時必須考慮到訪問權限,子類覆寫的方法不能擁有比父類更加嚴格的訪問權限。

子類要覆寫的方法若是要訪問父類的方法,可使用 super 關鍵字。

覆寫示例:

public class MethodOverrideDemo {
    static class Animal {
        public void move() {
            System.out.println("會動");
        }
    }
    static class Dog extends Animal {
        @Override
        public void move() {
            super.move();
            System.out.println("會跑");
        }
    }

    public static void main(String[] args) {
        Animal dog = new Dog();
        dog.move();
    }
}
// Output:
// 會動
// 會跑
複製代碼

方法的重載(Overload)是指方法名稱相同,但參數的類型或參數的個數不一樣。經過傳遞參數的個數及類型的不一樣能夠完成不一樣功能的方法調用。

注意:

重載必定是方法的參數不徹底相同。若是方法的參數徹底相同,僅僅是返回值不一樣,Java 是沒法編譯經過的。

重載示例:

public class MethodOverloadDemo {
    public static void add(int x, int y) {
        System.out.println("x + y = " + (x + y));
    }

    public static void add(double x, double y) {
        System.out.println("x + y = " + (x + y));
    }

    public static void main(String[] args) {
        add(10, 20);
        add(1.0, 2.0);
    }
}
// Output:
// x + y = 30
// x + y = 3.0
複製代碼

小結



參考資料

相關文章
相關標籤/搜索