方法(有的人喜歡叫函數(shù))是一段可重用的代碼段。
?? 本文已歸檔到:「javacore」
?? 本文中的示例代碼已歸檔到:「javacore」
1. 方法的使用
1.1. 方法定義
方法定義語法格式:
[修飾符] 返回值類型 方法名([參數(shù)類型 參數(shù)名]){
...
方法體
...
return 返回值;
}
示例:
public static void main(String[] args) {
System.out.println("Hello World");
}
方法包含一個方法頭和一個方法體。下面是一個方法的所有部分:
- 修飾符 - 修飾符是可選的,它告訴編譯器如何調(diào)用該方法。定義了該方法的訪問類型。
- 返回值類型 - 返回值類型表示方法執(zhí)行結(jié)束后,返回結(jié)果的數(shù)據(jù)類型。如果沒有返回值,應設為 void。
- 方法名 - 是方法的實際名稱。方法名和參數(shù)表共同構(gòu)成方法簽名。
- 參數(shù)類型 - 參數(shù)像是一個占位符。當方法被調(diào)用時,傳遞值給參數(shù)。參數(shù)列表是指方法的參數(shù)類型、順序和參數(shù)的個數(shù)。參數(shù)是可選的,方法可以不包含任何參數(shù)。
- 方法體 - 方法體包含具體的語句,定義該方法的功能。
-
return - 必須返回聲明方法時返回值類型相同的數(shù)據(jù)類型。在 void 方法中,return 語句可有可無,如果要寫 return,則只能是
return;這種形式。
1.2. 方法的調(diào)用
當程序調(diào)用一個方法時,程序的控制權(quán)交給了被調(diào)用的方法。當被調(diào)用方法的返回語句執(zhí)行或者到達方法體閉括號時候交還控制權(quán)給程序。
Java 支持兩種調(diào)用方法的方式,根據(jù)方法是否有返回值來選擇。
- 有返回值方法 - 有返回值方法通常被用來給一個變量賦值或代入到運算表達式中進行計算。
int larger = max(30, 40);
- 無返回值方法 - 無返回值方法只能是一條語句。
System.out.println("Hello World");
遞歸調(diào)用
Java 支持方法的遞歸調(diào)用(即方法調(diào)用自身)。
?? 注意:
- 遞歸方法必須有明確的結(jié)束條件。
- 盡量避免使用遞歸調(diào)用。因為遞歸調(diào)用如果處理不當,可能導致棧溢出。
斐波那契數(shù)列(一個典型的遞歸算法)示例:
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");
}
}
}
2. 方法參數(shù)
在 C/C++ 等編程語言中,方法的參數(shù)傳遞一般有兩種形式:
- 值傳遞 - 值傳遞的參數(shù)被稱為形參。值傳遞時,傳入的參數(shù),在方法中的修改,不會在方法外部生效。
- 引用傳遞 - 引用傳遞的參數(shù)被稱為實參。引用傳遞時,傳入的參數(shù),在方法中的修改,會在方法外部生效。
那么,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]
說明:
以上兩個示例,無論向方法中傳入的是基礎數(shù)據(jù)類型,還是引用類型,在方法中修改的值,在外部都未生效。
Java 對于基本數(shù)據(jù)類型,會直接拷貝值傳遞到方法中;對于引用數(shù)據(jù)類型,拷貝當前對象的引用地址,然后把該地址傳遞過去,所以也是值傳遞。
擴展閱讀:
3. 方法修飾符
前面提到了,Java 方法的修飾符是可選的,它告訴編譯器如何調(diào)用該方法。定義了該方法的訪問類型。
Java 方法有好幾個修飾符,讓我們一一來認識一下:
3.1. 訪問控制修飾符
訪問權(quán)限控制的等級,從最大權(quán)限到最小權(quán)限依次為:
public > protected > 包訪問權(quán)限(沒有任何關鍵字)> private
-
public- 表示任何類都可以訪問; -
包訪問權(quán)限- 包訪問權(quán)限,沒有任何關鍵字。它表示當前包中的所有其他類都可以訪問,但是其它包的類無法訪問。 -
protected- 表示子類可以訪問,此外,同一個包內(nèi)的其他類也可以訪問,即使這些類不是子類。 -
private- 表示其它任何類都無法訪問。
3.2. static
被 static 修飾的方法被稱為靜態(tài)方法。
靜態(tài)方法相比于普通的實例方法,主要有以下區(qū)別:
在外部調(diào)用靜態(tài)方法時,可以使用
類名.方法名的方式,也可以使用對象名.方法名的方式。而實例方法只有后面這種方式。也就是說,調(diào)用靜態(tài)方法可以無需創(chuàng)建對象。靜態(tài)方法在訪問本類的成員時,只允許訪問靜態(tài)成員(即靜態(tài)成員變量和靜態(tài)方法),而不允許訪問實例成員變量和實例方法;實例方法則無此限制。
靜態(tài)方法常被用于各種工具類、工廠方法類。
3.3. 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 方法,否則會編譯報錯。
3.4. default
JDK8 開始,支持在接口 Interface 中定義 default 方法。default 方法只能出現(xiàn)在接口 Interface 中。
接口中被 default 修飾的方法被稱為默認方法,實現(xiàn)此接口的類如果沒 Override 此方法,則直接繼承這個方法,不再強制必須實現(xiàn)此方法。
default 方法語法的出現(xiàn),是為了既有的成千上萬的 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
3.5. abstract
被 abstract 修飾的方法被稱為抽象方法,方法不能有實體。抽象方法只能出現(xiàn)抽象類中。
抽象方法示例:
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()
3.6. synchronized
synchronized 用于并發(fā)編程。被 synchronized 修飾的方法在一個時刻,只允許一個線程執(zhí)行。
在 Java 的同步容器(Vector、Stack、HashTable)中,你會見到大量的 synchronized 方法。不過,請記住:在 Java 并發(fā)編程中,synchronized 方法并不是一個好的選擇,大多數(shù)情況下,我們會選擇更加輕量級的鎖 。
4. 特殊方法
Java 中,有一些較為特殊的方法,分別使用于特殊的場景。
4.1. main 方法
Java 中的 main 方法是一種特殊的靜態(tài)方法,因為所有的 Java 程序都是由 public static void main(String[] args) 方法開始執(zhí)行。
有很多新手雖然一直用 main 方法,卻不知道 main 方法中的 args 有什么用。實際上,這是用來接收接收命令行輸入?yún)?shù)的。
示例:
public class MainMethodDemo {
public static void main(String[] args) {
for (String arg : args) {
System.out.println("arg = [" + arg + "]");
}
}
}
依次執(zhí)行
javac MainMethodDemo.java
java MainMethodDemo A B C
控制臺會打印輸出參數(shù):
arg = [A]
arg = [B]
arg = [C]
4.2. 構(gòu)造方法
任何類都有構(gòu)造方法,構(gòu)造方法的作用就是在初始化類實例時,設置實例的狀態(tài)。
每個類都有構(gòu)造方法。如果沒有顯式地為類定義任何構(gòu)造方法,Java 編譯器將會為該類提供一個默認構(gòu)造方法。
在創(chuàng)建一個對象的時候,至少要調(diào)用一個構(gòu)造方法。構(gòu)造方法的名稱必須與類同名,一個類可以有多個構(gòu)造方法。
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());
}
}
注意,構(gòu)造方法除了使用 public,也可以使用 private 修飾,這種情況下,類無法調(diào)用此構(gòu)造方法去實例化對象,這常常用于設計模式中的單例模式。
4.3. 變參方法
JDK5 開始,Java 支持傳遞同類型的可變參數(shù)給一個方法。在方法聲明中,在指定參數(shù)類型后加一個省略號 ...。一個方法中只能指定一個可變參數(shù),它必須是方法的最后一個參數(shù)。任何普通的參數(shù)必須在它之前聲明。
變參方法示例:
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]
4.4. finalize() 方法
finalize 在對象被垃圾收集器析構(gòu)(回收)之前調(diào)用,用來清除回收對象。
finalize 是在 java.lang.Object 里定義的,也就是說每一個對象都有這么個方法。這個方法在 GC 啟動,該對象被回收的時候被調(diào)用。
finalizer() 通常是不可預測的,也是很危險的,一般情況下是不必要的。使用終結(jié)方法會導致行為不穩(wěn)定、降低性能,以及可移植性問題。
請記?。簯摫M量避免使用 finalizer()。千萬不要把它當成是 C/C++ 中的析構(gòu)函數(shù)來用。原因是:Finalizer 線程會和我們的主線程進行競爭,不過由于它的優(yōu)先級較低,獲取到的 CPU 時間較少,因此它永遠也趕不上主線程的步伐。所以最后可能會發(fā)生 OutOfMemoryError 異常。
擴展閱讀:
下面兩篇文章比較詳細的講述了 finalizer() 可能會造成的問題及原因。
5. 覆寫和重載
覆寫(Override)是指子類定義了與父類中同名的方法,但是在方法覆寫時必須考慮到訪問權(quán)限,子類覆寫的方法不能擁有比父類更加嚴格的訪問權(quán)限。
子類要覆寫的方法如果要訪問父類的方法,可以使用 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)是指方法名稱相同,但參數(shù)的類型或參數(shù)的個數(shù)不同。通過傳遞參數(shù)的個數(shù)及類型的不同可以完成不同功能的方法調(diào)用。
?? 注意:
重載一定是方法的參數(shù)不完全相同。如果方法的參數(shù)完全相同,僅僅是返回值不同,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
6. 小結(jié)
