JAVA数组详解



数组概述

如果把变量理解成一个容器,那数组就是一排相同类型的容器,就像一排娃哈哈饮料。
数组要求所有数组元素具有相同的数据类型
注意:
由于JAVA有继承的机制,所以会出现一个数组存储“多种数据类型”的假象。比如一个水果数组,可能数组元素包含苹果对象、香蕉对象等等,但是这些数组元素都是水果类型的特殊化,也就是子类。所以,本质上,它们还是同一种水果类型的数组元素。

数组的元素可以是任意类型,可以是基本类型,也可以是引用类型,只需要保持同一种类型即可。
数组本身是引用类型。数组本身会定义一个引用变量,用于指向存储在堆中的数组元素。



定义数组

定义数组变量

JAVA虽然支持2种定义数组变量的方式,但其中一种是为了考虑C++程序员的习惯,我们真正推荐的是另一种,它更能表达数组是引用类型的事实。
语法1:type arrayName[]; // 不推荐
语法2:type[] arrayName; // 推荐,表示定义了某种数组类型的引用变量

注意:上面仅仅定义了一个数组类型的引用变量,我们并没有创建数组的对象,没有为数组元素划分内存空间存储数组

数组的初始化

仅仅有数组的引用是不行的,还需要为数组元素分配内存空间,所以需要对数组进行初始化。
数组的初始化分为2种:静态初始化和的动态初始化

静态初始化

静态初始化会显式的指定每个数组元素的初始值,并且由系统决定数组长度。

静态初始化的语法格式为:
arrayName = new Type[] {element1, element2, element3…};

注意:
这里的Type[]类型一定和arrayName的类型一致或者Type类型是arrayName数组元素类型的子类。而花括号里面的数据元素也必须是Type类型或者其子类。
举例:

// String类型是Object类型的子类,所以正确
Object[] array = new String[] {"Java", "C++"};
//这时候左右都是Object类型,而数组元素是String类型,是Object的子类,所以正确
Object[] array = new Object[] {"Java", "C++"};

在实际开发中,使用简化的静态初始化语法更常见
type[] arrayName = {element1, element2, element3, element4…};

这种写法需要注意:数组的定义和简化静态初始化必须同时完成
不能这样写:
int[] array;
array = {1, 2, 3};
上面这样是不合法的,必须写成一行:
int[] array = {1,2,3};

动态初始化

动态初始化:只需要指定数组长度,不需要显式设置数组元素初始值,系统会自动为数组元素分配初始的零值。

动态初始化的语法为:
arrayName = new type[length];

和静态初始化不同的是,多了一个int类型的length参数,用于设置数组的长度,同时取消了花括号部分,因为不需要进行显示初始化。

与静态初始化相似的是,这里的type[]类型必须和arrayName的类型相同或者是其子类
举例:

	// String类型是Object的子类,合法
	Object books = new String[2];

动态初始化只需要确定数组的长度,系统会自动为数组元素分配对应的内存空间,并且分配初始值。初始值就是所谓的零值(0,0.0,‘\u0000’,false,null)。

开发需要记住的初始化方法

一般,静态初始化我们实际使用简易的静态初始化,需要把数组定义和数组初始化一起进行:
type[] arrayName = {element1, element2, element3, element4…}; //静态初始化一般用这种格式
动态初始化只有一种写法,需要有new关键词明确是在创建数组类型的对象,也就是:
arrayName = new type[length]; // 动态初始化就一种格式,必须使用new关键字

误区:
不要同时使用静态和动态初始化,也就是不能既指定长度又显示分配初始值。
错误举例:int[] prices = new int[5] {2 ,3 ,4 ,5 ,6};
这里即使length=5和后面分配初始值的数量对得上,程序也是错误的。

var与数组初始化

如果初始化出现了new Type[]或者new Type[length]这部分,我们很容易看出数组的类型,此时数组定义可以使用var,看起来会更简洁。
举例:
var array1 = new int[10];
var array2 = new String[] {“java”, “C++”};

使用数组

使用数组就是存取数组元素
值得注意的是,访问数组元素一定要注意下标越界的问题
数组元素的索引是从0开始的,最后一个数组元素的索引值为length-1。
如果访问的索引值小于0或者大于等于数组长度,编译阶段不会报错,但是运行时会出现ArrayIndexOutOfBoundsException数组下标越界异常

