深入浅出Java数组:从基础到实战的编程练习指南68


作为一名专业的程序员,我深知数据结构在软件开发中的基石作用。而在众多数据结构中,数组无疑是最基础、最常用的一种。无论您是Java初学者还是希望巩固基础的开发者,掌握数组的精髓都至关重要。本文将从Java数组的基础概念出发,结合丰富的实战练习,帮助您系统地理解和熟练运用数组,解决实际编程问题。我们将一起探索数组的声明、初始化、遍历、常见操作以及使用时的注意事项,最终目标是让您能够灵活地使用Java数组。

Java数组基础回顾

在深入练习之前,我们先快速回顾一下Java数组的核心概念。

什么是数组?


数组是存储同类型数据元素的集合,这些元素在内存中是连续存放的。在Java中,数组是对象,这意味着它拥有自己的方法和属性,例如length属性。

数组的声明与初始化


声明数组有两种方式:
// 方式一:推荐,类型[] 数组名
int[] numbers;
// 方式二:C/C++风格,类型 数组名[]
String names[];

数组的初始化是指为数组分配内存空间并为其元素赋初值。Java提供了多种初始化方式:
动态初始化:在声明数组时,只指定数组的长度,系统会为每个元素赋默认值。

整型 (byte, short, int, long) 默认值为 0
浮点型 (float, double) 默认值为 0.0
字符型 (char) 默认值为 '\u0000' (空字符)
布尔型 (boolean) 默认值为 false
引用类型 (如 String, 自定义对象) 默认值为 null


int[] ages = new int[10]; // 声明一个长度为10的int数组,元素默认为0
String[] cities = new String[5]; // 声明一个长度为5的String数组,元素默认为null


静态初始化:在声明数组的同时,为所有元素赋指定的值。数组的长度由元素的个数决定。

