文章目錄
- 方法概述
- 什么是方法?
- 方法的結構
- 方法使用
- 方法定義和呼叫
- 方法呼叫注意
- 方法三種呼叫格式
- 方法多載
- 方法多載前
- 方法多載后
- 多載練習
- 練習1
- 練習2
- 方法遞回
- 遞回常見應用
- 求n的階乘
- 第n個斐波那契數
- 青蛙跳臺階問題
- 漢諾塔問題
- 迷宮回溯問題
- 八皇后問題
- 代碼塊
- 代碼塊的作用
- 代碼塊分類
- 靜態代碼塊
- 非靜態代碼塊
- 類中對屬性可以賦值的位置
方法概述
什么是方法?
方法是將一組完成特定功能的代碼整合在一起,以達到簡化開發,減少代碼耦合,提高代碼復用性的結構,類似與C語言中的函式,
簡化開發:將完成特定功能的代碼封裝在方法內,當我們在呼叫所需要的方法時,無須關心方法內部的具體實作,只需要知道哪些方法可以完成我們的功能,可以使得開發人員更加專注于自己所要去實作的功能,
減少代碼耦合:好的程式設計是高內聚、低耦合的,將具有特定功能得代碼封裝在方法內,可以使得程式結構變得更加清晰,代碼的可讀性也更加好,降低了開發的復雜度,
提高代碼復用性:方法可以減少冗余的代碼,當我們在撰寫程式時,出現了大量冗余代碼時,應該考慮使用方法將其封裝在內,這樣每當我們需要使用相應功能時只需要呼叫相應的方法即可,實作了一個方法的多處使用,
方法的結構
[權限修飾符] [關鍵字] 回傳值型別 方法名([引數串列]) [throws 例外型別] {
方法體
}
- 權限修飾符,對應了4種權限,public、protected、預設(包權限)、private,【封裝內容】
- 關鍵字,修飾方法的關鍵字常用的有:static(靜態)、abstract(抽象)、final(斷子絕孫)、synchronized(同步)…
- 回傳值型別,可以是基本資料型別和參考型別
- 方法名,見名知意,小駝峰命名
- 引數串列,方法所需要的引數,包括資料型別和引數名,【注意】java中采用的值傳遞機制,基本資料型別傳遞的是變數的值,參考資料型別傳遞的是參考變數的hash地址值
- throws 例外型別,宣告方法可能拋出的例外,例外是程式執行中發生的不正常情況,例外在Java中也是類,所有例外的最高父類是Throwable,其下有兩個子類Error和Exception,
- 方法體,方法功能的具體實作,
// 例如: main方法
public static void main(String[] args) {
//...
}
tips:
- static修飾的方法屬于靜態方法,靜態方法中不能呼叫非靜態的方法和屬性
原因:
靜態方法屬于類不屬于物件(面向物件,目前了解),在類中,靜態的結構(static修飾的)隨著類的加載而加載,而非靜態結構屬于類的物件,只有在物件被創建后,才會在堆空間中創建,所以非靜態結構是晚于靜態結構加載的,因此,靜態方法中不能呼叫非靜態的方法和屬性,
方法使用
方法定義和呼叫
- 方法定義的先后順序無所謂,
- 方法定義在當中,方法不能嵌套定義,
- 方法定義關系平等,不能嵌套,
- 使用方法需要呼叫方法,
// 原來寫在main函式中的代碼, 可以寫在方法中
public class Demo01Method {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 20; j++) {
System.out.print('*'); // 不換行
}
System.out.println(); // 只換行不輸出
}
}
}
將上訴代碼封裝在方法中實作
public class Demo01Method {
public static void main(String[] args) {
printMethod(); //方法呼叫
}
// 方法定義
public static void printMethod(){
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 20; j++) {
System.out.print('*');
}
System.out.println();
}
}
}
方法呼叫注意
- 方法傳參時,實參的資料型別和個數要嚴格與方法定義中的引數串列匹配
// 方法定義
public void method(int a, double b){
//...
}
public void test(){
method(); // 錯誤, 沒有引數
method(1); // 錯誤, 引數個數不匹配
method(1.23,1); // 錯誤, 引數型別不匹配, double無法自動向下轉型
method(1,1.23); // 【正確】
}
-
方法只能有一個回傳值,使用return關鍵字來回傳值,
return的作用:1)結束當前方法 2)回傳方法的回傳值,
// 方法定義
public int method(int a, double b){
//...
return 1,2; // 錯誤
}
public int method(int a, double b){
//...
return 1;
return 2; // 錯誤, return會結束當前方法, 后面的代碼永遠執行不到,并且編譯也會報錯
}
public int method(int a, double b){
//...
return 0;
}
// 需要回傳多個值時,可以使用容器(陣列,集合)
- return的回傳值型別必須嚴格與定義的回傳值型別相同,
public int method(int a, double b){
//...
return 1.0; // 錯誤回傳值型別為int,return企圖回傳double
}
public int method(int a, double b){
return 0; // 正確
}
- 方法的呼叫會在堆疊空間開辟堆疊幀,方法內部的變數
方法三種呼叫格式
- 單獨呼叫
method(para1,...); - 列印呼叫
System.out.println(method(para1,...)); - 賦值呼叫
dataType var = method(para1,...);
public class Demo02MethodDefine {
// 方法實作求和
public static void main(String[] args){
// 單獨呼叫
getSum(10,20);
// 列印呼叫
System.out.println(getSum(10,20));
// 賦值呼叫
int res = getSum(10,20);
System.out.println(res);
}
public static int getSum(int x, int y){
return x + y;
}
}
方法的回傳值型別為void,只能單獨呼叫,不能列印或者賦值呼叫,因為沒有回傳值,
方法多載
方法多載(overload):指在同一個類中,允許存在一個以上的同名方法,只要它們的引數串列不同即可,與修飾符、引數的名稱和回傳值型別無關,
引數串列:引數個數不同,資料型別不同,多型別的順序不同,
多載方法呼叫:JVM通過方法的引數串列,呼叫不同的方法,
說明:對于繼承關系中的“多載”,個人認為子類繼承了父類的方法,也就是子類結構中隱式存在繼承的方法,對于這個方法的多載應該也是發生在本類中的,個人觀點,
方法多載前
在沒有方法多載之前,如果要需要對不同的資料進行功能大體相同的操作,需要定義多個不同名的方法,
例如:需要完成加法功能的方法,但是方法的引數可能是兩個整型,兩個浮點型或者是三個整型,四個浮點型… 那么就需要撰寫多個不同名的方法進行呼叫,但是這些方法所實作的核心功能都是進行加法計算,當引數串列變化很多時,方法的名字也就越來越多,對于實際開發非常不友好,
public class Demo01Method {
public static void main(String[] args){
// 這樣呼叫方法非常復雜,需要記住對應方法的名字,
System.out.println(sumInt1(10, 20));
System.out.println(sumInt2(10, 20, 30));
System.out.println(sumDouble1(11.1, 22.2));
System.out.println(sumDouble2(11.1, 22.2, 33.3, 44.4));
}
// 引數不同,但是功能大致相同的方法
public static int sumInt1(int a, int b){
return a + b;
}
public static int sumInt2(int a, int b,int c){
return a + b + c;
}
public static double sumDouble1(double a, double b){
return a + b;
}
public static double sumDouble2(double a, double b, double c, double d) {
return a + b + c + d;
}
}

