磨刀不误砍材工 – Java的基础语言要素(数组)
最后更新于:2022-04-01 20:08:35
在日常生活中,可乐有罐装的,有瓶装的。这里的“罐”和“瓶”就是可乐的容器。
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]);
}
}
}
~~~
但是我们运行该程序,发现其输出结果为:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-17_57b4319ce64f2.jpg)
也就是说,置换后两个元素的值都变为了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;
}
~~~
再次运行程序,查看其输出结果:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-17_57b4319d06eee.jpg)
了解了数组元素的置换工作,就可以试着完成对数组的排序了。这里我们对两种原理较简单但又很常用的排序方式进行了解。
第一种:选择排序
原理:每一趟从待排序的数组中选出最小(或最大)的一个元素,顺序放在已排好序的数列之后的一个位置,直到全部待排序的数据元素排完。
以我们日常生活中,按从矮到高的顺序站队列来说,假设现在有十个高矮不一的人需要排列。选择排序的方式就是:
首先我们假定现在站在最左边的第一个人就是最矮的,然后让他分别与后面的九个人依次进行比较。
在一次比较过程完成后,记录下这次比较中最矮的人和他站的位置。然后让这个人改变位置站到最左边去,而最初站在最左边的人则站到这个人的位置上去。
然后因为已经选出了最矮的人站在了最左边了,这次就直接将最左边第二个人选出,分别与剩下的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;
}
}
}
}
~~~
对于数组的理解和应用,到此就基本结束了。更多的使用方式还是应该根据实际的需求,自己加以利用。
';