本文總共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ù)組基礎(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)存分析

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];

結(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

結(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類反編譯的代碼如下,你看懂了嗎?

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)用哪個方法。