方法多載后
有了方法多載,可以將這些功能相似,只是引數不同的方法命名為同一個方法名,在呼叫JVM會根據引數串列來呼叫對應的多載方法,這樣就簡化了開發的復雜度,
public class Demo01MethodOverload {
public static void main(String[] args) {
// 根據傳遞的引數不同,呼叫對應的多載方法
System.out.println(sum(10, 20));
System.out.println(sum(10, 20, 30));
System.out.println(sum(11.1, 22.2));
System.out.println(sum(11.1, 22.2, 33.3, 44.4));
}
// 方法名相同,引數串列不同
public static int sum(int a, int b) {
return a + b;
}
public static int sum(int a, int b, int c) {
return a + b + c;
}
public static double sum(double a, double b) {
return a + b;
}
public static double sum(double a, double b, double c, double d) {
return a + b + c + d;
}
}

多載練習
練習1
// 判斷哪些方法是多載關系,
public static void open(){} // 正確多載
public static void open(int a){} // 正確多載
static void open(int a,int b){} // 和第9行沖突
public static void open(double a,int b){} // 正確多載
public static void open(int a,double b){} // 和第7行沖突
public void open(int i,double d){} // 和第6行沖突
public static void OPEN(){} // 代碼正確,不會報錯,但這不是有效多載,名稱與第2行不同,區分大小寫
public static void open(int i,int j){} // 和第4行沖突

