數(shù)組中你可能不知道的一些事,數(shù)組和可變參數(shù)和重載方法的坑

本文總共13700字,估計閱讀時間34分鐘。如果你已經(jīng)會了一部分內(nèi)容,請查看文末數(shù)組常見問題。

本文屬于老薛原創(chuàng)內(nèi)容,轉(zhuǎn)載請注明出處:http://www.itdecent.cn/p/0dfca97cc728

數(shù)組

一、本章內(nèi)容知識點(diǎn)概括

本文總共13700字,估計閱讀時間34分鐘。如果你已經(jīng)會了一部分內(nèi)容,請查看文末數(shù)組常見問題。

碼歌數(shù)組章節(jié)概括

二、數(shù)組基礎(chǔ)知識

2-1:什么是數(shù)組

數(shù)組本質(zhì)就是用來存儲一組數(shù)的器皿。

2-1-1:數(shù)組的定義

在內(nèi)存中開辟一塊連續(xù)的內(nèi)存區(qū)域,用來存儲相同數(shù)據(jù)類型有序集合。

PS:這里有兩點(diǎn)我們需要注意我們在后續(xù)章節(jié)中會發(fā)現(xiàn),現(xiàn)在簡單做個普及:

  • 相同數(shù)據(jù)類型 :數(shù)組中存儲的數(shù)據(jù)是相同的數(shù)據(jù)類型數(shù)據(jù),不能出現(xiàn)多種數(shù)據(jù)類型,但是引用類型(Object)除外。這里大家可以思考一下為什么這么說?
  • 有序 : 數(shù)組存儲元素在內(nèi)存層面上講是有序的,因?yàn)槭沁B續(xù)的存儲空間;另一方面數(shù)組獲取值是通過索引去實(shí)現(xiàn),索引也是有序的。

2-2: 數(shù)組的定義以及賦值

2-2-1:數(shù)組的定義

2-2-1-1:數(shù)組定義第一種方式:
public class ArraysTest01 {
    public static void main(String[] args) {
        //聲明一個數(shù)組
        int[] arrs ;// 聲明了一個int類型的數(shù)組
    }
}

PS小結(jié):定義格式 數(shù)據(jù)類型 [] 變量名,例子 int [] arrs ;

2-2-1-2:數(shù)組定義第二種方式(不推薦使用):
public class ArraysTest01 {
    public static void main(String[] args) {
        //第二種聲明方式
        String strs[];// 聲明一個String類型的數(shù)組
    }
}

PS小結(jié): 定義格式 數(shù)據(jù)類型 變量名[],例子int arrs[]

2-2-1-3:考慮為什么第二種方式不推薦

數(shù)組本質(zhì)上也是一種數(shù)據(jù)類型,所以第一種寫法更能體現(xiàn)這種關(guān)系。所以很多語言中對于數(shù)組的定義,第二種寫法編譯都是錯誤的,比如在C#中是無法使用第二種聲明方式的。

2-2-2:數(shù)組的初始化

當(dāng)聲明一個數(shù)組之后,我們需要使用時,本質(zhì)上和使用局部變量是一樣的,我們無法將一個未初始化的數(shù)組變量直接使用,所以需要初始化動作

2-2-2-1:靜態(tài)初始化
2-2-2-1-1:第一種方式
public class ArraysTest02 {
    public static void main(String[] args) {
        //1:靜態(tài)初始化:
        // 會在存儲中開辟存儲3個字符串對象的數(shù)組,
        // 并且這個三個元素的值也確定了分別是java、htlm、css。
        String[] strs = new String[]{"java","html","css"};
        System.out.println("查看當(dāng)前數(shù)組的長度:"+strs.length);
    }
}

輸出內(nèi)容:

查看當(dāng)前數(shù)組的長度是:3

strs.length用來顯示當(dāng)前數(shù)組的元素個數(shù)或者叫數(shù)組長度

PS小結(jié):

類型 結(jié)構(gòu) 例子
結(jié)構(gòu) type[] type_names = new type[]{e1,e2,....} String[] strs = new String[]{"java","html","css"}
2-2-2-1-2:第二種方式
public class ArraysTest02 {
    public static void main(String[] args) {
        //1:靜態(tài)初始化第二種方式:
        //會開辟存儲三個字符串對象的字符串?dāng)?shù)組
        String[] names = {"張三","李四","老薛"};
        System.out.println("names數(shù)組的長度:"+names.length);
    }
}

輸出內(nèi)容:

names數(shù)組的長度:3

PS小結(jié):

