目錄
一.方法的概念及使用
1 什么是方法(method)
?2.方法定義
3 方法調用的執行過程
4 實參和形參的關系(重要)
5.沒有返回值的方法
二.方法重載
1.為什么需要方法重載
2.方法重載概念
3.方法簽名 ?
三.遞歸
1.遞歸的概念
2.遞歸執行過程分析
3.?遞歸練習
一.方法的概念及使用
1 什么是方法(method)
方法就是一個代碼片段 . 類似于 C 語言中的 " 函數 " 。方法存在的意義 ( 不要背 , 重在體會 ):
(1)是能夠模塊化的組織代碼 ( 當代碼規模比較復雜的時候 ).
(2)做到代碼被重復使用 , 一份代碼可以在多個位置使用 .
(3)讓代碼更好理解更簡單 .
(4)直接調用現有方法開發 , 不必重復造輪子 .
比如:現在要開發一款日歷,在日歷中經常要判斷一個年份是否為閏年,則有如下代碼:
int year = 1900;
if((0 == year % 4 && 0 != year % 100) || 0 == year % 400){System.out.println(year+"年是閏年");
}else{System.out.println(year+"年不是閏年");
}
?2.方法定義
?方法語法格式
// 方法定義
修飾符 返回值類型 方法名稱([參數類型 形參 ...]){方法體代碼;[return 返回值];
}
示例一 :實現一個函數,檢測一個年份是否為閏年
public class Method{
// 方法定義public static boolean isLeapYear(int year){if((0 == year % 4 && 0 != year % 100) || 0 == year % 400){return true;}else{return false;}}
}
示例二 : 實現一個兩個整數相加的方法
【 注意事項 】
(1)修飾符:現階段直接使用 public static 固定搭配
(2)返回值類型: 如果方法有返回值,返回值類型必須要與返回的實體類型一致,如果沒有返回值,必須寫成 void

(3)方法名字:采用小駝峰命名
(4)參數列表:如果方法沒有參數, () 中什么都不寫,如果有參數,需指定參數類型,多個參數之間使用逗號隔開