練習2
/*
定義一個方法用來顯示不同型別
shitf + F6所有修改所有相同字符
*/
public class Demo04MthodOverloadPrint {
public static void main(String[] args){
myPrinter((byte) 1);
myPrinter((short) 10);
myPrinter(100);
myPrinter(1000L);
myPrinter('A');
myPrinter(1.23F);
myPrinter(1.234);
myPrinter(100 == 200);
myPrinter("Hello,World!");
}
public static void myPrinter(byte num){
System.out.println(num);
}
public static void myPrinter(short num){
System.out.println(num);
}
public static void myPrinter(int num){
System.out.println(num);
}
public static void myPrinter(long num){
System.out.println(num);
}
public static void myPrinter(char ch){
System.out.println(ch);
}
public static void myPrinter(float fl){
System.out.println(fl);
}
public static void myPrinter(double df){
System.out.println(df);
}
public static void myPrinter(boolean truth){
System.out.println(truth);
}
public static void myPrinter(String str){
System.out.println(str);
}
}
方法遞回
方法遞回,即方法自己呼叫自己,遞回屬于一種演算法思想,其核心是將一個復雜的問題通過層層的遞回變成一個與原問題相似但規模更小的子問題,遞回往往可以通過少量的代碼來完成多此重復的計算,大大減少了程式的代碼量,
遞回需要有邊界條件,并且需要在程序中不斷向該條件靠近,這屬于遞程序,當滿足邊界條件后,則回傳上一層方法呼叫處,這屬于歸程序,
注意:遞回的實作一定要具備邊界條件,否則會造成堆疊溢位現象,
遞回的實作是利用了堆疊的資料結構特點,堆疊是后進先出(LAST IN FIRST OUT)的,也就是先進入堆疊結構的元素是最后出堆疊的,就向彈夾一樣,而在Java中每次呼叫方法都會在虛擬機堆疊的堆疊頂為該方法開辟一塊空間,當前堆疊幀結束后,會彈堆疊回傳上一級方法呼叫處,也就是新的堆疊頂,
遞回常見應用
常見的遞回應用有求n的階乘、求第n個斐波那契數、青蛙跳臺階、漢諾塔問題、以及更加復雜的迷宮問題和八皇后問題等,
求n的階乘
// 求階乘
public int fact(int n) {
while (n > 1) {
// 利用了求階乘公式
// n! = n * (n - 1)!
return n * fact(n - 1);
}
return n;
}
@Test
public void testFact() {
int fact = fact(10);
System.out.println(fact); // 3628800
}
第n個斐波那契數
斐波那契數列指的是這樣一個數列:
這個數列從第3項開始,每一項都等于前兩項之和,
@Test
public void testFib() {
System.out.println(fib(10)); // 55
}
public int fib(int n) {
// 利用了斐波那契數列的公式
// F[n]=F[n-1]+F[n-2](n>=2,F[0]=0,F[1]=1)
if (n < 1) return 0;
if (n < 2) return 1;
return fib(n - 1) + fib(n - 2);
}
青蛙跳臺階問題
一只青蛙一次可以跳上1級臺階,也可以跳上2級臺階,求該青蛙跳上一個n級的臺階總共有多少種跳法?
假設:
-
如果只有1級臺階那么只有1種跳法,即只跳一級臺階,
-
如果有2級臺階則有2種跳法,即一次跳一級臺階,或者直接跳兩級臺階,
-
如果有3級臺階則有3種跳法:
1)每次跳一級臺階,
2)第一次跳一級臺階,第二次跳兩級臺階,
3)第一次跳兩級臺階,第二次跳一級臺階,