int[] primes = {2, 3, 5, 7, 11}; // 声明并初始化一个包含5个素数的int数组
String[] weekdays = new String[]{"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}; // 完整写法



访问数组元素


数组的元素通过索引(下标)来访问,索引从0开始,到数组长度-1结束。

int[] data = {10, 20, 30};
(data[0]); // 访问第一个元素,输出10
(data[2]); // 访问第三个元素,输出30
// (data[3]); // 这会抛出 ArrayIndexOutOfBoundsException 异常,因为索引超出范围

数组的长度


所有Java数组都含有一个公共的length属性,用于获取数组的长度(即元素的数量)。

int[] scores = new int[5];
("数组的长度是:" + ); // 输出 5

数组的遍历与操作

遍历数组是执行各种操作的基础。Java提供了两种主要的遍历方式。

使用for循环遍历


这是最传统、最灵活的遍历方式,可以通过索引访问每个元素。

int[] nums = {1, 2, 3, 4, 5};
("使用for循环遍历:");
for (int i = 0; i < ; i++) {
("元素[" + i + "]:" + nums[i]);
}

使用增强for循环(foreach循环)遍历


增强for循环简化了遍历过程,尤其适用于只需要访问元素值而不需要知道索引的情况。

String[] fruits = {"Apple", "Banana", "Cherry"};
("使用增强for循环遍历:");
for (String fruit : fruits) {
("水果:" + fruit);
}

实战练习:从入门到进阶

理论知识只是基础,实践才是王道。接下来,我们通过一系列由简到繁的练习来巩固和提升对Java数组的理解与运用。

练习1:计算数组元素的总和与平均值


问题描述:给定一个整数数组,计算所有元素的总和以及平均值。

思路分析:
1. 声明一个变量用于存储总和,初始值为0。
2. 遍历数组,将每个元素累加到总和变量中。
3. 遍历结束后,用总和除以数组的长度即可得到平均值。注意,为了得到精确的平均值,总和或数组长度需要转换为浮点类型。
public class ArraySumAverage {
public static void main(String[] args) {
int[] numbers = {10, 20, 30, 40, 50};

// 计算总和
int sum = 0;
for (int i = 0; i < ; i++) {
sum += numbers[i];
}

// 计算平均值
// 注意类型转换,以避免整数除法截断
double average = (double) sum / ;

("数组元素总和:" + sum);
("数组元素平均值:" + average);
}
}

练习2:查找数组中的最大值与最小值


问题描述:给定一个整数数组,找出其中的最大值和最小值。

思路分析:
1. 首先假设数组的第一个元素就是最大值和最小值。
2. 从第二个元素开始遍历数组。
3. 在遍历过程中,如果当前元素大于已知的最大值,则更新最大值;如果当前元素小于已知的最小值,则更新最小值。
public class ArrayMinMax {
public static void main(String[] args) {
int[] data = {8, 15, 3, 21, 10, 5, 25};

// 假设第一个元素为最大值和最小值
int max = data[0];
int min = data[0];

// 从第二个元素开始遍历
for (int i = 1; i < ; i++) {
if (data[i] > max) {
max = data[i]; // 更新最大值
}
if (data[i] < min) {
min = data[i]; // 更新最小值
}
}

("数组中的最大值:" + max);
("数组中的最小值:" + min);
}
}

练习3:数组元素的查找(线性查找)


问题描述:给定一个整数数组和一个目标值,查找目标值在数组中第一次出现的位置(索引),如果不存在则返回-1。

思路分析:
1. 遍历数组的每个元素。
2. 在遍历过程中,将当前元素与目标值进行比较。
3. 如果找到匹配的元素,立即返回其索引。
4. 如果遍历完整个数组仍未找到,则返回-1表示未找到。
public class ArraySearch {
public static void main(String[] args) {
int[] numbers = {4, 7, 2, 9, 1, 7, 5};
int target = 7;
int target2 = 10;

int index = -1; // 默认值为-1,表示未找到
for (int i = 0; i < ; i++) {
if (numbers[i] == target) {
index = i; // 找到目标值,记录索引
break; // 找到第一个后即可停止查找
}
}

if (index != -1) {
("元素 " + target + " 第一次出现在索引 " + index);
} else {
("元素 " + target + " 未在数组中找到");
}
// 查找另一个不存在的元素
index = -1;
for (int i = 0; i < ; i++) {
if (numbers[i] == target2) {
index = i;
break;
}
}
if (index != -1) {
("元素 " + target2 + " 第一次出现在索引 " + index);
} else {
("元素 " + target2 + " 未在数组中找到");
}
}
}

练习4:数组的反转


问题描述:给定一个数组,将其中的元素进行反转,即第一个元素和最后一个元素交换,第二个和倒数第二个交换,以此类推。

思路分析:
1. 使用两个指针,一个指向数组的开始(start),另一个指向数组的结束(end)。
2. 在start小于end的循环中,交换start和end位置的元素。
3. 每次交换后,start向后移动一位,end向前移动一位。
4. 当start不再小于end时,反转完成。
import ; // 引入Arrays工具类以便打印数组
public class ArrayReverse {
public static void main(String[] args) {
int[] originalArray = {1, 2, 3, 4, 5};
("原始数组:" + (originalArray));

int start = 0;
int end = - 1;

while (start < end) {
// 交换元素
int temp = originalArray[start];
originalArray[start] = originalArray[end];
originalArray[end] = temp;

// 移动指针
start++;
end--;
}

("反转后的数组:" + (originalArray));
}
}

练习5:过滤数组中的偶数/奇数


问题描述:给定一个整数数组,创建一个新数组,其中只包含原数组中的偶数(或奇数)。

思路分析:
1. 首先,我们需要知道新数组的长度。这可以通过遍历原数组,统计偶数(或奇数)的个数来确定。
2. 创建一个新的数组,其长度为统计出的偶数(或奇数)个数。
3. 再次遍历原数组,将符合条件的元素拷贝到新数组中。

进阶提示:在实际开发中,如果新数组长度不确定,或者需要频繁增删元素,更推荐使用ArrayList等集合类型。但此处我们只使用数组来完成。
import ;
public class ArrayFilter {
public static void main(String[] args) {
int[] numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

// 步骤1:统计偶数个数
int evenCount = 0;
for (int number : numbers) {
if (number % 2 == 0) {
evenCount++;
}
}

// 步骤2:创建新数组
int[] evenNumbers = new int[evenCount];

// 步骤3:填充新数组
int index = 0;
for (int number : numbers) {
if (number % 2 == 0) {
evenNumbers[index] = number;
index++;
}
}

("原始数组:" + (numbers));
("过滤后的偶数数组:" + (evenNumbers));
// 类似地,可以过滤奇数
int oddCount = 0;
for (int number : numbers) {
if (number % 2 != 0) {
oddCount++;
}
}
int[] oddNumbers = new int[oddCount];
index = 0;
for (int number : numbers) {
if (number % 2 != 0) {
oddNumbers[index] = number;
index++;
}
}
("过滤后的奇数数组:" + (oddNumbers));
}
}

练习6:移除数组中的重复元素


问题描述:给定一个整数数组,创建一个新数组,其中只包含原数组中的唯一元素(不重复)。

思路分析:
1. 一种简单的方法是使用一个辅助数据结构,例如HashSet,它的特性就是不允许重复元素。将数组中的所有元素添加到HashSet中,然后将HashSet转换回数组。
2. 如果不允许使用集合,则需要更复杂的双重循环或先排序再去重。这里为了实用性,我们演示使用HashSet的方法。
import ;
import ;
import ;
public class ArrayRemoveDuplicates {
public static void main(String[] args) {
int[] numbersWithDuplicates = {1, 2, 3, 2, 4, 5, 1, 6, 3};
("原始数组(含重复):" + (numbersWithDuplicates));

// 使用HashSet存储唯一元素
Set<Integer> uniqueElements = new HashSet<>();
for (int number : numbersWithDuplicates) {
(number); // HashSet会自动处理重复元素
}

// 将HashSet转换回数组
int[] uniqueArray = new int[()];
int index = 0;
for (int element : uniqueElements) {
uniqueArray[index] = element;
index++;
}

("移除重复后的数组:" + (uniqueArray));
// 注意:HashSet不保证元素的顺序。如果需要保持原始相对顺序,则需要其他方法。
// 例如:使用LinkedHashSet可以保持插入顺序,或者手动双重循环检查。
}
}

练习7:二维数组的创建与遍历(矩阵操作)


问题描述:创建一个3x3的二维整数数组,并初始化其元素,然后将其打印出来。

思路分析:
1. 二维数组可以看作是“数组的数组”。声明时需要指定行数和列数。
2. 初始化可以通过嵌套的大括号完成,或者通过嵌套循环逐个赋值。
3. 遍历二维数组需要使用嵌套循环,外层循环控制行,内层循环控制列。
public class TwoDArray {
public static void main(String[] args) {
// 声明并初始化一个3x3的二维数组
int[][] matrix = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};

("二维数组(矩阵)内容:");
// 遍历二维数组
for (int i = 0; i < ; i++) { // 外层循环控制行
for (int j = 0; j < matrix[i].length; j++) { // 内层循环控制列
(matrix[i][j] + " ");
}
(); // 每行结束后换行
}
// 动态创建并赋值
("动态创建并赋值的2D数组:");
int[][] dynamicMatrix = new int[2][3]; // 2行3列
int value = 10;
for (int i = 0; i < ; i++) {
for (int j = 0; j < dynamicMatrix[i].length; j++) {
dynamicMatrix[i][j] = value++;
(dynamicMatrix[i][j] + " ");
}
();
}
}
}

