一、数组介绍
数组是Java中的一种数据结构,用于存储一组相同类型的元素。它们在内存中是连续存储的,并且通过索引来访问元素。以下是关于Java数组的详细介绍:
1、数组的创建和初始化
在Java中,数组是一种对象,它可以存储固定大小的同类型元素。数组的大小在创建时确定,并且一旦创建就不能改变。
1)方法一:动态创建
第一步要声明数组变量:
// 声明数组变量
type[] arrayName;
// 或者(C/C++形式声明)
type arrayName[];
上面两种都可以用来声明数组变量,但是更推荐第一种,下面为例子:
int[] arrExample1;
char arrExample2[];
第二步要分配大小:
使用new
关键字来分配数组的内存。
// 分配内存并初始化数组
arrayName = new type[size];
下面为例子:
int[] arrExample1;
char arrExample2[];
arrExample1 = new int[5];//对第一个数组分配5个整型空间
arrExample1 = new char[10];//对第一个数组分配10个字符型空间
当然,这两步可以使用一条语句完成:
type[] arrayName = new type[size];
下面为例子:
double[] arrExample = new double[10];//声明一个double类型数组,并为其分配十个double的空间
动态创建对应的初始化
对于上面的动态创建数组,只能使用下标一个一个赋值,使用循环可以更方便赋值。
import java.util.Scanner;
public class Test {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int[] arrExample = new int[5];
//输入整型,存入数组
for(int i = 0; i < arrExample.length; i++) {
System.out.println("请输入整数");
arrExample[i] = input.nextInt();
}
//打印数组的每一个元素
for(int i = 0; i < arrExample.length; i++) {
System.out.println("arrExample[" + i + "] = " + arrExample[i]);
}
}
}
运行结果:
2)方法二:静态创建和初始化
你也可以通过以下方法来创建数组。
dataType[] arrayRefVar = {value0, value1, ..., valuek};
例子:
int[] arrExample = {1, 2, 3, 4, 5};
使用这种方式创建和初始化的数组的大小就是初始化时放入的所有元素的总大小。这样创建的数组是被初始化好的。
这样初始化就相当于:
int[] arrExample = new int[5];
int[0] = 1;
int[1] = 2;
int[2] = 3;
int[3] = 4;
int[4] = 5;
2、数组的属性和方法
数组是引用类型,数组型数据是对象。
1)数组的属性
数组的 length
属性用于获取数组的长度,即数组中元素的个数。
int[] arr = {1, 2, 3, 4, 5};
System.out.println("Array length: " + arr.length); // 输出: Array length: 5
2)数组的方法
尽管数组对象本身没有方法,但 Java 提供了 java.util.Arrays
类,该类包含了许多用于操作数组的静态方法。以下是一些常用的 Arrays
类方法:
Arrays.toString()
将数组转换为字符串表示形式。
import java.util.Arrays;
public class Test {
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5};
System.out.println("arr: " + Arrays.toString(arr));
}
}
运行结果:
Arrays.fill()
用指定的值填充数组的每个元素。
import java.util.Arrays;
public class Test {
public static void main(String[] args) {
int[] arr = new int[10];
Arrays.fill(arr,6);
System.out.println("arr: " + Arrays.toString(arr));
}
}
运行结果:
Arrays.equals()
比较两个数组是否相等。
import java.util.Arrays;
public class Test {
public static void main(String[] args) {
int[] arr1 = {1, 2, 3};
int[] arr2 = {1, 2, 3};
int[] arr3 = {1, 2};
System.out.println("arr1 equals to arr2 is " + Arrays.equals(arr1,arr2));
System.out.println("arr1 equals to arr3 is " + Arrays.equals(arr1,arr3));
}
}
运行结果:
3、补充
1)默认初始化
当数组被创建后,没有被赋值,对于不同的数据类型它会被默认初始化为以下值:
int | short | byte | long | float | double | char | boolean | String |
0 | 0 | 0 | 0L | 0.0f | 0.0 | \u0000 | false | null |
2)下标范围
当使用下标访问数组的元素时,下标的范围应当在 0 到 (array.length - 1) 之间,如果超出了这个范围,就造成了下标越界异常。
3)数组赋值机制
对于一个数组的创建,例如下面:
int[] arrExample;
arrExample = new int[10];
第一步实际上是声明一个数组引用变量,然后第二步就是申请 10 个整型的空间,然后将这个空间的引用赋值给 arrExample 这个数组引用变量。就像 C 语言那样,数组名可以退化成首元素地址。对于这里的数组引用变量,实际上也可以说存储的是数组的引用。
然后下面我们将一个数组引用变量的值赋给另一个数组引用变量,实际上赋的值是地址:
int[] arr1 = new int[10];
int[] arr2 = arr1;
这里将数组引用变量 arr1 的值赋值给 arr2,这里实际上就是将 arr1 中的数组的地址赋值给 arr2,然后我们就可以使用 arr2 来访问原来的数组。
下面我们可以通过一段代码来验证:
public class Test {
public static void main(String[] args) {
int[] arr1 = new int[10];//这里的数组arr1的元素都被默认初始化为0
int[] arr2 = arr1;
//下面我们改动arr1中的元素
for(int i = 0; i < arr1.length; i++) {
arr1[i] = i;
}
//然后我们打印arr2中的元素
for(int i = 0; i < arr2.length; i++) {
System.out.println("arr2 的第" + i + "个元素 = " + arr2[i]);
}
}
}
运行结果:
可以发现,虽然上面我们是对数组 arr1 的元素进行更改,但是数组 arr2 中的元素也与数组 arr1 发生了一样的变化,所以说,实际上这样对数组引用变量进行赋值,赋的值实际上是数组的地址。
这里对数组 arr2 的元素进行改动也会影响到数组 arr1。
4)数组初始化补充
数组初始化还可以通过以下方式进行(这里的第二个的中括号中不用写出元素个数,编译器会自动推断出需要的个数):
int[] arr = new int[]{1, 2, 3};
这种形式与下面这种形式有什么区别呢:
int[] arr = {1, 2, 3};
区别:
int[] arr = new int[]{1, 2, 3};
这是显式数组初始化的完整形式。在这种语法中,你明确地使用了 new int[]
来创建一个新的数组对象,并且立即初始化它的值。这种方式可以在任何上下文中使用,包括在需要返回数组的表达式中。例如就(下面这种写法是正确的):
public int[] getArray() {
return new int[]{1, 2, 3};
}
因为这里是在堆区创建了一个数组对象,然后将其引用返回。
对于这种形式:
int[] arr = {1, 2, 3};
这是隐式数组初始化的简化形式。编译器自动推断出数组的类型和大小。这种语法只能在声明和初始化同时进行时使用。这种方式不能用于需要创建数组对象的表达式中。例如(下面这种写法是错误的):
public int[] getArray() {
return {1, 2, 3}; // 编译错误
}
因为对于 {1, 2, 3} 只能是一个字面值,并没有在堆区创建一个数组变量,自然也没有像上面的数组对象的引用,所以这里是错误的。
二、数组细节
1、数组在内存中的存储
数组的元素数据存储在堆区:
在 Java 中,数组是一种对象。所有对象都是在堆区(heap memory)中分配的。因此,数组的数据存储在堆区。当我们创建一个数组时,JVM 会在堆区中为这个数组分配内存空间,用于存储数组的实际元素。
数组名是引用变量:
数组名本质上是一个引用变量。这个引用变量是存储在栈区的。这意味着数组名存储了对数组对象的引用(即数组对象在堆区中的地址)。当我们声明一个数组时,例如 int[] arr;
,此时 arr
是一个引用变量,但它还没有指向任何实际的数组对象。只有当我们初始化数组时,例如 arr = new int[5];
,JVM 才会在堆区中分配空间来创建数组对象,并将其地址赋给 arr
。
数组引用变量存储的是堆区地址:
引用变量中存储的是数组对象的堆区地址。这意味着通过数组名 arr
可以访问实际存储在堆区中的数组数据。所以说上面我们使用一个数组引用变量的值赋给另一个数组引用变量,实际上赋的值是地址。
2、数组的存储图解
三、对数组的操作
1、数组拷贝
上面我们提到如果我们将一个数组引用变量赋值给另一个数组引用变量,会导致两个数组引用变量访问的数组是同一个,也就是它们中存储的地址是同一个,这样本质上还是一个数组。
如果我们想要两个数组,是独立的空间,但是存储的元素个数和值大小都是一样的,改动一个不会影响另一个。需要怎么办呢,假设我们开始有一个数组,我们就可以再创建一个数组引用变量,然后开辟相应的空间,也就是创建对象,然后将对象的地址赋值给引用变量。这样我们就有两个空间独立的数组了。
然后我们遍历数组一,依次将数组一的元素赋值给数组二,这样就实现了数组拷贝。
public class Test {
public static void main(String[] args) {
int[] arr1 = {1, 2, 3, 4, 5};
int[] arr2 = new int[arr1.length];//保证两个数组的空间大小一样
for(int i = 0; i < arr1.length; i++) {
arr2[i] = arr1[i];//依次将元素的值赋值
}
//然后我们对arr1进行改动
arr1[0] = 666;
//打印arr1
System.out.println("arr1");
for(int i = 0; i < arr1.length; i++) {
System.out.println(arr1[i]);
}
//打印arr2
System.out.println("arr2");
for(int i = 0; i < arr2.length; i++) {
System.out.println(arr2[i]);
}
}
}
运行结果:
可以发现这里我们改动 arr1 没有影响 arr2,就是因为两者使用的是不同的内存空间。
2、数组反转
public class Test {
public static void main(String[] args) {
int[] arr1 = {1, 2, 3, 4, 5};
//反转前的数组
System.out.println("反转前的数组");
for(int i = 0; i < arr1.length; i++) {
System.out.print(arr1[i] + " ");
}
//进行反转
for(int i = 0; i < arr1.length / 2; i++) {
int temp = arr1[i];
arr1[i] = arr1[arr1.length - 1 - i];
arr1[arr1.length - 1 - i] = temp;
}
//反转后的数组
System.out.println("\n反转后的数组");
for(int i = 0; i < arr1.length; i++) {
System.out.print(arr1[i] + " ");
}
}
}
运行结果:
3、数组扩容
我们这里进行数组扩容的思路就是,先创建一个比要扩容的数组大一个元素空间的数组,然后将原数组的元素依次拷贝到新数组中,然后将要添加的值放到新数组的最后,然后将新数组的数组引用变量赋值给原数组的数组引用变量,也就是将新的数组的地址赋值给原来的数组引用变量。这样原来的数组引用变量就可以访问到扩容好的新数组了。
对于原数组空间,就无法再次被访问了,就会被 jvm 当垃圾回收,原数组空间就会被回收。
public class Test {
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5};//最开始的数组,我们需要对其进行扩容
int[] newArr = new int[arr.length + 1];
//这里我们创建一个新的数组,并开辟一个新空间,新空间比原来的数组的大小大一,这里就是扩容
//然后将原来的数组中的元素依次赋值到新数组中
for(int i = 0; i < arr.length; i++) {
newArr[i] = arr[i];
}
//然后就是要添加扩容的数字
int addNum = 10;
//将要添加的数组放到新数组最后
newArr[newArr.length - 1] = addNum;
//然后将新的数组的引用变量赋值给原来的数组引用变量,
//这样原来的数组引用变量就可以访问这个新的扩容好的数组,
//然后原来的数组空间无法再次被引用,就被jvm当作垃圾回收掉了,其空间就被回收了
arr = newArr;
//这里打印扩容后的数组,使用原来的数组引用变量访问,发现数组扩容成功
for(int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
}
}
运行结果:
可以发现这里实现了数组扩容,然后将要添加的数字添加到了扩容好的新数组最后。