其實青蛙跳到3級臺階時,只有2種狀態,要么是從第1級臺階通過2級跳跳上來,要么從第2級臺階通過1級跳跳上來,
那么假設有n級臺階時,青蛙跳到第n級臺階也只有2種狀態,從n-1級臺階通過1級跳到達,或者n-2級臺階通過2級跳到達,
這里應該可以看出來,青蛙跳臺階也是一個斐波那契數列問題,因此按照分析直接實作代碼即可,
@Test
public void testFrogJump() {
System.out.println(frogJump(3));
}
// 青蛙跳臺階
public int frogJump(int n) {
if (n < 1) return 0;
// 當n == 1時,只有一種跳法
if (n == 1) return 1;
// 當n == 2時,有兩種跳法
if (n == 2) return 2;
// 否則n級臺階就有 F(n-1) + F(n-2)種跳法
return frogJump(n - 1) + frogJump(n - 2);
}
漢諾塔問題
漢諾塔問題是指:一塊板上有三根柱子 A、B、C,A 柱上套有 n 個大小不等的圓盤,按照大的在下、小的在上的順序排列,要把這 n 個圓盤從 A 柱移動到 C 柱上,每次只能移動一個圓盤,移動程序可以借助 B 柱,但在任何時候,任何針上的圓盤都必須保持大盤在下,小盤在上,從鍵盤輸入需移動的圓盤個數,給出移動的程序,
假設只有一個盤,則直接將這個盤從A柱子移動到C柱子

假設有兩個盤子則需要將1號盤從a柱移動到b柱,2號盤從a柱移動到c柱,1號盤從b柱移動到c柱

如果是三個盤子,則需要把3號盤子放到C柱,而移動3號盤子之前需要將上面兩個盤子移走,并且這兩個盤子也不能在c柱上,所以第一步是需要將上面1、2號盤子移動到b柱上,然后將3號盤移動到c柱,接著將1、2號盤子移動到c柱上,我們可以將1、2號盤子看成一個整體,
第一步解決的就是將上面兩個盤子經由c柱移動到b柱,
第二步解決的就是將3號盤移動到c柱子(目標柱)
第三步解決的就是將b柱的兩個盤子經由a柱移動到c柱,則完成,
第一步的的移動又可以看成是2個盤子的移動,目標柱為b柱,而這又可以劃分為1號盤一個盤子的移動,目標柱為c柱,

我們不需要深入到每一個步驟,不管有多少個盤子,只需要看成2個部分:
-
最下面的盤子n(需要移動到目標柱)
-
上面的n-1個盤子(需要先從n盤子上經由目標柱移動到中間柱)
接著這n-1個盤子繼續拆分…
所以解決漢諾塔問題總體也可以看成三個步驟:
- 將a柱n-1個盤子經由c柱移動到到b柱
- 將a柱盤子移動到c柱
- 將b柱子n-1個盤子經由a柱移動到c柱
@Test
public void test() {
hanoi(3, 'a', 'b', 'c');
}
public static void hanoi(int n, char a, char b, char c) {
// 如果只有一個盤子直接從a柱移動到c柱
if (n == 1)
System.out.println(a + "--->" + c);
else {
// 三步
// 第一步: 將a柱n-1個盤子經由c柱移動到到b柱
hanoi(n - 1, a, c, b);
// 第二步: 將a柱盤子移動到c柱
System.out.println(a + "--->" + c);
// 第三步: 將b柱子n-1個盤子經由a柱移動到c柱
hanoi(n - 1, b, a, c);
}
}
/*
a--->c
a--->b
c--->b
a--->c
b--->a
b--->c
a--->c
*/
迷宮回溯問題
// 測驗
public static void main(String[] args) {
int[][] map = new int[8][7];
int row = map.length;
int col = map[0].length;
// 1. 設定地圖的墻
// 上下全部置為1
for (int i = 0; i < col; i++) {
map[0][i] = 1;
map[row - 1][i] = 1;
}
// 左右全部置為1
for (int i = 1; i < row - 1; i++) {
map[i][0] = 1;
map[i][col - 1] = 1;
}
// 設定地圖中的擋板
map[3][1] = 1;
map[3][2] = 1;
for (int[] arr : map) {
for (int i : arr) {
System.out.print(i + "\t");
}
System.out.println();
}
System.out.println("****************************");
getWay(map, 1, 1);
for (int[] arr : map) {
for (int i : arr) {
System.out.print(i + "\t");
}
System.out.println();
}
}
/**
* 約定:
* 0 表示未經過的點,
* 1 表示墻,
* 2 表示可以經過的點,
* -1 表示不能經過的點,
* <p>
* 思路: 下 --> 右 --> 上 --> 左
* 1. 默認先從(x,y)的位置【向下】探路, 當向下探路遇到 1 或 -1 則表示此路不同, 回傳false
* 2. 如果回傳false, 接著從(x,y)的位置【向右】探路, 當向下探路遇到 1 或 -1 則表示此路不同, 回傳false
* 3. 如果回傳false, 接著從(x,y)的位置【向上】探路, 當向下探路遇到 1 或 -1 則表示此路不同, 回傳false
* 4. 如果回傳false, 接著從(x,y)的位置【向左】探路, 當向下探路遇到 1 或 -1 則表示此路不同, 回傳false
* 當所有路徑均回傳false, 則將當前(x,y)置為3表示此路不通
* 只有當最終遞回到終點位置才會回傳true, 并回溯
*
* @param map 表示地圖
* @param x 表示當前點的橫坐標
* @param y 表示當前點的縱坐標
* @return 找到通路回傳true, 找不到回傳false
*/
public static boolean getWay(int[][] map, int x, int y) {
int row = map.length;
int col = map[0].length;
if (map[row - 2][col - 2] == 2) { // 當終點坐標被置為2, 遞回呼叫結束
return true;
} else {
if (map[x][y] == 0) { // 開始選擇路徑
map[x][y] = 2; // 先假設本次可以走通
if (getWay(map, x + 1, y)) { // 向下探路, 如果回傳值為true, 則本層遞回呼叫不再繼續往下直接, 而是直接回傳true, 如果回傳false, 則會向下繼續執行其它幾條if,直到抵達終點(終點坐標被置為2)或者本級遞回之下的子遞回嘗試所有路徑都回傳3表示無路可走,如果最侄訓傳false, 則會執行else陳述句將當前坐標置為-1,同時也回傳false到上一級呼叫處,上一級呼叫處也重復一樣的操作,
return true;
} else if (getWay(map, x, y + 1)) { // 向右探路
return true;
} else if (getWay(map, x - 1, y)) { // 向上探路
return true;
} else if (getWay(map, x, y - 1)) { // 向左探路
return true;
} else {
// 說明找不到通路
map[x][y] = -1; // 將該點置為-1
return false;
}
} else { // 如果map[x][y] != 0, 則map[x][y] == -1 || 1 || 2; 說明當前點要么不通, 要么已經走過, 則直接return false;
return false;
}
}
}