练习8:使用``工具类


问题描述:探索类提供的实用方法,如排序、复制、填充和转换为字符串。

思路分析:Arrays类是Java标准库中专门用于操作数组的工具类,提供了许多静态方法,可以极大地简化数组操作。熟练使用它是专业程序员的必备技能。
import ; // 必须导入
public class ArraysUtility {
public static void main(String[] args) {
int[] numbers = {5, 2, 8, 1, 9, 3};
("原始数组:" + (numbers)); // toString() 将数组转换为字符串
// 1. 排序:()
(numbers); // 对数组进行升序排序
("排序后数组:" + (numbers));
// 2. 复制:()
int[] newNumbers = (numbers, ); // 复制整个数组
int[] partialCopy = (numbers, 3); // 复制前3个元素
("复制后的数组:" + (newNumbers));
("部分复制数组:" + (partialCopy));
// 3. 填充:()
int[] filledArray = new int[5];
(filledArray, 7); // 将数组所有元素填充为7
("填充后的数组:" + (filledArray));
// 4. 比较:()
int[] arr1 = {1, 2, 3};
int[] arr2 = {1, 2, 3};
int[] arr3 = {3, 2, 1};
("arr1 和 arr2 是否相等?" + (arr1, arr2)); // true
("arr1 和 arr3 是否相等?" + (arr1, arr3)); // false
// 5. 查找(二分查找):()
// 注意:使用binarySearch前,数组必须是已排序的
int[] sortedArray = {10, 20, 30, 40, 50};
int searchIndex = (sortedArray, 30);
("30 在 sortedArray 中的索引:" + searchIndex); // 2
searchIndex = (sortedArray, 25);
("25 在 sortedArray 中的索引 (未找到):" + searchIndex); // 负数表示插入点
}
}