類型 結(jié)構(gòu) 例子
結(jié)構(gòu) type[] type_names = {e1,e2,....} String[] strs = {"java","html","css"}
2-2-2-2:動態(tài)初始化
public class ArraysTest02 {
    public static void main(String[] args) {
         //3:動態(tài)初始化:
   // 在內(nèi)存中開辟一個存放5個數(shù)據(jù)的int類型的數(shù)組 將地址賦值給arrs變量存放
        int[] arrs = new int[5];
        //4:打印輸出數(shù)組
        System.out.println(arrs);
    }
}

輸出內(nèi)容:

[I@6bdf28bb

PS小結(jié):

打印內(nèi)容 含義
[ 以后遇到對象輸出時,"["開頭,證明當(dāng)前對象是一個數(shù)組類型
I 代表當(dāng)前數(shù)組的類型是int類型
@ 自動拼接
6bdf28bb 當(dāng)前數(shù)組對象的hash值

2-2-3:常見錯誤

  • 聲明和初始化數(shù)組時,忘記指定長度
int[] arrs = new int[]; //編譯錯誤
  • 重新給數(shù)組賦值時,不能通過{}指定,語法不允許
int[] arrs = new int[4];
arrs = {1,20,23};//語法不允許
  • 聲明數(shù)組時,小心數(shù)組的長度,出現(xiàn)越界異常
int[] arrs = new int[4];
System.out.println(arrs[5]);  //java.lang.ArrayIndexOutOfBoundsException異常

2-3:數(shù)組常見屬性

2-3-1: 數(shù)組length屬性

數(shù)組在聲明賦值時,都需要指定當(dāng)前數(shù)組的長度,通過length屬性獲取當(dāng)前數(shù)組的長度。

public class ArraysTest03 {
    public static void main(String[] args) {
        //1:聲明一個int類型的數(shù)組
        int[] arrs = new int[5];//聲明了一個存放5個數(shù)據(jù)的int類型的數(shù)組 名字叫arrs
        //2:查看數(shù)組中的元素
        System.out.println("查看數(shù)組中的元素:"+arrs[3]);
        //3:獲取當(dāng)前數(shù)組的長度
        System.out.println("獲取數(shù)組的長度:"+arrs.length);
     }
}

2-3-2:數(shù)組的索引屬性

數(shù)組中的元素是有序說的有序稱是索引有序。
數(shù)組中存儲的元素,都存在一個下標(biāo),我們可以通過下標(biāo)去獲取、修改以及添加元素到數(shù)組中去。一定要注意數(shù)組的索引是從0開始的,從length-1結(jié)束。數(shù)組的索引是[0,length-1]

public class ArraysTest03 {
    public static void main(String[] args) {
        //1:聲明一個int類型的數(shù)組
        int[] arrs = new int[5];
        //2:查看數(shù)組中的元素
        System.out.println("查看數(shù)組中的元素:"+arrs[3]);
        //3:獲取當(dāng)前數(shù)組的長度
        System.out.println("獲取數(shù)組的長度:"+arrs.length);
        //4:通過索引獲取數(shù)組中第5個元素的值
        System.out.println("查看數(shù)組中第5個元素的值:"+arrs[4]);
        //5:通過索引改變對應(yīng)位置上的元素值
        arrs[4] = 10;
        System.out.println("索引是4的元素,也就是第5個元素的值是:"+arrs[4]);
    }
}

2-3-3:內(nèi)存分析

2-3-3-1:代碼實(shí)例:
public class ArraysTest05 {
    public static void main(String[] args) {
        //1:聲明一個存放5個int類型元素的數(shù)組
        int[] arrs = new int[5];
        //2:指定數(shù)組索引是0的,也就是第一個元素的值是10
        arrs[0] = 10;
        //3:獲取索引是1的,也就是第2個元素的值
        System.out.println("查看第2個元素的值是:"+arrs[1]);
    }
}
2-3-3-2:內(nèi)存展示01
第一步

ps:第一步,創(chuàng)建int數(shù)組對象時,內(nèi)存結(jié)構(gòu)

  • 1>、會在堆內(nèi)存當(dāng)中開辟5個長度的連續(xù)的存儲空間,用來存儲int類型的元素。
  • 2>、由于當(dāng)前數(shù)組的類型是int類型,所以每個存儲空間的元素都是int的默認(rèn)值0填充。如果是String類型則默認(rèn)值為null。
  • 3>、將堆內(nèi)存的地址付給arrs變量存儲
2-3-3-3:內(nèi)存展示02
第二步

ps:第二步,將索引是1的元素的位置改為10,注意索引是從0開始,length-1結(jié)束

在按照元素的索引獲取元素值時,注意圖中的索引,我們都是安裝元素的索引對于數(shù)組中的元素進(jìn)行獲取和修改。

2-3-3-4:為什么說數(shù)組是不可變的
2-3-3-4-1:測試用例
public class ArraysTest05 {
    public static void main(String[] args) {
        //1:聲明一個存放5個int類型元素的數(shù)組
        int[] arrs = new int[5];
        //2:指定數(shù)組索引是0的,也就是第一個元素的值是10
        arrs[0] = 10;
        //3:獲取索引是1的,也就是第2個元素的值
        System.out.println("查看第2個元素的值是:"+arrs[1]);
        //4:重新賦值一個新的數(shù)組 這里是改變了數(shù)組長度嗎?并沒有
        arrs = new int[7];
        //5:給arrs第6個元素賦值
        arrs[5] = 33;
        //6:輸出索引是5的元素及第6個元素
        System.out.println(arrs[5]);
    }
}

2-3-3-4-2:內(nèi)存分析
內(nèi)存分析
2-3-3-4-3:結(jié)論

結(jié)論:數(shù)組一旦聲明,其長度是不可變得,我們通常說的改變長度其實(shí)都是通過重新定義一個新的數(shù)組,改變局部變量的引用而已。

2-4: 針對于數(shù)組的CRUD

2-4-1:數(shù)組的修改以及獲取元素

2-4-1-1:獲取元素(通過索引)
public class ArraysTest06 {
    public static void main(String[] args) {
        //1:聲明一個存放3個String字符串對象的數(shù)組
        String[] strs = new String[3];
        //2:獲取strs數(shù)組中的第一個元素
        System.out.println("strs數(shù)組中的第一個元素是:"+strs[0]);
    }
}

? PS:輸出內(nèi)容

strs數(shù)組中的第一個元素是:null
2-4-1-2:修改元素(通過索引)
public class ArraysTest06 {
    public static void main(String[] args) {
        //1:聲明一個存放3個String字符串對象的數(shù)組
        String[] strs = new String[3];
        //2:修改數(shù)組的第一個元素的值
        strs[0] = "老薛好帥";
        //3:獲取strs數(shù)組中的第一個元素
        System.out.println("strs數(shù)組中的第一個元素是:"+strs[0]);
    }
}

PS:輸出內(nèi)容

strs數(shù)組中的第一個元素是:老薛好帥

2-4-2:數(shù)組添加元素和刪除元素的細(xì)節(jié)

這里我們暫且不細(xì)述添加和刪除元素的細(xì)節(jié),在后續(xù)章節(jié)再做介紹,但是這里我們給一點(diǎn)思路,刪除和添加元素時需要查看原來的數(shù)組的大小,一般情況下我們都會返回一個新的數(shù)組。

2-5:數(shù)組的遍歷方式

2-5-1:數(shù)組的迭代第一種(普通for循環(huán))

public class ArraysTest04 {
    public static void main(String[] args) {
        //1:聲明一個數(shù)組
        int[] arrs = new int[6];

        //2:通過索引一次給數(shù)組中的元素填充值
        int value = 10;
        for(int i = 0;i<arrs.length;i++){
            arrs[i] = value+(i*2);
        }

        System.out.println("當(dāng)前數(shù)組的長度是:"+arrs.length);
        //3:遍歷迭代當(dāng)前數(shù)組  依次獲取數(shù)組中的值
        for(int i = 0;i<arrs.length;i++){
            System.out.println("索引是"+i+"的元素的是:"+arrs[i]);
        }
    }
}

PS:通過普通for循環(huán),通過索引填充以及迭代當(dāng)前數(shù)組的元素。

2-5-2:數(shù)組的迭代第二種(foreach)

public class ArraysTest04 {
    public static void main(String[] args) {

        //1:聲明一個數(shù)組
        int[] arrs = new int[6];

        //2:通過索引一次給數(shù)組中的元素填充值
        int value = 10;
        for(int i = 0;i<arrs.length;i++){
            arrs[i] = value+(i*2);
        }
        //3:通過foreach迭代當(dāng)前數(shù)組
        System.out.println("jdk1.5之后,提供了增強(qiáng)for循環(huán),專門用來獲取集合中的元素");
        for(int num:arrs){
            System.out.println(num);
        }
    }
}

PS:通過foreach循環(huán)迭代數(shù)組中的元素。但是千萬注意,foreach循環(huán)對于和索引有關(guān)的操作無能為力,它只能做為迭代和遍歷使用。

三、多維數(shù)組

3-1:多維數(shù)組的聲明以及賦值

3-1-1:多維數(shù)組的聲明和初始化

這里我們通過二維數(shù)組類一探多維數(shù)組,其實(shí)本質(zhì)上而言,不管數(shù)組的維數(shù),它都是由一維數(shù)組慢慢拼湊起來的。

3-1-1-1:二維數(shù)組的聲明第一種方式(靜態(tài)初始化)

這里我們將二維數(shù)組的聲明和初始化放在一起來演示

代碼演示1
public class Test01 {
    public static void main(String[] args) {

        //1:聲明多維數(shù)組
        //聲明一個二維數(shù)組arrs 其實(shí)這里也可以理解為一個一維數(shù)組,只不過數(shù)組中包含的內(nèi)容是一個數(shù)組
        int[][] arrs;

        //2:數(shù)組的初始化:
        //指定當(dāng)前數(shù)組的長度。聲明了一個一維數(shù)組,數(shù)組中包含三個元素 每個元素都是一個新的數(shù)組
        arrs = new int[3][];

        //3:初始化第二種方式:
        //聲明一個數(shù)組,數(shù)組長度是3,每個元素中存放的是一個長度為4的新的數(shù)組 有點(diǎn)類似表格的展示
        arrs = new int[3][4];
    }
}
內(nèi)存分析1

第二行代碼初始化方式內(nèi)存分析,及arrs = new int[3] [];

第一步
結(jié)論1

聲明二維數(shù)組時,我們可以理解為聲明的是一個一維數(shù)組,比如聲明的數(shù)組為new int[3] [],其實(shí)就是聲明了一個int[3]數(shù)組,這個數(shù)組的每個元素都是一個新的數(shù)組對象,而此時元素存儲的每個數(shù)組對象的長度還沒有指定。所以在獲取元素時,由于二維數(shù)組是int類型,所以訪問二維數(shù)組的元素時獲取的值還是默認(rèn)值0。這里之所以在數(shù)組的位置上寫addr只是為了方便后期演示。

內(nèi)存分析2

第三行代碼初始化方式內(nèi)存分析,及arrs = new int[3] [4];

image
結(jié)論2

arrs = new int[3] [4],創(chuàng)建一個數(shù)組對象,數(shù)組中的元素個數(shù)是3個,每個元素的存儲是一個新的數(shù)組,新數(shù)組的長度是4。其實(shí)也可以看成是一個三行4列的二維表格。而這個二維數(shù)組的索引就是通過索引依次去獲取以及修改等。

3-1-1-2:二維數(shù)組的聲明第一種方式(動態(tài)初始化)
代碼演示1
public class Test01 {
    public static void main(String[] args) {
        //初始化的第三種方式:
        //初始化一個數(shù)組,數(shù)組的長度是3,每個元素都是一個數(shù)組,數(shù)組索引是1的指向的新數(shù)組的長度是1,以后以此類推
        String[][] strs = new String[][]{{"你好","我好"},{"嘿嘿","呵呵"},{"碼歌","老薛"}};
    }
}
內(nèi)存分析1
動態(tài)創(chuàng)建
結(jié)論1

1:會在堆內(nèi)存中開辟一個連續(xù)的存儲空間,存儲當(dāng)前的多維數(shù)組。

2:數(shù)組的每個元素其實(shí)存儲的是一個新的一維數(shù)組。存儲的是一維數(shù)組的地址。

3:根據(jù)聲明方式,新的一維數(shù)組中的元素個數(shù)是兩個,所以會開辟三個新的一維數(shù)組,開始給新的一維數(shù)組的每個元素賦值null

4:通過靜態(tài)初始化的方式,新的一維數(shù)組的元素開始正常賦值,比如:strs[0] 代表的是二維數(shù)組的第一個元素,str[0][0]代表二維數(shù)組的第一個元素中存儲的一維數(shù)組的第一個元素,賦值你好,str[0][1]代表二維數(shù)組的第一個元素中存儲的一維數(shù)組的第二個元素,賦值我好。然后依次類推

代碼演示2:(第二種靜態(tài)初始化)
public class Test01 {
    public static void main(String[] args) {
        //初始化的第三種方式:
        //初始化一個數(shù)組,數(shù)組的長度是3,每個元素都是一個數(shù)組,數(shù)組索引是1的指向的新數(shù)組的長度是1,以后以此類推
        String[][] strs = {{"你好","我好"},{"嘿嘿","呵呵"},{"碼歌","老薛"}};
    }
}
內(nèi)存分析2:

這里的內(nèi)存分析和上圖是一致的。

3-2:多維數(shù)組的CRUD以及遍歷

3-2-1:多維數(shù)組的填充值

3-2-1-1:簡單的填充值
3-2-1-1-1:測試用例
public class Test02 {
    public static void main(String[] args) {
        //1:聲明一個二維數(shù)組,存放3個一維數(shù)組
        String[][] strs = new String[3][];
        //2:獲取二維數(shù)組中的第一元素
        System.out.println("獲取二維數(shù)組中的第一個元素:"+strs[0]);
         //3:給二維數(shù)組中的每個元素指定意味數(shù)組的長度
        //3-1:指定二維數(shù)組的第一個元素是一個長度為2的數(shù)組
        strs[0] = new String[2];
        //3-2:指定二維數(shù)組的第二個元素是一個長度為2的數(shù)組
        strs[1] = new String[2];
        //3-3:指定二維數(shù)組的第三個元素是一個長度為2的數(shù)組
        strs[2] = new String[2];
         //4:查看二維數(shù)組中的第一個元素,該元素的第一個位置上的元素值
        System.out.println("查看二維數(shù)組中的第一個元素數(shù)組中的第一個元素:"+strs[0][0]);
3-2-1-1-2:打印結(jié)果
獲取二維數(shù)組中的第一個元素:null
查看二維數(shù)組中的第一個元素數(shù)組中的第一個元素:null
3-2-1-1-3:結(jié)論
  • 聲明的二維數(shù)組本質(zhì)上就是一個一維數(shù)組中的每個元素存儲的還是一個數(shù)組
  • 第二步獲取二維數(shù)組的第一個元素,由于創(chuàng)建方式的問題,其實(shí)本質(zhì)上而言,二維數(shù)組的每個元素還不是一個一維數(shù)組的地址,而只是int的默認(rèn)值0,這個千萬要注意
  • 第三步通過new 數(shù)組的方式給二維數(shù)組的每個元素填充值
  • 通過strs[索引][索引]去獲取或者填充值
3-2-1-2:常見的錯誤
很多人在這里犯錯,覺得通過上述方式可以聲明的就是二維數(shù)組,通過
   索引訪問二維數(shù)組中的一個元素:通過strs[0][0],那么結(jié)果是什么呢?
System.out.println("獲取二維數(shù)組中的第一個數(shù)組的第一個元素:"+strs[0][0]);
報錯:Exception in thread "main"java.lang.NullPointerException
at com.mage.arrays.multi.Test02.main(Test02.java:16)
因?yàn)樵诼暶鞫S數(shù)組的時候,只指定了二維數(shù)組中的存儲元素是3個,但是這三個元素中存儲的一維數(shù)組并沒有指定長度,也就意味著,這里訪問第一個元素時,第一個元素是0,并沒有可以通過索引訪問的內(nèi)容,所以報錯。所以訪問時,只能訪問二維數(shù)組的第一個元素
3-2-1-3:通過循環(huán)填充值
3-2-1-3-1:測試用例
public class Test03 {
    public static void main(String[] args) {

        //1:聲明二維數(shù)組,且通過循環(huán)填充值
 //聲明的二維數(shù)組,包含三個元素,每個元素中存儲一個包含了4個元素的一維數(shù)組
        int[][] arrs = new int[3][4];

        //2:通過循環(huán)填充值
        //循環(huán)二維數(shù)組的長度
        for(int i = 0;i<arrs.length;i++){
            //循環(huán)二維數(shù)組的每個元素中的一維數(shù)組的長度
            for(int j = 0;j<arrs[i].length;j++){
                ////往指定的位置上填充值
                arrs[i][j] = (int)(Math.random()*40);
            }
        }

        //3:查看二維數(shù)組中的元素值
        System.out.println("查看二維數(shù)組中第2個元素數(shù)組中的第1個位置上的值是:"+arrs[1][0]);
     }
}
3-2-1-3-2:打印結(jié)果
打印結(jié)果:查看二維數(shù)組中第2個元素數(shù)組中的第1個位置上的值是:7
3-2-1-3-3:結(jié)論

其實(shí)二維數(shù)組我們可以看做一個二維表格,上述通過循環(huán)填充值的代碼,其實(shí)就是一個表格如下圖:

表格

3-2-2:多維數(shù)組修改、查看元素

我們通過索引去查看以及修改元素

測試代碼:

public class Test02 {
    public static void main(String[] args) {
        //1:聲明一個二維數(shù)組,存放3個一維數(shù)組 每個數(shù)組長度為4
        String[][] strs = new String[3][4];
        //2:通過索引給二維數(shù)組的第一個元素數(shù)組中的第二個元素填充值
        strs[0][1] = "嘿嘿";
        System.out.println("查看二維數(shù)組中的第一個元素位置上的數(shù)組的第二位位置上的值是:"+strs[0][1]);
    }
}

3-2-3:多維數(shù)組的迭代

通過普通for循環(huán)和foreach循環(huán)依次迭代二維數(shù)組

3-2-3-1:測試用例
public class Test04 {
    public static void main(String[] args) {

        //1:聲明二維數(shù)組,且通過循環(huán)填充值
//聲明的二維數(shù)組,包含三個元素,每個元素中存儲一個包含了4個元素的一維數(shù)組
        int[][] arrs = new int[4][5];

        //2:通過循環(huán)填充值
        for(int i = 0;i<arrs.length;i++){
            for (int j = 0;j<arrs[i].length;j++){
                arrs[i][j] = (int)(Math.random()*60);
            }
        }

        //3:查看二維數(shù)組中的元素值
        System.out.println("=======通過for循環(huán)迭代=======");
        for(int i = 0;i<arrs.length;i++){
            System.out.print((i+1)+"行\(zhòng)t");
            for (int j = 0;j<arrs[i].length;j++){
                System.out.print(arrs[i][j]+"\t");
            }
            System.out.println();
        }

        //4:查看二維數(shù)組中的元素值
        System.out.println("=======通過foreach循環(huán)迭代=======");
        for(int[] arr:arrs){
            for (int value:arr){
                System.out.print(value+"\t");
            }
            System.out.println();
        }
    }
}
3-2-3-2:打印結(jié)果
=======通過for循環(huán)迭代=======
1行  40  6   16  12  29  
2行  12  50  22  48  16  
3行  21  54  5   44  20  
4行  6   34  28  3   16  
=======通過foreach循環(huán)迭代=======
40  6   16  12  29  
12  50  22  48  16  
21  54  5   44  20  
6   34  28  3   16  

四、數(shù)組和可變參數(shù)作為形式參數(shù)的區(qū)別

4-1:為什么需要可變參數(shù)

4-1-1:可變參數(shù)的演變

需求:計算兩個數(shù)相加 計算三個數(shù)相加 計算四個數(shù)等等,此時我們需要定義多個重載方法完成功能,在jdk5之前,我們可以通過數(shù)組完成該功能

4-1-1-1:測試用例
public class Test01 {
    public static void main(String[] args) {
        //1:定義調(diào)用方法實(shí)參
        int num1 = 10;
        int num2 = 20;
        int num3 = 30;
        
        //2:將多個值通過數(shù)組包裝
        int[] arrs = new int[]{num1,num2,num3};

        //3:調(diào)用相加的方法完成功能
        add(arrs);
    }
    //定義方法完成該功能
    public static void add(int[] arrs){
        int totle = 0;
        for(int num:arrs){
            totle += num;
        }
        System.out.println("多個值相加結(jié)果是:"+totle);
    }
}
打印結(jié)果:多個值相加結(jié)果是:60
4-1-1-2:問題

每次調(diào)用時,都需要對于實(shí)際參數(shù)進(jìn)行包裝,不夠簡單。這種做法可以有效的達(dá)到“讓方法可以接受個數(shù)可變的參數(shù)”的目的,只是調(diào)用時的形式不夠簡單

4-1-2:可變參數(shù)的定義

4-1-2-1:測試用例

定義可變參數(shù)方法完成功能

public class Test02 {
    public static void main(String[] args) {
        /**
         * jdk5 之后支持可變參數(shù) 使得調(diào)用變得更加簡單
         */
        //1:定義調(diào)用方法實(shí)參
        int num1 = 10;
        int num2 = 20;
        int num3 = 30;

        //2:調(diào)用add方法
        add(num1,num2,num3);

    }
    //定義可變參數(shù)的方法
    public static void add(int... arrs){
        int totle = 0;
        for(int num:arrs){
            totle += num;
        }
        System.out.println("多個值相加結(jié)果是:"+totle);
    }
}
打印結(jié)果:多個值相加結(jié)果是:60
4-1-2-2:結(jié)論

調(diào)用變得更加簡單一點(diǎn),而且實(shí)際方法中使用時也是通過數(shù)組的方式解析可變參數(shù)的值。

4-1-2:可變參數(shù)的使用規(guī)則

可變的使用,其實(shí)本質(zhì)上和使用數(shù)組是一致的。數(shù)組如何操作,可變參數(shù)的使用也如何操作即可。

4-1-2-1:可變參數(shù)的定義規(guī)則

定義可變參數(shù)是,就是形參列表中通過<span style="color:red">type...type_names</span> 這樣的形式去定義。

4-1-2-2:可變參數(shù)定義時一些問題
4-1-2-2-1:定義可變參數(shù)的方法不能包含多個可變參數(shù)

測試用例:

public static void function(String... strs,int ... arrs){}

問題:

編譯時報錯,Vararg parameter must be the last in the list。要確??勺儏?shù)在參數(shù)列表的最后一個位置。因?yàn)榭勺儏?shù)無法確保傳入的實(shí)參到底是多少個,所以本質(zhì)上Java的方法調(diào)用還是要確保實(shí)參和形參的個數(shù)、順序、類型要匹配到,不然無法調(diào)用