为了更好的得知数组的长度(来防止越界),所有数组对象都提供了length实例变量,可以通过数组名.length的方式访问数组的长度。配合循环可以很好的实现数组的遍历:
for (int i = 0; i < array.length; i++)
{
代码块的细节
}

foreach循环与数组

foreach循环是专门用来遍历数组或者集合的。它牺牲了for循环的灵活性,但是如果用于遍历会非常简洁。循环需要的循环条件判断、迭代语句、初始化语句都被系统内置了。
foreach循环的语法格式为:
for (type(var) variableName : array | collection)
{
// variableName自动迭代访问每个元素…
}

括号的右半部分很好理解,就是提供想遍历的数组名或者集合名。左边的变量是用于迭代的循环变量。循环变量是一个临时变量,每轮循环开始时,循环变量就会得到下一个数组(集合)元素的值,用于数组或者集合的遍历。
注意:
1.循环变量的类型一定要和数组或者集合的类型一致。由于类型是固定的,所以用var做类型声明代码更简洁
2.循环变量只是一个临时变量,它不会改变数组或者集合的任何元素的值,它的作用只是用于遍历。

foreach循环使用说明

1.foreach循环的用途非常固定,就是用于数组或者集合的遍历如果希望改变数组或者集合元素的值,那就不能使用foreach循环
2.不要为循环变量赋值,循环变量每轮会固定得到下一个数组(集合)元素的值,使用foreach循环的目的就是遍历这些值,所以不要擅自为循环变量赋值覆盖掉有价值的数据



内存中的数组

只要用对象的思路去理解数组,就可以掌握数组的内存存储机制。
定义数组实际是定义一个数组的引用变量,用于指向存储在堆内存中的数组对象
初始化数组才会真正在内存中创建数组对象(不管是静态还是动态初始化),数组对象在内存中是一块连续的空间,可以通过数组的引用变量配合下标的方式访问对应的数组元素。

只需要理解数组的创建需要2部分就足够,把定义引用变量和定义数组对象分开理解

数组元素可以是基本类型,也可以是引用类型。
基本类型数组的思路非常简单,只是把多个同一类型的基本变量放到一块连续的空间而已。
引用类型数组的难点其实不是数组本身,而是创建对象本身就需要两步,需要创建引用变量和创建对象本身。
也就是,每个引用类型的数组元素都需要指向一个对象,这需要先让某个引用指向一个对象,再把这个引用变量的值存储到数组元素内部



“多维数组”

多维数组的概念其实很简单,只需要理解,数组本身也是对象。所以多维数组的底层机制就是每个数组元素本身也是一个数组的引用
假如要定义二维数组,只需要理解,二维数组的数组元素是一维数组的引用即可
用下面一段代码举例:

// 定义并初始化了一个二维数组,这是动态初始化,所以二维数组的元素都是null,并没有指向实际的一维数组
int[][] twoDimen = new int[3][];
// 让二维数组的元素依次指向新创建的一维数组对象,每个一维数组的长度都是不同的
for (int i = 0; i < 3; i ++) {
    twoDimen[i] = new int[i + 1];
}
// 依次打印每个二维数组元素(也就是每个一维数组),打印完一个一维数组就换行
for (int[] twoDimenI : twoDimen) {
    for (int i : twoDimenI) {
        System.out.print(i + " ");
    }
    System.out.println();
}

代码的运行结果为:
0
0 0
0 0 0

以上例子可以看出来,创建并初始化二维数组后,二维数组的元素还只是null(这和数组元素是对象的数组是一致的),还必须让每个元素依次指向有意义的一维数组

下面用二维数组举例,讲解多维数组的定义和初始化细节。

二维数组的定义

定义二维数组和定义一维数组是一样的方式。
一维数组的定义推荐的通用语法为:
type[] arrayName;
二维数组的type本身也是一个数组,比如数组元素是int[]的二维数组按照一维数组的定义推导就应该为:
int[][] arrayName; // 只需要把int[]这部分当一个特殊的type类型理解即可

所以,二维数组的定义推荐的通用语法为:
type[][] arrayName;