(5)方法體:方法內部要執行的語句
(6)在 java 當中,方法必須寫在類當中
(7)在 java 當中,方法不能嵌套定義
(8)在 java 當中,沒有方法聲明一說(不同于C語言)
3 方法調用的執行過程
【 方法調用過程 】
調用方法 --- > 傳遞參數 --- > 找到方法地址 --- > 執行被調方法的方法體 --- > 被調方法結束返回 --- > 回到主調方法繼續往下 執行
?
【 注意事項 】
(1)定義方法的時候 , 不會執行方法的代碼 . 只有調用的時候才會執行 .
(2)一個方法可以被多次調用 .
代碼示例 1 計算兩個整數相加
public class Method {public static void main(String[] args) {int a = 10;int b = 20;System.out.println("第一次調用方法之前");int ret = add(a, b);System.out.println("第一次調用方法之后");System.out.println("ret = " + ret);System.out.println("第二次調用方法之前");ret = add(30, 50);System.out.println("第二次調用方法之后");System.out.println("ret = " + ret);
}public static int add(int x, int y) {System.out.println("調用方法中 x = " + x + " y = " + y);return x + y;}
}
// 執行結果
一次調用方法之前
調用方法中 x = 10 y = 20
第一次調用方法之后
ret = 30
第二次調用方法之前
調用方法中 x = 30 y = 50
第二次調用方法之后
ret = 80
?代碼示例: 計算 1! + 2! + 3! + 4! + 5!
public class TestMethod {public static void main(String[] args) {int sum = 0;for (int i = 1; i <= 5; i++) {sum += fac(i);}System.out.println("sum = " + sum);}public static int fac(int n) {System.out.println("計算 n 的階乘中n! = " + n);int result = 1;for (int i = 1; i <= n; i++) {result *= i;}return result;}
}
// 執行結果
計算 n 的階乘中 n! = 1
計算 n 的階乘中 n! = 2
計算 n 的階乘中 n! = 3
計算 n 的階乘中 n! = 4
計算 n 的階乘中 n! = 5
sum = 153
使用方法 , 避免使用二重循環 , 讓代碼更簡單清晰
4 實參和形參的關系(重要)
- 方法的形參相當于數學函數中的自變量,比如:1 + 2 + 3 + … + n的公式為sum(n) =1/2(1+n)*n
- Java中方法的形參就相當于sum函數中的自變量n,用來接收sum函數在調用時傳遞的值的。形參的名字可以隨意取,對方法都沒有任何影響,形參只是方法在定義時需要借助的一個變量,用來保存方法在調用時傳遞過來的值。
public static int getSum(int N){ // N是形參return (1+N)*N / 2;
}
getSum(10); // 10是實參,在方法調用時,形參N用來保存10
getSum(100); // 100是實參,在方法調用時,形參N用來保存100
再比如:
public static int add(int a, int b){return a + b;
}
add(2, 3); // 2和3是實參,在調用時傳給形參a和b
注意: 在 Java 中,實參的值永遠都是拷貝到形參中,形參和實參本質是兩個實體
代碼示例 : 交換兩個整型變量
public class TestMethod {public static void main(String[] args) {int a = 10;int b = 20;swap(a, b);System.out.println("main: a = " + a + " b = " + b);
}
public static void swap(int x, int y) {int tmp = x;x = y;y = tmp;System.out.println("swap: x = " + x + " y = " + y);}
}
// 運行結果
swap: x = 20 y = 10
main: a = 10 b = 20
可以看到,在 swap 函數交換之后,形參 x 和 y 的值發生了改變,但是 main 方法中 a 和 b 還是交換之前的值,即沒有交換成功。
實參 a 和 b 是 main 方法中的兩個變量,其空間在 main 方法的棧 ( 一塊特殊的內存空間 ) 中,而形參 x 和 y 是 swap 方法中的兩個變量,x 和 y 的空間在 swap 方法運行時的棧中,因此:實參 a 和 b 與形參 x 和 y 是兩個沒有任何關聯性的變量, 在 swap 方法調用時,只是將實參 a 和 b 中的值拷貝了一份傳遞給了形參 x 和 y ,因此對形參 x 和 y 操作不會對實參 a 和 b產生任何影響。
注意:對于 基礎類型 來說 , 形參相當于實參的拷貝 . 即 傳值調用
int a = 10;
int b = 20;
int x = a;
int y = b;
int tmp = x;
x = y;
y = tmp;
可以看到 , 對 x 和 y 的修改 , 不影響 a 和 b。
【 解決辦法 】 : 傳引用類型參數 ( 例如數組來解決這個問題 )
public class TestMethod {public static void main(String[] args) {int[] arr = {10, 20};swap(arr);System.out.println("arr[0] = " + arr[0] + " arr[1] = " + arr[1]);
}
public static void swap(int[] arr) {int tmp = arr[0];arr[0] = arr[1];arr[1] = tmp;}
}
// 運行結果
arr[0] = 20 arr[1] = 10
5.沒有返回值的方法
方法的返回值是可選的 . 有些時候可以沒有的,沒有時返回值類型必須寫成 void
代碼示例:
class Test {public static void main(String[] args) {int a = 10;int b = 20;print(a, b);
}public static void print(int x, int y) {System.out.println("x = " + x + " y = " + y);}
}
另外 , 如剛才的交換兩個整數的方法 , 就是沒有返回值的 .
二.方法重載
1.為什么需要方法重載
public class TestMethod {public static void main(String[] args) {int a = 10;int b = 20;int ret = add(a, b);System.out.println("ret = " + ret);double a2 = 10.5;double b2 = 20.5;double ret2 = add(a2, b2);System.out.println("ret2 = " + ret2);
}public static int add(int x, int y) {return x + y;}
}// 編譯出錯
Test.java:13: 錯誤: 不兼容的類型: 從double轉換到int可能會有損失
double ret2 = add(a2, b2);^
由于參數類型不匹配 , 所以不能直接使用現有的 add 方法 .
一種比較簡單粗暴的解決方法如下:
public class TestMethod {public static void main(String[] args) {int a = 10;int b = 20;int ret = addInt(a, b);System.out.println("ret = " + ret);double a2 = 10.5;double b2 = 20.5;double ret2 = addDouble(a2, b2);System.out.println("ret2 = " + ret2);
}public static int addInt(int x, int y) {return x + y;
}public static double addDouble(double x, double y) {return x + y;}
}
上述代碼確實可以解決問題,但不友好的地方是:需要提供許多不同的方法名,而取名字本來就是讓人頭疼的事 情。那能否將所有的名字都給成 add 呢?
2.方法重載概念
在自然語言中,經常會出現“一詞多義”的現象,比如:“好人”。
在自然語言中,一個詞語如果有多重含義,那么就說該詞語被重載了,具體代表什么含義需要結合具體的場景。 在Java 中方法也是可以重載的。
在Java中,如果多個方法的名字相同,參數列表不同,則稱該幾種方法被重載了。
public class TestMethod {public static void main(String[] args) {add(1, 2); // 調用add(int, int)add(1.5, 2.5); // 調用add(double, double)add(1.5, 2.5, 3.5); // 調用add(double, double, double)
}public static int add(int x, int y) {return x + y;
}public static double add(double x, double y) {return x + y;
}public static double add(double x, double y, double z) {return x + y + z;}
}
?注意:
(1)方法名必須相同
(2)參數列表必須不同(參數的個數不同、參數的類型不同、類型的次序必須不同)
(3)與返回值類型是否相同無關
// 注意:兩個方法如果僅僅只是因為返回值類型不同,是不能構成重載的
public class TestMethod {public static void main(String[] args) {int a = 10;int b = 20;int ret = add(a, b);System.out.println("ret = " + ret);}public static int add(int x, int y) {return x + y;}public static double add(int x, int y) {return x + y;}
}
// 編譯出錯
Test.java:13: 錯誤: 已在類 Test中定義了方法 add(int,int)
public static double add(int x, int y) {^
1 個錯誤
(4)?編譯器在編譯代碼時,會對實參類型進行推演,根據推演的結果來確定調用哪個方法
3.方法簽名 ?
(1)在同一個作用域中不能定義兩個相同名稱的標識符。比如:方法中不能定義兩個名字一樣的變量,那 為什么類中就 可以定義方法名相同的方法呢?
(2)方法簽名即:經過編譯器編譯修改過之后方法最終的名字。具體方式: 方法全路徑名 + 參數列表 + 返回值類型,構成 方法完整的名字。
public class TestMethod {public static int add(int x, int y){return x + y;}public static double add(double x, double y){return x + y;}public static void main(String[] args) {add(1,2);add(1.5, 2.5);}
}
上述代碼經過編譯之后,然后使用 JDK 自帶的 javap 反匯編工具查看,具體操作:
(1)先對工程進行編譯生成
(2)在控制臺中進入到要查
(3)輸入: javap -v 字節碼
?
方法簽名中的一些特殊符號說明:
特殊字符 | 數據類型 |
V | void |
Z | boolean |
B | byte |
C | char |
S | short |
I | int |
J | long |
F | float |
D | double |
[ | 數組 ( 以 [ 開頭,配合其他的特殊字符,表述對應數據類型的數組,幾個 [ 表述幾維數組 ) |
L | 引用類型,以 L 開頭,以 ; 結尾,中間是引用類型的全類名 |
三.遞歸
生活中的故事
從前有坐山,山上有座廟,廟里有個老和尚給小和尚將故事,講的就是:
" 從前有座山,山上有座廟,廟里有個老和尚給小和尚講故事,講的就是:
" 從前有座山,山上有座廟 ..."
" 從前有座山 ……"
"
上面的兩個故事有個共同的特征: 自身中又包含了自己 ,該種思想在數學和編程中非常有用,因為有些時候,我們 遇到的問題直接并不好解決,但是發現將原問題拆分成其子問題之后,子問題與原問題有相同的解法,等子問題解 決之后,原問題就迎刃而解了 。
1.遞歸的概念
一個方法在執行過程中調用自身, 就稱為 "遞歸". 遞歸相當于數學上的 "數學歸納法", 有一個起始條件, 然后有一個遞推公式.
例如 , 我們求 N!
起始條件 : N = 1 的時候 , N! 為 1. 這個起始條件相當于遞歸的結束條件.
遞歸公式 : 求 N! , 直接不好求 , 可以把問題轉換成 N! => N * (N-1)!
遞歸的必要條件:
1. 將原問題劃分成其子問題,注意:子問題必須要與原問題的解法相同
2. 遞歸出口
代碼示例: 遞歸求 N 的階乘
public static void main(String[] args) {int n = 5;int ret = factor(n);System.out.println("ret = " + ret);
}
public static int factor(int n) {if (n == 1) {return 1;}return n * factor(n - 1); // factor 調用函數自身
}
// 執行結果
ret = 120
2.遞歸執行過程分析
遞歸的程序的執行過程不太容易理解 , 要想理解清楚遞歸 , 必須先理解清楚 " 方法的執行過程 ", 尤其是 " 方法執行結束之后, 回到調用位置繼續往下執行 ".
代碼示例 : 遞歸求 N 的階乘
public static void main(String[] args) {int n = 5;int ret = factor(n);System.out.println("ret = " + ret);
}
public static int factor(int n) {System.out.println("函數開始, n = " + n);if (n == 1) {System.out.println("函數結束, n = 1 ret = 1");return 1;}int ret = n * factor(n - 1);System.out.println("函數結束, n = " + n + " ret = " + ret);return ret;
}
// 執行結果
函數開始, n = 5
函數開始, n = 4
函數開始, n = 3
函數開始, n = 2
函數開始, n = 1
函數結束, n = 1 ret = 1
函數結束, n = 2 ret = 2
函數結束, n = 3 ret = 6
函數結束, n = 4 ret = 24
函數結束, n = 5 ret = 120
ret = 120
執行過程圖

遞歸比較耗費內存,,空間復雜度高
關于 " 調用棧 ":
方法調用的時候 , 會有一個 " 棧 " 這樣的內存空間描述當前的調用關系 . 稱為調用棧 .
每一次的方法調用就稱為一個 " 棧幀 ", 每個棧幀中包含了這次調用的參數是哪些 , 返回到哪里繼續執行等信息 . 后面我們借助 IDEA 很容易看到調用棧的內容 .
3.?遞歸練習
代碼示例1 按順序打印一個數字的每一位(例如 1234 打印出 1 2 3 4)
public static void print(int num) {if (num > 9) {print(num / 10);}System.out.println(num % 10);
}
?代碼示例2 遞歸求 1 + 2 + 3 + ... + 10
public static int sum(int num) {if (num == 1) {return 1;}return num + sum(num - 1);
}
代碼示例 3 寫一個遞歸方法,輸入一個非負整數,返回組成它的數字之和 . 例如,輸入 1729, 則應該返回1+7+2+9,它的和是 19
public static int sum(int num) {if (num < 10) {return num;}return num % 10 + sum(num / 10);
}
代碼示例 4 求斐波那契數列的第 N 項
斐波那契數列介紹 : https://baike.sogou.com/v267267.htm?
fromTitle=%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0%E5%88%97
public static int fib(int n) {if (n == 1 || n == 2) {return 1;}return fib(n - 1) + fib(n - 2);
}
當我們求 fib(40) 的時候發現 , 程序執行速度極慢 . 原因是進行了大量的重復運算
class Test {public static int count = 0; // 這個是類的成員變量. 后面會詳細介紹到.public static void main(String[] args) {System.out.println(fib(40));System.out.println(count);
}
public static int fib(int n) {if (n == 1 || n == 2) {return 1;}if (n == 3) {count++;}return fib(n - 1) + fib(n - 2);}
}
// 執行結果
102334155
39088169 // fib(3) 重復執行了 3 千萬次.
可以使用循環的方式來求斐波那契數列問題 , 避免出現冗余運算
public static int fib(int n) {int last2 = 1;int last1 = 1;int cur = 0;for (int i = 3; i <= n; i++) {cur = last1 + last2;
//這兩個式子不能互換last2 = last1;last1 = cur;}return cur;
}
此時程序的執行效率大大提高了