4-1-2-2-2:定義可變參數(shù)的方法的可變參數(shù)要在最后定義

測試用例:

public static void function(String... strs,int num){}

問題:

這的問題和上述問題是一致的,還是有序無法確定int類型的實(shí)際參數(shù)在調(diào)用時是具體是第幾個。

4-1-3:可變參數(shù)的優(yōu)勢

  • 調(diào)用形式變得更加簡單
  • 調(diào)用者可以根據(jù)自己的需要傳入合適的參數(shù)即可
  • 避免通過數(shù)組調(diào)用時需要構(gòu)造數(shù)組的過程

4-2:可變參數(shù)的一些坑

<h4 id="4.2.1">4-2-1:重載方法調(diào)用時,可變參數(shù)的調(diào)用順序</h4>

4-2-1-1:測試用例:
public class Test03 {
    public static void main(String[] args) {
        short s = 20;
        fun(10,s);
    }

    public static void fun(int num1,int num2){
        System.out.println("我是兩個個參數(shù)的方法 int int");
    }

    public static void fun(int num1,long num2){
        System.out.println("我是兩個個參數(shù)的方法 int long");
    }

    public static void fun(int ... num){
        System.out.println("我是個可變參數(shù)的方法");
    }
}
4-2-1-2:打印結(jié)果
我是兩個個參數(shù)的方法 int int
4-2-1-3:結(jié)論
  • 方法調(diào)用時首先會進(jìn)行精確匹配,是參合形參完全匹配的
  • 如果不存在這樣的方法,則會采用最優(yōu)最近方式去匹配,上述例子因?yàn)橹剌d方法包含了兩個參數(shù)的方法,分別是(int,int)和(int,long)這里會調(diào)用離的最近的(int,int),這里的近可以理解為short 和 int的距離要近于short到long
  • 可變參數(shù)的方法,如果上述的兩個方法都不存在,則會調(diào)用到,也就意味著可變參數(shù)的方法調(diào)用在最后才會被調(diào)用到。因?yàn)樵谡{(diào)用可變參數(shù)的時候,編譯器需要將實(shí)參編譯為對應(yīng)的數(shù)組,在進(jìn)行調(diào)用。

4-2-2:數(shù)組和可變參數(shù)同時存在重寫方法

4-2-2-1:重寫方法的要求
  • 一定要發(fā)生繼承關(guān)系
  • 方法的修飾符子類的要大于或者等于父類的修飾符
  • 方法的返回值子類的小于或者等于父類的返回值類型
  • 方法的名稱和子類和父類必須保持一致
  • 方法的參數(shù)簽名子類的要和父類的一致(包含個數(shù)、順序、類型)
4-2-2-2:編寫測試用例
需求:

提供商品打折功能,父類定義了打折的方法,子類根據(jù)需要重寫父類的方法。不過這里父類的方法形參通過可變參數(shù)定義,子類重寫的方法通過數(shù)組定義。

編寫測試用例
class F{
    void fun(int price,int ... discount){
        System.out.println("F.fun");
    }
}
class S extends F{
    @Override
    void fun(int price, int[] discount) {
        System.out.println("S.fun");
    }
}
問題

注意這里并不會出現(xiàn)報錯,很多人好奇的原因是由于這里子類重寫的方法的參數(shù)列表和父類的不一樣呀,為什么@Override難道不會報錯嗎?這里注意確實(shí)不會報錯,我們通過反編譯工具可以看到F類反編譯的代碼如下,你看懂了嗎?

F反編譯之后

PS:本質(zhì)上最后編譯的.class文件中我們發(fā)現(xiàn)可變參數(shù)會變成一個與之對應(yīng)的數(shù)組。

4-2-2-3:問題
編寫測試用例1:
public class Test04 {
    public static void main(String[] args) {
        F f = new S();
        f.fun(10,10);
        S s = new S();
    }
}
測試用例輸出結(jié)果
S.fun
結(jié)果分析

這里使用到了多態(tài),在真正運(yùn)行時,會執(zhí)行子類中的fun方法,但是參數(shù)列表是根據(jù)父類確定的。而此時父類中的方法是可變參數(shù),這時會把傳入的10編譯器會猜測為數(shù)組。因?yàn)榭勺儏?shù)可以接受多個值,所以根據(jù)傳入的參數(shù),其實(shí)這里會對于輸入的10進(jìn)行包裝,將其封裝為一個int數(shù)組,在進(jìn)行方法調(diào)用。請看下圖反編譯之后的結(jié)果:

測試類的反編譯工具
編寫測試用例2:
public class Test04 {
    public static void main(String[] args) {
        F f = new S();
        f.fun(10,10);
        S s = new S();
        s.fun(10,10);
    }
}
測試用例輸出結(jié)果
編譯報錯,Wrong 2nd argument type. Found: 'int', required: 'int[]' 
結(jié)果分析

這里調(diào)用時,由于直接指定了子類調(diào)用該方法,但是注意子類中的方法的參數(shù)列表是個數(shù)組,本身也是一種數(shù)據(jù)類型,編譯器無法將一個10直接轉(zhuǎn)為一個數(shù)組類型。本身Java的方法調(diào)用就嚴(yán)格要求類型匹配,所以這里就報錯,類型不匹配。

<span style="color:green">重點(diǎn):所以以后在去重寫可變參數(shù)的方法時,一定要慎重,盡量不要這么干。</span>

4-2-3:重載方法中定義可變參數(shù)問題

這個問題在<span style="color:green">[4-2-1]</span>中已經(jīng)提及過,就是當(dāng)出現(xiàn)可變參數(shù)方法的時候,千萬小心,因?yàn)楹笃诘木S護(hù)時,一不小心就會調(diào)入坑中。

4-2-4:可變參數(shù)+null值問題的重載方法

