Java8:Java数组详解:从入门到精通,一文就够了
目录
写在前面
想象这样一个场景:期末考试结束了,老师让你录入班上50名同学的Java成绩。按照我们之前学过的知识,你会怎么做?大概率是定义50个变量:score1、score2、score3……一直到score50。
听起来就很离谱对吧?不仅代码冗长得吓人,而且如果要统计平均分、找最高分,你还得把这50个变量挨个操作一遍。更糟糕的是,如果班级人数增加到100人,难道还要再加50个变量?
显然,我们需要一种更聪明的方式来管理同类型的数据。这就是今天的主角——数组。
一、数组是什么?用生活场景来理解
数组可以理解为一排编号连续的储物柜。每个柜子可以存放一件物品,所有柜子的规格是一样的,而且它们紧密排列在一起。
┌──┬──┬──┬──┬──┬──┐
│ │ │ │ │ │ │
│ 0│ 1│ 2│ 3│ 4│ 5│
└──┴──┴──┴──┴──┴──┘
从这个图可以看出数组的几个核心特征:
-
空间是连续的——柜子一个挨着一个,没有空隙
-
每个位置有编号——编号从0开始,依次递增
-
所有位置存放同类型数据——要么全是书,要么全是文具,不能混着装
在Java的世界里,数组就是相同类型元素的集合,在内存中占据一段连续的空间。
二、数组的创建与初始化
2.1 创建数组
Java创建数组的基本语法如下:
// 语法:类型[] 数组名 = new 类型[长度];
int[] scores = new int[10]; // 创建一个能存10个整数的数组
double[] prices = new double[5]; // 创建一个能存5个小数的数组
String[] names = new String[3]; // 创建一个能存3个字符串的数组
这里的 int[]表示"这是一个int类型的数组",new int[10]表示"在堆上开辟10个连续的int空间"。
2.2 两种初始化方式
动态初始化:先指定长度,后续再赋值。
int[] array = new int[10]; // 先创建,长度为10,默认值都是0
静态初始化:创建的同时直接指定具体内容。
int[] array1 = new int[]{1, 2, 3, 4, 5};
double[] array2 = new double[]{1.0, 2.0, 3.0};
String[] array3 = new String[]{"Hello", "Java", "世界"};
静态初始化还有一种更简洁的写法——省略格式:
int[] array1 = {1, 2, 3, 4, 5};
double[] array2 = {1.0, 2.0, 3.0};
2.3 几个容易踩的坑
坑一:省略格式不能拆分
int[] array;
array = {1, 2, 3}; // 编译报错!省略格式只能在定义时使用
正确的做法是:
int[] array;
array = new int[]{1, 2, 3}; // OK
坑二:不推荐C语言风格的写法
int arr[] = {1, 2, 3}; // 虽然能编译通过,但不推荐
int[]放在一起更能清晰地表达"这是一个int数组类型"的意思。
2.4 默认值:不赋值也有值
如果你创建了数组但没有显式赋值,Java会给每个元素赋予默认值:
|
元素类型 |
默认值 |
|---|---|
|
byte/short/int/long |
0 |
|
float/double |
0.0 |
|
char |
'\u0000'(空字符) |
|
boolean |
false |
|
引用类型(如String) |
null |
三、数组的基本使用
3.1 访问元素:通过下标
数组中的每个元素都有一个唯一的下标(也叫索引),从0开始,到 长度-1结束。
int[] array = {10, 20, 30, 40, 50};
System.out.println(array[0]); // 10
System.out.println(array[1]); // 20
System.out.println(array[4]); // 50
// 也可以修改
array[0] = 100;
System.out.println(array[0]); // 100
特别提醒:下标不能越界。数组长度为5,有效下标是0到4。如果访问 array[5],程序会抛出 ArrayIndexOutOfBoundsException(数组下标越界异常)。
3.2 遍历数组:三种方式
方式一:普通for循环
int[] array = {10, 20, 30, 40, 50};
for (int i = 0; i < array.length; i++) {
System.out.println(array[i]);
}
这里用到了 array.length来获取数组的长度,避免了硬编码。
方式二:for-each循环
int[] array = {10, 20, 30, 40, 50};
for (int x : array) {
System.out.println(x);
}
for-each的优点是语法简洁,不用操心下标和边界条件。但它有一个局限:拿不到当前元素的下标。如果你需要知道元素的位置,还是要用普通for循环。
方式三:借助Arrays工具类
import java.util.Arrays;
int[] array = {10, 20, 30, 40, 50};
System.out.println(Arrays.toString(array));
// 输出:[10, 20, 30, 40, 50]
Arrays.toString()会将数组转换成美观的字符串格式,非常适合调试时快速查看数组内容。
四、理解数组的内存布局
4.1 JVM内存区域的划分
要真正理解数组,必须了解它在内存中是怎么存的。JVM的内存主要分为以下几个区域:
-
程序计数器:记录当前执行到哪条指令
-
虚拟机栈:存储方法调用时的局部变量等信息
-
堆:存放所有new出来的对象(包括数组)
-
方法区:存放类信息、常量、静态变量等
对我们来说,现阶段重点关心栈和堆。
4.2 基本类型 vs 引用类型
int a = 10; // 基本类型变量
int[] arr = new int[]{1, 2, 3}; // 引用类型变量
这两者在内存中的表现完全不同:
-
a是基本类型变量,它的空间中直接存储值10 -
arr是引用类型变量,它的空间中存储的是数组对象在堆上的地址
栈空间:
┌─────────┐
│ a = 10 │
├─────────┤
│ arr ───────┐
└─────────┘ │
▼
堆空间:
┌───┬───┬───┐
│ 1 │ 2 │ 3 │
└───┴───┴───┘
这就是为什么我们说"数组是引用类型"——变量 arr并不直接"包含"数组元素,而是"指向"数组对象。
4.3 null:空引用
int[] arr = null;
System.out.println(arr[0]); // NullPointerException!
null表示"这个引用没有指向任何对象"。试图通过null引用去访问数组元素,会抛出 NullPointerException(空指针异常)。这是Java程序中最常见的异常之一,务必小心。
五、数组作为方法的参数和返回值
5.1 数组作为参数
传基本类型 vs 传数组
public static void main(String[] args) {
int num = 0;
changeNum(num);
System.out.println("num = " + num); // 0,没变
int[] arr = {1, 2, 3};
changeArray(arr);
System.out.println("arr[0] = " + arr[0]); // 100,变了!
}
public static void changeNum(int x) {
x = 10;
}
public static void changeArray(int[] a) {
a[0] = 100;
}
为什么一个变了,一个没变?
-
传基本类型时,传递的是值的副本,方法内部修改不影响外部
-
传数组(引用类型)时,传递的是地址的副本,但两个引用指向同一个数组对象,通过任何一个引用修改数组内容,另一个引用也能看到
5.2 数组作为返回值
方法不仅可以接收数组参数,也可以返回数组:
public static int[] createFibonacci(int n) {
int[] fib = new int[n];
if (n >= 1) fib[0] = 1;
if (n >= 2) fib[1] = 1;
for (int i = 2; i < n; i++) {
fib[i] = fib[i-1] + fib[i-2];
}
return fib;
}
// 调用
int[] result = createFibonacci(10);
System.out.println(Arrays.toString(result));
// [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
六、数组的常见操作
6.1 数组拷贝
int[] original = {1, 2, 3, 4, 5};
// 方式一:使用Arrays.copyOf
int[] copy1 = Arrays.copyOf(original, original.length);
// 方式二:使用System.arraycopy
int[] copy2 = new int[original.length];
System.arraycopy(original, 0, copy2, 0, original.length);
// 方式三:自己实现
public static int[] myCopyOf(int[] arr) {
int[] ret = new int[arr.length];
for (int i = 0; i < arr.length; i++) {
ret[i] = arr[i];
}
return ret;
}
6.2 查找元素
顺序查找:适用于无序数组
public static int find(int[] arr, int target) {
for (int i = 0; i < arr.length; i++) {
if (arr[i] == target) {
return i;
}
}
return -1; // 没找到
}
二分查找:适用于有序数组,效率极高
public static int binarySearch(int[] arr, int target) {
int left = 0;
int right = arr.length - 1;
while (left <= right) {
int mid = (left + right) / 2;
if (target < arr[mid]) {
right = mid - 1;
} else if (target > arr[mid]) {
left = mid + 1;
} else {
return mid;
}
}
return -1;
}
二分查找的核心思想是每次排除一半的数据。对于一个长度为10000的有序数组,最多只需要14次比较就能找到目标。
6.3 冒泡排序
public static void bubbleSort(int[] arr) {
for (int i = 0; i < arr.length; i++) {
for (int j = 1; j < arr.length - i; j++) {
if (arr[j-1] > arr[j]) {
int tmp = arr[j-1];
arr[j-1] = arr[j];
arr[j] = tmp;
}
}
}
}
当然,实际开发中直接用 Arrays.sort(arr)就好,Java内置的排序算法比我们自己写的冒泡高效得多。
6.4 数组逆序
public static void reverse(int[] arr) {
int left = 0;
int right = arr.length - 1;
while (left < right) {
int tmp = arr[left];
arr[left] = arr[right];
arr[right] = tmp;
left++;
right--;
}
}
七、二维数组
7.1 基本用法
二维数组本质上是一个特殊的一维数组——它的每个元素又是一个一维数组。
int[][] arr = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
// 遍历
for (int row = 0; row < arr.length; row++) {
for (int col = 0; col < arr[row].length; col++) {
System.out.print(arr[row][col] + "\t");
}
System.out.println();
}
-
arr.length表示行数 -
arr[row].length表示该行的列数
7.2 不规则二维数组
Java支持不规则二维数组,即每一行的列数可以不同:
int[][] arr = new int[2][];
arr[0] = new int[3]; // 第一行3列
arr[1] = new int[5]; // 第二行5列
这在某些场景下非常灵活,比如存储杨辉三角。
八、总结
今天我们系统地学习了Java数组的方方面面:
-
数组的概念:相同类型元素的连续集合
-
创建和初始化:动态初始化 vs 静态初始化
-
基本操作:下标访问、遍历、长度获取
-
内存原理:引用类型、栈与堆、null
-
方法交互:数组作为参数和返回值
-
常见算法:拷贝、查找、排序、逆序
-
二维数组:规则和不规则两种形态
数组是Java编程的基石,几乎所有的数据结构(列表、队列、栈等)底层都离不开数组。掌握好数组,你就为后续学习更高级的数据结构打下了坚实的基础。
下一篇文章,我们将进入Java面向对象编程的世界——类和对象,敬请期待!
更多推荐


所有评论(0)