仅仅定义二维数组的引用变量是没意义的,必须创建二维数组的对象,再让这个引用变量指向该对象。也就是需要初始化二维数组。

初始化二维数组

和一维数组一样,二维数组的初始化也有静态初始化和动态初始化

静态初始化其实很好理解,静态初始化的通用语法为
type[][] arrayName = new type[][] { {…}, {…}, {…}, …}; // 不简化的静态初始化
或者:
type[][] arrayName = { {…}, {…}, {…}, …}; // 简化静态初始化

也就是说,花括号初始化的元素本身也是一个数组,所以花括号里面还有多个花括号。其实也不止这一种,里面的{}相当于是简化的静态初始化,而一维数组还有多种初始化方式。
下面用代码来举例:

// 第一个一维数组用的动态初始化,第二个一维数组用的简化的静态初始化,第三个一维数组用的不简化的静态初始化
// 这3种就是初始化一维数组的所有方式
int[][] twoDimen = new int[][] {new int[3], {2,3,4,5}, new int[] {7,8,9,10}};

动态初始化和一维数组有一些区别,因为二维数组可以选择是否要确定一维数组的长度,所以有两种初始化的语法

二维数组动态初始化的通用语法为
arrayName = new type[length][]; // 第一种只是确定了二维数组的长度,也就是确定存储多少个一维数组
arrayName = new type[length1][lenght2]; // 第二种不仅确定了二维数组的长度,还确定了一维数组的长度

没有多维数组

其实java的多维数组只是利用了对象的机制。拿二维数组举例,二维数组只是存储了一维数组的引用而已。所以,本质上,java并没有所谓的多维数组



工具类Arrays

JAVA提供了工具类Arrays,里面有很多类方法可以直接操作数组
因为Arrays类在java的util工具包下,所以使用Arrays类之前需要加上包的引用:import java.util.Arrays;
加粗样式
以下是关于数组的常见类方法:

  1. void sort(type[] a):该方法对a数组的数组元素进行升序排序,使用的是快速排序算法
  2. void sort(type[] a, int fromIndex, int toIndex):和方法1唯一的区别是,该方法指定了要排序的数组元素的范围,java的范围类型的参数都是一个规律,包含起点,不包含终点这里的fromIndex和toIndex指的是下标,是从0开始
  3. int binarySearch(type[] a, type key)用二分法查找数组中指定元素值的索引,能使用的前提是数组已经进行了升序排序。如果key值在数组中存在,就返回值为key的数组元素对应下标;如果key值在数组中不存在,就返回一个负值。
  4. int binarySearch(type[] a, int fromIndex, int toIndex, type key):方法4和方法3是类似的,只是方法4只会搜索指定范围内的数组元素,查找key值的位置。
  5. type[] copyOf(type[] original, int length):这个方法会复制原数组指定长度的数组元素,并且返回一个新的数组。length有3种可能,如果length小于原数组长度,就只复制原数组前面length个元素;如果length等于原数组长度,就完全复制原数组;如果length大于原数组长度,新数组的前面是原数组,后面多余的部分填充零值。
  6. type[] copyOfRange(type[] original, int from, int to):方法6和方法5类似,区别是方法6只复制指定范围的原数组元素。因为范围一定小于等于原数组,所以没有填充零值的可能。
  7. String toString(type[] a):该方法会把一个数组转换为字符串,这个字符串会用逗号(,)隔开所有数组元素,并在开头结尾加上[]。
  8. void fill(type[] a, type val)将原数组的所有值都填充为val
  9. void fill(type[] a, int fromIndex, int toIndex, type val):方法9和方法8完全相同,但方法9只会把值val填充到原数组的指定范围
  10. boolean equals(type[] a, type[] a2)如果两个数组长度相等,同时数组元素也完全相同,就返回true,否则返回false

补充:
其实不管是copyOf方法还是copyOfRange方法的实现,都调用了System类arraycopy类方法

void arraycopy(Object src, int srcPos, Object dest, int destPos, int length):该方法将src数组的元素赋给dest数组的元素,srcPos是从原数组开始赋值的元素下标destPos是赋值给目标数组的第一个元素下标,length参数指定了要赋值的元素数量。

注意:arraycopy方法是在System类下,和Arrays类无关。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值