這是原騰訊的一道筆試題,通過閱讀請找出問題原因以及解決方案?

4-2-4-1:一下測試用例會不會出問題?原因是什么?
public class Test05 {
    public static void main(String[] args) {
        //調(diào)用該方法會出現(xiàn)什么問題
        fun("",null);
    }

    public static void fun(String str,String ... strs){
        System.out.println("str = [" + str + "], strs = [" + strs + "]");
    }
    public static void fun(String str,Integer ... ins){
        System.out.println("str = [" + str + "], ins = [" + ins + "]");
    }
}
4-2-4-2:原因描述

這里出現(xiàn)問題的原因是由于null值是可以轉(zhuǎn)換為任意引用類型。導(dǎo)致這里方法調(diào)用就會出現(xiàn)二義性,編譯器不知道要調(diào)用那個方法。所以在調(diào)用時需要手動繞開,比如想要調(diào)用String時,需要編寫 String[] str = null 。這樣程序編譯時就知道調(diào)用的是fun(String,String...)

4-2-4-3:增加難度 一下代碼會出問題嗎?
public class Test06 {
    public static void main(String[] args) {
        invoke(null,1);//會調(diào)用哪個方法呢?
    }

    static void invoke(Object obj,Object ... args ){
        System.out.println("obj = [" + obj + "], args = [" + args + "]");
    }
    static void invoke(String str,Object obj,Object ... args){
        System.out.println("str = [" + str + "], obj = [" + obj + "], args = [" + args + "]");
    }
}

這里會選擇調(diào)用invoke(String,...),注意重載方法調(diào)用時由于null既可以作為Object類型,也可以作為String類型。那么這里遵守的規(guī)則就是查看繼承關(guān)系,由于String是繼承Object,所以會選擇invoke(String,...)。

4-2-4-4:好奇害死貓
public class Test07 {
    public static void main(String[] args) {
        invoke(null,1);//會調(diào)用哪個方法呢?
    }

    static void invoke(Integer obj,Object ... args ){
        System.out.println("obj = [" + obj + "], args = [" + args + "]");
    }
    static void invoke(String str,Object obj,Object ... args){
        System.out.println("str = [" + str + "], obj = [" + obj + "], args = [" + args + "]");
    }
}

這里會就會報錯,存在二義性,導(dǎo)致JVM也不曉得要調(diào)用哪個方法。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容