在日常生活中,可樂有罐裝的,有瓶裝的。這里的“罐”和“瓶”就是可樂的容器。
Java當中也一樣,當同一類型的數據數量較多時,我們也可以通過容器將其裝在一起,更加方便使用。
數組是Java中的對象,用以存儲多個相同數據類型的變量。
數組能夠保存基本數據類型也能保存對象引用,但數組自身總是堆中的對象。
**一、數組的創建**
**1.1、聲明數組:**
通過說明數組要保存的元素類型來聲明數組,元素類型可以是基本數據類型或對象,后跟的方括號可以位于標示符的左邊或右邊。
也就是說,數組的聲明方式可以分為以下兩種:
- ArrayType ArrayName [];
- ArrayType [] ArrayName;
符號“[]”代表聲明的是一個數組,這兩種聲明方式就達到的效果而言,沒有任何區別。
但第二種方式可以一次性聲明多個數組,如:int [] intArr1,intArr2;而且第二種方式的閱讀性更強。
所以通常來說,都選擇使用第二種方式來聲明一個數組對象。
**1.2、構造數組:**
當我們使用上面說到的方式聲明了一個數組后,實際上只是聲明了一個對應數組類型的對象引用,而內存中還沒有真正的數組對象存在。
這時候,就需要完成數組對象的構造工作。所以說,我們所謂的構造數組,實際上也就是是指在堆內存中真正的創建出數組對象。
而換句話說,所謂的構造數組的工作。就是指,在數組類型上執行一次new,從而完成數組的對象實例化工作。
為了創建數組對象,JVM需要了解在堆內存上需要分配多少空間,因此必須在構造時指定數組長度。數組長度是該數組將要保存的元素的數量。
構造數組最直觀的方法是使用關鍵字new,后跟數據類型,并帶方括號“[]”指出該數組要保存的元素的數量,也就是數組長度。
例如:int [] intArray = new int[10];。該條語句代表聲明并構造了一個長度為10的保存int類型數據的數組。
再次提醒,構造數組時必須要求聲明數組的長度,因為我們已經說過了JVM需要得到這個長度,才能為該數組對象在堆中分配合適的內存空間。
所以,不要使用int [] intArray = new int [];這樣的語句。這將會引起編譯錯誤。
現在我們通過一段代碼來看一看,數組在內存中的構造初始化特點:
~~~
private static void demo_1() {
byte[] byteArr = new byte[5];
short[] shortArr = new short[5];
int[] intArr = new int[5];
long[] longArr = new long[5];
char[] charArr = new char[5];
float[] floatArr = new float[5];
double[] doubleArr = new double[5];
String[] strArr = new String[5];
System.out.println("byteArr:" + byteArr[0]);
System.out.println("shortArr:" + shortArr[0]);
System.out.println("intArr:" + intArr[0]);
System.out.println("longArr:" + longArr[0]);
System.out.println("charArr:" + charArr[0]);
System.out.println("floatArr:" + floatArr[0]);
System.out.println("doubleArr:" + doubleArr[0]);
System.out.println("strArr:" + strArr[0]);
}
~~~
這段程序運行的輸出結果為:
byteArr:0
shortArr:0
intArr:0
longArr:0
charArr:
floatArr:0.0
doubleArr:0.0
strArr:null
觀察輸出結果我們發現,對于數組構造,JVM在內存中還會根據該數組的數據類型對其中的元素進行一次默認初始化的賦值。其實這也正是源自于堆內存自身的特點。
對于在堆內存中存儲的變量,如果我們沒有在聲明變量時對其進行賦值工作。那么堆也會對這些變量根據其自身數據類型進行一次默認初始化的賦值工作。
這也正是我初學Java時,一直不明白為什么一個類的成員變量可以不做手動的初始化賦值工作,仍然能夠在以后的程序中正常調用;
而如果一個局部變量如果不進行手動的初始化賦值,如果在之后的代碼對其發生調用,就會編譯出錯的原因所在。
因為成員變量存儲在堆當中,即使我們沒有對其做手動的初始化賦值工作,其也會有一個默認的初始化值。而存儲在棧內存當中的成員變量則不會被進行默認初始化賦值工作,所以如果說我們沒有人為的為其指定一個初始化值的話,在之后對其調用時,該變量自身是沒有值的,自然無法調用。所以也就不難理解為什么會編譯出錯了。
**多維數組**
像我們前面說到的格式為:int [] intArray = new int [5];這樣聲明的數組,被稱為一維數組。那么對應的,自然也就存在多維數組。
要明白的是:多維數組其實也是數組,只不過一維數組用于保存基本數據類型或對象引用,而多維數組用于保存數組(其實也是保存對象引用,數組的對象引用)。
所以,假設我們聲明一個二維數組:
int [ ] [ ] twoD = new int [5] [5];
對于這樣的多維數組,我們應該這樣理解:
一個int型的二維數組,實際上就是一個int型的一維數組(int [])的對象,而它保存的是一維數組的數組對象引用。
也就是說我們使用一個int型的二維數組,實際上存儲的元素就是:int [] intArray = new int [5];這樣的數組對象的引用。
所以就int [ ] [ ] twoD = new int [5] [5];而言,
實際上就是說,構造了一個int型的二維數組,該二維數組存儲5個“int [] intArray = new int [5]”這樣的一維數組。
如果覺得這樣的說法還是過于抽象,不易理解的話。我們不妨結合一些現實生活中的事物來看待:
舉個例子,我們在感冒或者中暑之類的時候,可能都喝過一樣東西叫:藿香正氣液。
就以藿香正氣液來說,我們知道它的最小包裝單位是“一小瓶”。我們可以將“一小瓶藿香正氣液”,視為我們要存儲的數據元素。那么:
假設一盒藿香正氣液里面包裝有10小瓶,就正是所謂的一維數組:
藿香正氣液 [ ] 一盒 = new 藿香正氣液 [10];
假設一箱藿香正氣液里面包裝有50盒,這種情況就是所謂的二維數組:
藿香正氣液 [ ] 一箱 = new 藿香正氣液 [50] [10];
同樣的原理,更多維的數組也可以以此類推。所以需要明白的就是:所謂的多維數組,根本來講還是數組。
**1.3、數組元素的賦值與取值**
既然知道了數組是作為存放統一數據類型的容器存在的,那么所謂容器,自然就涉及到向容器中存放元素或從中取出元素的操作的。
Java中,對于數組元素的訪問方式很簡單,數組中的各個元素都能通過索引(也就是數組下標)進行訪問,格式為:arrayName[下標]。
而需要注意的就是,Java中數組元素的索引是從0而不是從1開始的,也就是說第一個被存儲進行數組的元素的索引是0而不是1;
相對的,數組中最后一個元素的索引就是聲明的數組的長度減去1,而不是聲明的數組長度。如果通過無效的索引訪問數組,則會觸發數組越界異常。
具體的使用,通過一段簡單的代碼進行簡單的演示:
~~~
private static void demo_2() {
int[] array = new int[5];
// 可以通過length獲取數組的長度
System.out.println("數組的長度為:" + array.length);
// 遍歷數組中的元素,并為其賦值
for (int i = 0; i < array.length; i++) {
array[i] = i + 1;
}
// 遍歷獲取數組中的元素的值
for (int i = 0; i < array.length; i++) {
System.out.println("array[" + i + "] = " + array[i]);
}
}
~~~
話到這里,我們已經知道了在Java當中:
1、通過arrayType [] arrayName;可以聲明一個數組。
2、通過arraryName = new arrayType[length];可以對聲明的數組在內存中進行構造工作,并完成元素的一次默認初始化。
3、通過arrayName[index]對數組中存放的元素進行賦值或訪問。
那么,順帶一提的就是,Java種還提供另外一種聲明方式。這種聲明方式將數組的聲明、構造以及賦值(顯式初始化)工作都集合到一條語句當中。
這種聲明方式就是:arrayType [ ] arrayName = {value1,value2,value3};。
舉例來說,如果我們想聲明一個int型的數組對象,其數組長度為4,我們想要存放的4個值分別為1,3,6,9。那么就可以定義為:nt [] intArray = {1,3,6,9};
使用這種方式最大的特點就在于:可以在聲明數組的同時,就完成數組中的元素的賦值工作。
除此之外,還有另外一種數組的聲明方式,被稱為:匿名數組創建。
舉例來說,我們這樣定義:int [ ] intArray = new int [ ]{1,2,3}; 。這里的"new int [ ]{1,2,3}"就被稱為匿名數組的創建。
你可能在想,使用這樣的創建方式相對于其它方式而言,好處是什么?
好處就是:可以實時的創建出一個新的數組,而不需要將其賦給任何變量,就直接作為參數傳遞給接受對應數組類型的方法。
我們通過一段代碼更形象的來理解關于“匿名數組”的這一特點:
~~~
//分別使用三種創建方式完成相同效果的數組創建工作
private void array_demo(){
//第一種方式
int [] array_1 = new int[3];
array_1[0] = 1;
array_1[1] = 2;
array_1[2] = 3;
//第二種方式
int [] array_2 = {1,2,3};
//第三種方式
int [] array_3 = new int[]{1,2,3};
/*
* 第三種方式最大的好處就在于:
* 直接實時創建匿名數組對象作為方法參數進行傳遞
* 而不需要像第一和第二種方式必須通過變量(對象引用)進行傳遞。
*/
this.acceptUnnamedArray(new int[]{1,2,3});
}
private void acceptUnnamedArray(int [] arr){
//...
}
~~~
我想順帶說明一下的就是,之所以被稱為“匿名”數組,就是因為“new int [ ]{1,2,3}”直接作為方參數進行傳遞,
而沒有將自身賦給任何對象引用,也就是說該數組對象自身是沒有標示符的,我們知道在Java中,標示符就是一個變量的名字。
既然它沒有對應的“名字”,自然就被稱為“匿名”了。而同理的,Java中的匿名對象也是如此。
與此同時數組本身也屬于對象,所以匿名數組其實也可以被視作是匿名對象的一種。
**數組排序**
很多時候,我們會需要對數組中的元素按照一定的順序(如按從小到大順序等等)進行重新排列。
所謂的排序,也就是指將元素按照指定順序進行位置的置換。所以,在正式的排序工作之前,我們應該首先了解數組中元素的置換工作怎樣完成。
數組中元素的置換過程與我們傳統的思想可能有些不同:
以學生相互換座位之間的問題為例,座位號為10的a學生與座位號為25的b學生進行座位的互換,其過程被分解一下,其實就是:
首先,讓兩位學生都起身離開座位。然后讓a學生先坐到25號座位,b學生坐到10號座位,就完成了座位的置換。
而如果我們以相同的思想,對數組中的元素進行置換工作的話,對應代碼的體現形式就是:
~~~
package com.tsr.j2seoverstudy.base;
public class ArrayDemo {
public static void main(String[] args) {
int [] arr = {1,2};
System.out.println("置換之前:");
printArr(arr);
//進行元素置換
swap(arr, 0, 1);
System.out.println("置換之后:");
printArr(arr);
}
/**
* 數組元素置換
* @param arr 數組
* @param a 要置換的第一個元素的索引(下標)
* @param b 要置換的第二個元素的索引
*/
private static void swap(int [] arr,int a,int b){
arr[a] = arr[b];
arr[b] = arr[a];
}
private static void printArr(int [] arr){
for (int i = 0; i < arr.length; i++) {
System.out.println("arr["+i+"] = " +arr[i]);
}
}
}
~~~
但是我們運行該程序,發現其輸出結果為:

也就是說,置換后兩個元素的值都變為了2。出現這樣的錯誤,原因并不難理解:
首先arr[a] = arr[b],實際上執行的就是arr[0] = 2;于是這個時候內存中該數組內索引為0和為1的兩個元素的值實際上都變為了2.
所以當再執行arr[b] = arr [a]的時候實際上就對應于arr[1] = 2;然后通過分析,我們發現:
之所以出現這樣的輸出結果,是因為原本的索引0的元素的值"1"在置換過程中丟失了。
那么我們應該做的措施就是讓記錄下這個值,不讓其丟失。所以,就可以通過新建一個臨時變量專門記錄該值的方式,避免丟失的發生。
于是上面的swap方法,應該修改為:
~~~
private static void swap(int [] arr,int a,int b){
//定義一個臨時變量記錄索引a的元素的值
int temp = arr [a];
arr[a] = arr[b];
arr[b] = temp;
}
~~~
再次運行程序,查看其輸出結果:

了解了數組元素的置換工作,就可以試著完成對數組的排序了。這里我們對兩種原理較簡單但又很常用的排序方式進行了解。
第一種:選擇排序
原理:每一趟從待排序的數組中選出最小(或最大)的一個元素,順序放在已排好序的數列之后的一個位置,直到全部待排序的數據元素排完。
以我們日常生活中,按從矮到高的順序站隊列來說,假設現在有十個高矮不一的人需要排列。選擇排序的方式就是:
首先我們假定現在站在最左邊的第一個人就是最矮的,然后讓他分別與后面的九個人依次進行比較。
在一次比較過程完成后,記錄下這次比較中最矮的人和他站的位置。然后讓這個人改變位置站到最左邊去,而最初站在最左邊的人則站到這個人的位置上去。
然后因為已經選出了最矮的人站在了最左邊了,這次就直接將最左邊第二個人選出,分別與剩下的8個人進行比較,然后以此類推。體現代碼中的表現形式就是:
~~~
public static void main(String[] args) {
int[] arr = { 76, 81, 19, 24, 35 };
selectSort(arr);
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]);
if (i < arr.length - 1)
System.out.print(",");
}
}
private static void selectSort(int[] arr) {
int temp; //緩存
int min; //最小值
int index;//最小值的索引
for (int i = 0; i < arr.length; i++) {
min = arr[i];
index = i;
//依次比較
for (int j = i + 1; j < arr.length; j++) {
if (arr[j] < min) {
min = arr[j];
index = j;
}
}
//位置置換
temp = arr[i];
arr[i] = min;
arr[index] = temp;
}
}
~~~
查看其輸出結果為:19,24,35,76,81
第二種:冒泡排序
原理:冒泡排序也是一種交換排序算法。其過程是數組中較小(或較大)的元素看做是“較輕的氣泡”,對其進行上浮操作。從底部開始,反復的對其進行上浮操作。
而對應于我們剛才談到的隊列問題,冒泡排序的方式就是:依次讓相鄰的兩個人之間進行比較,如果左邊的人高于右邊的人,則讓他們交換位置。對應的代碼體現則是:
~~~
public static void main(String[] args) {
int[] arr = { 76, 81, 19, 24, 35 };
bubbleSort(arr);
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]);
if (i < arr.length - 1)
System.out.print(",");
}
}
private static void bubbleSort(int [] arr){
int temp;
for (int i = 0; i < arr.length - 1; i++) {
/*
* 數組長度-1是為了保證不發生數組越界
* 而減-i則是因為每完成一次排序過程,該次比較中最大的數就會下沉到相對最后的位置,
* 那么就不需要進行重復而多余的比較工作了,-i就是為了避免這些多余的工作。
*/
for (int j = 0; j < arr.length - 1 - i; j++) {
if(arr[j]>arr[j+1]){
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
~~~
對于數組的理解和應用,到此就基本結束了。更多的使用方式還是應該根據實際的需求,自己加以利用。
- 前言
- 第一個專欄《重走J2SE之路》,你是否和我有一樣的困擾?
- 磨刀不誤砍材工 - 環境搭建(為什么要配置環境變量)
- 磨刀不誤砍材工 - Java的基礎語言要素(定義良好的標示符)
- 磨刀不誤砍材工 - Java的基礎語言要素(關鍵字)
- 磨刀不誤砍材工 - Java的基礎語言要素(注釋-生成你自己的API說明文檔)
- 磨刀不誤砍材工 - Java的基礎語言要素(從變量/常量切入,看8種基本數據類型)
- 磨刀不誤砍材工 - Java的基礎語言要素(運算符和表達式的應用)
- 磨刀不誤砍材工 - Java的基礎語言要素(語句-深入理解)
- 磨刀不誤砍材工 - Java的基礎語言要素(數組)
- 換一個視角看事務 - 用&quot;Java語言&quot;寫&quot;作文&quot;
- 牛刀小試 - 淺析Java的繼承與動態綁定
- 牛刀小試 - 詳解Java中的接口與內部類的使用
- 牛刀小試 - 趣談Java中的異常處理
- 牛刀小試 - 詳解Java多線程
- 牛刀小試 - 淺析Java集合框架的使用
- 牛刀小試 - Java泛型程序設計
- 牛刀小試 - 詳細總結Java-IO流的使用