八皇后問題
感興趣的可以看這篇博客: 利用遞回和回溯解決八皇后問題
代碼塊
代碼塊的作用
- 用來初始化類、物件
- 代碼塊如果有修飾的話,只能使用static修飾
代碼塊分類
靜態代碼塊
- 內部可以有輸出陳述句,
- 隨著類的加載而執行,
- 只會在類加載的時候,執行唯一一次,
- 如果一個類中定義了多個靜態代碼塊,則按照宣告的先后順序執行,
- 靜態代碼塊內只能呼叫靜態的屬性,靜態的方法,不能呼叫非靜態的結構,
- 作用:初始化靜態屬性,
非靜態代碼塊
- 內部可以有輸出陳述句,
- 隨著物件的創建而執行,
- 每創建一個物件,就執行一次非靜態代碼塊,
- 非靜態代碼塊內可以呼叫靜態的屬性、靜態的方法,或非靜態的屬性、非靜態的方法,
- 作用:可以在創建物件時,對物件的屬性當進行初始化,
注意:靜態代碼塊的執行優先于非靜態代碼塊的執行,
類中對屬性可以賦值的位置
- 默認初始化,
- 顯示初始化,
- 構造器中初始化,
- 有了物件以后,可以通過"物件.屬性" 或 "物件.方法"的方式,進行賦值,
- 在代碼塊中賦值,
執行的先后順序: 1 --> 2 / 5 --> 3 --> 4
public class BlockTest {
public static void main(String[] args) {
// 靜態代碼塊隨著類的加載而執行
System.out.println("--------------------");
// 非靜態代碼塊隨著物件的創建而執行
BlockTest blockTest = new BlockTest();
// 非靜態代碼塊可以執行多次
BlockTest blockTest1 = new BlockTest();
}
int age;
// 靜態代碼快
static {
System.out.println("hello,static block");
}
// 非靜態代碼塊
{
age = 10;
System.out.println("hello,block");
}
}
// 由父及子,靜態先行

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/300731.html
標籤:java
上一篇:面向物件編程之封裝、繼承、多型