数组使用注意事项与最佳实践

在使用数组时,除了掌握基本操作,了解其特性和潜在问题也同样重要。

数组长度固定


Java数组一旦创建,其长度就不能改变。如果您需要一个长度可变的数据结构,应该考虑使用Java集合框架中的ArrayList、LinkedList等。

`ArrayIndexOutOfBoundsException`


这是数组操作中最常见的运行时错误。当您尝试访问的数组索引超出其有效范围(0到length-1)时,就会抛出此异常。务必在循环和索引访问时检查边界。

初始化陷阱:null与空数组


声明一个数组变量但未初始化(int[] numbers;)时,它只包含一个null引用。此时如果尝试访问其length属性或元素,会抛出NullPointerException。

int[] myArr = null;
// (); // NullPointerException
int[] emptyArr = new int[0]; // 这是一个长度为0的数组,是合法的空数组,不是null
(); // 0

深拷贝与浅拷贝


当数组中存储的是引用类型时,直接赋值(如arr2 = arr1;)只是将arr2指向了arr1所引用的同一个数组对象,这称为浅拷贝。修改arr2中的元素会影响到arr1。
如果您需要完全独立的数组副本,需要进行深拷贝,即创建新数组并逐个复制元素,或者使用()等方法。

int[] original = {1, 2, 3};
int[] shallowCopy = original; // 浅拷贝:两个引用指向同一个数组
shallowCopy[0] = 99;
(original[0]); // 输出 99,因为它们是同一个数组
int[] deepCopy = (original, ); // 深拷贝
deepCopy[0] = 100;
(original[0]); // 输出 99,original不受影响

何时考虑使用集合(Collection)


虽然数组基础且高效,但在以下场景中,Java集合框架(如ArrayList、LinkedList、HashSet、HashMap等)通常是更好的选择:
需要动态调整大小的场景。
需要更丰富的数据操作(如排序、查找、去重等,集合提供了更多内置方法)。
需要存储不同类型数据或键值对的场景(Map)。

数组和集合各有优劣,根据具体需求选择合适的数据结构是专业程序员的必备素养。

通过本文的学习和练习,我们全面回顾了Java数组的基础知识,并完成了从求和、查找、反转到去重、二维数组操作等一系列实战练习,最后还探讨了数组使用中的注意事项和最佳实践。数组是Java编程的基石,熟练掌握它将为您构建更复杂的数据结构和算法打下坚实的基础。持续的练习和思考是提升编程能力的不二法门。希望这些练习能帮助您更深入地理解Java数组,并在未来的编程旅程中游刃有余。

推荐您继续探索Java集合框架,它们是数组功能上的强大扩展,能够帮助您应对更多复杂的编程挑战。

2025-10-01


上一篇:Java数组开发全攻略:掌握核心概念与实战技巧

下一篇:Java特殊字符生成与编码实践:从Unicode到实际应用