關于Java?SE數組的深入理解
1、數組的基本概念
1.1 我們?yōu)槭裁葱枰獢到M?
假設說我們要存每個同學的期末考試總成績,如果我們還不知道數組的話,那我們是不是得新建100個變量,而且賦值和打印也相當的麻煩, 而且我們發(fā)現成績的數據類型都是一樣的,所以就會有數組這個概念,數組即是相同類型元素的集合,而且是一塊連續(xù)的存儲空間,每個空間都有編號,也就是我們口中常說的數組下標。而且使用數組,也可以是代碼變得更簡化,方便的進行排序查找等,現在,我們就來進入數組的學習把:
1.2 數組的創(chuàng)建與初始化
我們這期是由淺到深講解的,所以在前邊有一些不理解的問題,請堅持往后看,會得到解決的。
在Java中對數組的創(chuàng)建并初始化有兩種方式,分別是靜態(tài)初始化和動態(tài)初始化:
public static void arrayInit() { int[] array1 = { 1,2,3,4,5,6,7,8,9,10 }; //靜態(tài)初始化 int[] array2 = new int[] { 1,2,3,4,5,6,7,8,9,10 }; //靜態(tài)初始化 }
這兩種寫法有什么不同呢?第一種雖然省去了 new int[ ],但是在編譯的時候還是會還原成第二種方式,這兩種寫法本質上沒有區(qū)別,都是在JVM棧上開辟一個 array1和array2元素,同時也會在堆上開辟兩個一維數組,這兩個變量分別存這這兩個數組的地址,當然,這里你不懂沒關系,后面我們也會畫圖一一講解到,你只需要知道,這兩種定義并初始化的方式只是寫法上的不同就可以了!
那定義了數組但是不初始化呢?
public static void arrayInit() { int[] array3 = new int[10]; //動態(tài)初始化 int[] array4 = null; }
如果是上面這種創(chuàng)建但是不初始化,array3 里面會默認初始化成0,也就是說,如果數組中存儲的元素為基本類型,默認值為基本類型對對應的默認值0,像 array4 賦值成 null 這種情況,就可以理解成這個數組沒有引用任何對象,這個地方聽不懂沒關系,往后看就能懂了。
類型 | 默認值 |
---|---|
byte | 0 |
short | 0 |
int | 0 |
long | 0L |
float | 0.0f |
double | 0.0 |
char | /u0000 |
boolean | false |
不知道學過C語言的小伙伴還記不記得,C語言數組是可以不完全初始化的,比如說你一個數組5個元素,你只初始化前三個元素,在C語言中后續(xù)未初始化的元素會默認補0,但是在Java中你就算完全初始化了,如果你指定了元素個數的話,也會報錯,只有在使用 new int[10],動態(tài)初始化的時候才能寫明數組元素個數,你們也可以下來自己試試。
在Java的數組中,我們有幾點需要注意:
- 數組 { } 里面的元素類型一定要跟 [ ] 前面的類型保持一致
- 靜態(tài)初始化的時候不能指定長度,動態(tài)初始化的時候 new int[10]需要指定長度
- 雖然沒有指定數組長度,但是編譯器在編譯的時候會根據 { } 里面的元素個數來確定數組的長度
- 雖然Java數組可以寫成跟C語言一樣的寫法 int array[ ],但不推薦,因為這樣語義不明確!
- new int[n] ,[ ]中可以是變量
這里有小伙伴就有個小問題了,如果我定義了但是沒有初始化,我能不能在下面重新賦值呢?
public static void arrayInit() { int[] array3; array3 = { 1,2,3,4,5 }; }
這樣是絕對不可以的,雖然你現在可能不是很理解,但我們前面有提過,數組變量 array3 里面放的是地址,這個在下面我們會細講。這里你知道即可。
那如何正確的在定義之后初始化呢?或者重新賦值呢?
public static void arrayInit() { int[] array1; array1 = new int[10]; int[] array2; array2 = new int[]{ 1,2,3,4,5 }; }
像如上代碼這樣是ok的,本質上其實就是在堆區(qū)創(chuàng)建了一個新對象,這個我們還是在后面細講,這里讓你們先見一見,為后面細節(jié)學習作鋪墊。
1.3 數組的使用
數組是定義好了,我們如何去使用他呢?在前面我們提到過,數組是一塊連續(xù)的內存空間,都有對應的編號,也就是下標,我們就可以通過下標的方式快速訪問數組的任意位置的元素:
public static void main(String[] args) { int[] array = { 1,2,3,4,5 }; array[2] = 88; System.out.println(array[2]); }
同時也可以對下標的值進行修改,但是這里我們要注意幾點,數組的下標是從 0 開始的,所以最后一個元素下標應該是 n-1,也就是Java中大部分采用的都是左閉右開的,即:[ 0,n )
如果我們下標越界訪問了,不用擔心,程序會直接剖出下標越界異常,我們根據報錯提示去找對應的位置即可。
1.4 數組的遍歷
在Java中對數組其實有三種遍歷打印的方式,每一種的情況都會講明優(yōu)缺點,接著往下看:
for 循環(huán)打?。?/strong>
public class TestDemo { public static void main(String[] args) { int[] array = { 1,2,3,4,5 }; for (int i = 0; i < array.length; i++) { System.out.print(array[i] + " "); } System.out.println(); } }
這種方式是很常規(guī)的通過下標遍歷訪問的方式,對于數組的長度我們可以通過,數組對象.length 來獲取數組的長度。
for-each 循環(huán)打印:
public class TestDemo { public static void main(String[] args) { int[] array = { 1,2,3,4,5 }; for (int a : array) { System.out.print(a + " "); } System.out.println(); } }
這種打印方式呢,在 for 循環(huán)里冒號的左邊必須是你要打印的數組里元素的類型定義的變量,右邊則是你要打印數組的數組名,下面就可以通過你定義的變量進行打印了。for-each是for循環(huán)的另一種使用方式,他能在代碼上更簡潔,也可以避免循環(huán)條件和更新語句的書寫錯誤,但是這樣也有缺陷,比如說這種方式并不能拿到數組的下標。
Arrays工具類打印
public class TestDemo { public static void main(String[] args) { int[] array = { 1,2,3,4,5 }; String ret = Arrays.toString(array); //既然返回的是字符串,我們就用字符串類型來接收 System.out.println(ret); System.out.println(Arrays.toString(array)); //既然有返回值,那我們也可以直接鏈式訪問 } }
首先我們要介紹下Arrays,他是在 java.util 包下的一個工具類,里面定義了很多對數組的操作方法,而 Arrays.toString 他的作用是把數組轉換成字符串并返回,所以嚴格意義上它并不是遍歷數組,只是把數組轉化成對應的字符串而已。
以上三種方式各有優(yōu)缺點,初學者結合實際情況作應用
2、引用類型數組的深入講解
2.1 簡單了解 JVM 的內存分布
Java的程序需要運行在虛擬機上的,如果對于內存中的數據隨意存儲的話,那以后對內存管理起來將會是很麻煩的一件事,所以JVM也對所使用的內存按照不同的功能進行了劃分:
但是我們今天只重點關心堆和虛擬機棧這兩塊空間,在后續(xù)學習中會依次詳細介紹這里面的內容:
虛擬機棧:與方法調用相關的一些信息,每個方法在執(zhí)行的時候都會先創(chuàng)建一個棧幀,棧幀中包含:局部變量,操作數棧,動態(tài)鏈接,返回地址,以及一些其他的信息,保存的都是與方法執(zhí)行相關的一些信息,比如:局部變量,當方法運行結束了后,棧幀也被銷毀,即棧幀中保存的數據也被銷毀了
堆:它是JVM管理的最大內存區(qū)域,使用 new 創(chuàng)建的對象都是在堆上保存的,堆是隨著程序的開始而創(chuàng)建,隨著程序的退出而銷毀,堆中的數據只要還有在使用,就不會被銷毀。
2.2 基本類型變量與引用類型變量的區(qū)別
對這個理解特別重要,很關系到你后面進行學習,首先我們要區(qū)別這個兩個的區(qū)別,先說基本類型變量,這種變量中直接存放的是所對應的值,而引用類型創(chuàng)建的變量,一般稱為對象的引用,這種變量里存儲的是對象所在空間的地址,下面我們用一小段代碼,并且畫圖讓大家理解的更清楚:
public class TestDemo { public static void main(String[] args) { int a = 10; int b = 20; int[] array = { 1,2,3,4,5 }; } }
其實看到這張圖,相信大家就能很好的理解了,其實我們引用變量并不是存儲了對象本身,只是存儲了對象的其實地址,那我們的 array 只是存儲了在堆中開辟的一維數組的地址!并沒有把整個數組都保存在變量中!通過該對象的地址,引用變量就可以去操作這個對象了!
2.3 通過方法更深刻理解引用變量
有了上面的認識,我們就要來理解下面這兩個方法的作用了,相信你看完會有更深刻的認識:
是不是結果可能跟你想得有點不一樣呢?不用擔心,我們來一個個分析下:
首先我們執(zhí)行的是 func1 我們知道 array 變量中存的是一個對象的地址,那么通過傳參給 func1 的 arr1,首先要建立棧幀,把 array 存的地址拷貝到 arr1 當中,這樣一來就相當于 arr1 也指向了那個變量,但是我們又 new 了一個對象,并把新對象的地址賦值給了 arr1,相當于 arr1 原來存放的地址已經被替換了,也就說 arr1 指向了一個新的對象,因為只是把 array 存的地址拷貝給了 arr1 所以執(zhí)行完 func1 這個方法之后,array 并不會受任何影響,當方法結束,arr2 變量銷毀,因為arr2 銷毀之后沒有變量接著引用在 func1 中 new 的新對象,所以此時新的對象就被 JVM 回收了!
當我們執(zhí)行完 func1 時,就是我們說的結果,所以 array 的值不受任何影響!
我們接著再來看執(zhí)行 func2 之后的結果,首先前半部分與 func1 一樣,都是傳遞的 array 指向對象的地址,但是 func2 里面語句是直接對 arr2 中對象的地址進行下標訪問,修改了 [1] 下標處的值,因為本質 array 和 arr2 引用的都是同一個對象,當 arr2 修改了對象的值,所以當函數結束后接著打印 array 指向對象的值肯定也被修改了!方法結束,arr2 被銷毀,但是 arr2 指向的對象仍然被 array 指向著,所以JVM不會回收此時的對象!
看到這,你肯定有了更深刻的理解,前面有疑問的地方肯定得到了解決,所謂的 "引用" 本質上只是存了一個地址,Java將數組設定成引用類型,這樣后續(xù)進行數組傳參,其實只是將數組的地址傳入到形參當中,這樣就可以避免對整個數組的拷貝!
在我們目前認識中,如果對象沒有被引用,則會自動回收,所以不用考慮內存泄漏的問題
2.4 數組作為函數返回值
假設這里我們有一個題,需要實現一個方法,這個方法需要獲取斐波那契數列的前 n 項,需要返回一個數組回來,本質其實就是返回數組的地址,如何實現呢?
public static int[] fib(int n) { if (n <= 0) { return null; } int[] array = new int[n]; array[0] = array[1] = 1; for (int i = 2; i < n; i++) { array[i] = array[i - 1] + array[i - 2]; } return array; } public static void main(String[] args) { int[] array = fib(8); System.out.println(Arrays.toString(array)); }
當然這個方法如果 n 為1 就會越界,這個下來可以自己優(yōu)化下,來到這里我們就來介紹下Arrays這個工具類里面的一些方法了: 像一些將數組轉換成字符串,數組二分查找,數組排序等等,可以下來查閱下幫助手冊,這里我就不細說了,交給大家自己去擴展了,如果以后用到,我會進行說明。
3、二維數組
3.1 二維數組的概念和內存布局
這里我們一定要有一個概念,二維數組是一個特殊的一維數組,如何理解呢?用文字來說,二維數組的每個元素是一維數組,也就是說,二維數組的每個元素里面放的是一維數組的地址!
相信聽完上面的話,大家可能不是很理解,那我們就定義一個二維數組,并畫圖:
public static void main(String[] args) { int[][] array = { {1,2,3}, {4,5,6} }; }
這里就很清晰明了了,圖中也能看到,二維數組本質就是一個一維數組,只不過這個一維數組的每個元素存的是地址而已!
3.2 二維數組的定義和初始化
當然,二維數組和一維數組一樣,同樣的三種定義方式:
int[][] array1 = { {1,2,3}, {4,5,6} }; //兩行三列的二維數組 int[][] array2 = new int[][]{ {1,2,3}, {4,5,6} }; int[][] array3 = new int[2][3];
如果只是單純的 int[][] array; 這樣數組里面沒有任何引用對象,并不能直接使用未引用對象的數組,如果不引用最好置null
3.3 二維數組遍歷
for 循環(huán)打?。?/strong>
public class TestDemo { public static void main(String[] args) { int[][] array1 = { {1,2,3}, {4,5,6} }; //兩行三列的二維數組 for (int i = 0; i < array1.length; i++) { for (int j = 0; j < array1[i].length; j++) { System.out.print(array1[i][j] + " "); } System.out.println(); } System.out.println(); } }
這里第一個 for 里面的長度求的是 array1.length,因為它本身就是一個特殊一維數組,這樣求出的就是它的行,第二個for array1[i].length,自然求得就是每一行一維數組長度了
for-each 循環(huán)打?。?/strong>
public class TestDemo { public static void main(String[] args) { int[][] array1 = { {1,2,3}, {4,5,6} }; //兩行三列的二維數組 for (int[] arr : array1) { //二維數組的每個元素是一維數組,所以每個元素的類型是int[] for (int a : arr) { //arr是一維數組,每個數組的元素的int System.out.print(a + " "); } System.out.println(); } } }
Arrays 工具類有個方法也可以打印二維數組,是Arrays.deepToString,這個方法也是打印二維數組的但只能在一行顯示,感興趣的可以下來自己實驗下。
3.4 不規(guī)則的二維數組
在Java中,二維數組的列可以省略,也就是把二維數組看成一個特殊的一維數組的每個元素指向的一維數組是可以元素個數不同的,如何理解這句話呢?我們還是一樣通過一小段并且以畫圖的形式給大家講解:
int[][] array = new int[2][]; array[0] = new int[3]; array[1] = new int[2];
當然,他的遍歷方法是跟普通二維數組一樣的, 這里我就不多說,自己摸索,實在不懂可以問下博主。
總結
到此這篇關于關于Java SE數組的文章就介紹到這了,更多相關Java SE數組理解內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
解決mybatis一對多查詢resultMap只返回了一條記錄問題
小編接到領導一個任務需求,需要用到使用resultMap相關知識,在這小編記錄下這個問題的解決方法,對mybatis一對多查詢resultMap項目知識感興趣的朋友一起看看吧2021-11-11SpringBoot中的@Configuration注解詳解
這篇文章主要介紹了SpringBoot中的@Configuration注解詳解,Spring Boot推薦使用JAVA配置來完全代替XML 配置,JAVA配置就是通過 @Configuration和 @Bean兩個注解實現的,需要的朋友可以參考下2023-08-08