Java数组从基础到面试题:手写排序、二分查找、去重与双指针实战

发布时间:2026/10/8 8:36:45
Java数组从基础到面试题:手写排序、二分查找、去重与双指针实战 1. 数组在Java里为什么值得专门写一篇先从一场面试说起上个月我帮团队做了一次初级Java工程师的现场面试其中一个环节是手写一个数组去重。候选人简历上写着两年开发经验项目里用过Spring Boot、MyBatis、Redis结果拿到这道题第一反应是转成List再用Set去重再转回数组。我问他如果不能用集合类只用手写逻辑怎么做他愣了两分钟最终写出来的双重循环还漏了内层移动的逻辑。这个场景我见过太多次了。数组在Java里看起来是最基础不过的东西每个培训班第一周就讲每个开发者都觉得自己会。但真到了面试、笔试、或者工作中要手写一个排序、做一个滑动窗口、解析一段二进制数据的时候数组的掌握程度立刻就能分出高下。数组这东西简单在概念复杂在细节值钱在思维。这篇内容我打算换个讲法。不按教科书那样先给定义再列API而是直接围绕数组练习题这条主线把从基础认知、常见操作、核心算法到面试实战的完整链路过一遍。适用人群很明确正在学Java基础的人、准备技术面试的人以及那些用数组做业务开发但从未深究过底层行为的Java工程师。文章里的代码我全都自己跑过注释和易错点也是从实际调试里抠出来的可以直接当练习材料用。在正式进入练习题之前关于数组有三个层面的认知必须先建立起来否则后面所有的会做都只是背答案换个问法就露馅。1.1 数组的本质一段连续内存一条固定长度的规则数组在JVM里到底是什么它是一段连续的、内存地址相邻的存储空间元素类型统一长度在创建那一刻就固定了。之所以强调连续和固定是因为这两个特性决定了数组的一切优劣连续内存让下标访问的时间复杂度是O(1)通过首地址加偏移量直接算出目标位置不需要像链表那样从头遍历固定长度则意味着数组没法动态伸缩想变大只能重新new一个更长的数组再把旧数据拷贝过去。这里有个生活化类比。数组就像电影院的一排座位座位号从0开始编号。你要找第5个座位直接按号走过去就行不用数前4个座位坐的是谁。但这排座位的数量是装修时定死的想加座位只能拆掉重装一排。ArrayList之所以能无限加元素本质就是内部维护了一个会自动扩容的数组扩容那一刻做的工作就是拆掉重装再把人挪过去。还有一个很多初学者容易忽略的点Java的数组是个对象不是一组简单的变量。这意味着数组类型属于引用类型它被声明时栈上存的是引用真正的数组对象在堆内存里。如果你把数组赋值给另一个变量它们指向的是同一块堆内存修改任何一个变量视角下的元素另一个也会跟着变。这个特性在后面练习题里会反复踩到。1.2 Java数组和C/C数组的差异别把别的语言的习惯带进来如果你之前学过C语言那到了Java里有两件事必须纠正否则代码里全是隐蔽问题。第一Java数组自带length属性注意是属性不是方法写arr.length不是arr.length()。这个属性在创建数组时被JVM初始化好之后不可变。C语言里数组没有长度信息边界全靠程序员自己保证——这也是为什么热搜词里有c语言数组指针移动指定位输出字符这种问题C程序员得拿指针做算术才能遍历数组而Java直接用下标就行。第二Java数组有运行时越界检查。访问arr[5]而数组长度只有3时JVM会抛出ArrayIndexOutOfBoundsException程序直接终止。这个机制在C里不存在越界读到的可能是别的变量的内存。很多从C转Java的人一开始会抱怨性能浪费但实际上这个检查复杂度是O(1)的是现代语言用极微小的代价换来的安全性实际开发中它救命的次数远大于损耗的性能。1.3 站在整体视角看数组它是无数框架的底层骨架写业务代码的时候你很少会直接面对数组。你用ArrayList、用HashMap、用消息队列它们内部其实都是数组或基于数组实现的。ArrayList的底层是Object[]HashMap的底层是Node[]数组加链表加红黑树线程池的任务队列是Object[]Netty的缓冲区更是围绕字节数组做文章。理解了数组你再看这些上层容器会突然豁然开朗——原来扩容、哈希、寻址这些听起来高大上的概念底子都是连续内存下标计算。这也是为什么面试官那么爱考数组。它不是后端八股文里最难的知识点但它是检验一个人计算机基础是否扎实的试金石。数组玩明白了HashMap的扩容机制、ArrayList的删除效率问题、Redis跳表为什么不用数组你都能自己想通一大半。2. 基础操作里的易错细节初始化、遍历、拷贝与比较在动手刷练习题之前先把数组的基础操作过一遍。说是基础但每一个细节点我都见过有人在面试或者代码评审里出问题。2.1 三种初始化方式与默认值陷阱Java数组有静态初始化和动态初始化两种最常用的写法概念上不复杂// 静态初始化声明时直接填入元素 int[] arr1 new int[] {1, 2, 3, 4, 5}; // 简化写法声明和赋值写在同一行时可以省略new int[] int[] arr2 {1, 2, 3, 4, 5}; // 动态初始化只指定长度元素由JVM给默认值 int[] arr3 new int[5]; String[] arr4 new String[3];动态初始化的默认值规则是考察基础知识时的高频陷阱。JVM会根据数组元素类型填充默认值int[]、short[]、byte[]、long[]的默认值是0float[]和double[]对应0.0f和0.0boolean[]默认是falsechar[]默认是\u0000也就是空字符引用类型数组比如String[]、Object[]自定义类数组默认是null。这个差异最坑人的地方在于基本类型和包装类型的混淆。下面这段代码int[] nums new int[5]; // 都是0 Integer[] nums2 new Integer[5]; // 都是null不是0int[]可以用循环直接累加因为初始值是0Integer[]如果直接累加大概率空指针因为每个元素都是null。实际开发中统计一批数量、初始化固定大小的缓存时要注意你声明的是基本类型数组还是包装类型数组这个选择直接影响默认值行为。2.2 遍历的两种姿势和打印手段遍历数组最常用的两种方式普通for循环和增强for。int[] arr {10, 20, 30, 40, 50}; // 普通for需要下标参与运算 for (int i 0; i arr.length; i) { System.out.println(下标 i 对应的值是 arr[i]); } // 增强for只关心元素本身 for (int num : arr) { System.out.println(num); }注意这两者的选择不是随意的。如果循环体里需要当前位置这个信息比如数组反转、两两交换、每隔一个取一个必须用普通for。增强for拿不到下标它适合纯粹看看里面有什么的场景。增强for在字节码层面实际会编译成迭代器模式对数组来说是一次遍历效率并不差但改元素的时候要注意——增强for里修改循环变量不会影响原数组因为int num拷贝的是值不是引用。打印数组也有坑。直接System.out.println(arr)输出的是[I1b6d3586这种类型哈希值的东西不是里面的内容。想看内容要用Arrays.toString(arr)import java.util.Arrays; int[] arr {1, 2, 3}; System.out.println(Arrays.toString(arr)); // 输出 [1, 2, 3]多维数组要用Arrays.deepToString()否则子数组打印出来的仍然是一串地址。2.3 数组拷贝System.arraycopy和Arrays.copyOf怎么选实际开发里数组扩容本质上就是拷贝。拷贝方式有好几种但重点掌握两个// 方式一System.arraycopy本地方法性能最优 int[] src {1, 2, 3, 4, 5}; int[] dest new int[5]; System.arraycopy(src, 0, dest, 0, src.length); // 方式二Arrays.copyOf底层也调System.arraycopy带返回值 int[] copy Arrays.copyOf(src, src.length); // copyOf还可以顺便扩容把旧数组元素拷贝过去多出来的位置补默认值 int[] bigger Arrays.copyOf(src, 10);从源码层面看Arrays.copyOf内部也是调用System.arraycopy区别是copyOf更高维要自己传入新长度并且返回新数组适合扩容这种语义System.arraycopy则适合在两个已存在的数组间指定偏移拷贝。日常开发里需要扩容数组时用Arrays.copyOf就够了不需要自己手动new再循环拷贝。这里还要提一个常用的工具Arrays.copyOfRange()它可以按起止下标截取数组的一段返回值是新的数组。这个API在算法题里处理子数组时很好用但要注意它截取的是浅拷贝——如果数组里放的是引用类型拷贝出来的是引用本身修改对象属性会影响原数组里的对象。2.4 数组比较比的是引用不是内容新手最常犯的错误之一是比较两个数组内容是否相同int[] a {1, 2, 3}; int[] b {1, 2, 3}; System.out.println(a b); // false两个不同对象 System.out.println(a.equals(b)); // false数组没有重写equals System.out.println(Arrays.equals(a, b)); // true比较的是逐个元素a b判断的是两个引用是否指向同一个对象a.equals(b)在数组上没有重写效果等同于。正确做法是用Arrays.equals()它会在底层遍历元素逐一比较。多维数组则用Arrays.deepEquals()。这个点几乎每次面试都有人答错值得记牢。3. 必须练到手的四个手写操作排序、查找、去重、反转基础操作之后进入正题。说句实话数组真正有价值的练习不在于记API而在于你能不能用裸数组完成经典算法。下面这四个操作是我认为Java数组学习里绕不开的必修题。每道题我给出手写实现、复杂度分析以及运行过程中容易忽略的边界。3.1 冒泡排序从能写到能写出优化版冒泡排序是排序算法里的入门题也是面试官判断你基本功的第一道关卡。最基本的写法是双重for循环外层控制轮数内层做相邻比较和交换public static void bubbleSort(int[] arr) { if (arr null || arr.length 2) { return; } int n arr.length; // 外层需要比较n-1轮 for (int i 0; i n - 1; i) { // 内层每轮比较的范围逐渐缩小因为末尾的元素已经就位 for (int j 0; j n - 1 - i; j) { if (arr[j] arr[j 1]) { int temp arr[j]; arr[j] arr[j 1]; arr[j 1] temp; } } } }这个版本的时间复杂度是O(n²)。但有一个明显的优化空间如果某一轮内层循环里一次交换都没发生说明整个数组已经有序后续轮次全是白跑。优化版本引入一个标志位public static void bubbleSortOptimized(int[] arr) { if (arr null || arr.length 2) { return; } int n arr.length; for (int i 0; i n - 1; i) { boolean swapped false; for (int j 0; j n - 1 - i; j) { if (arr[j] arr[j 1]) { int temp arr[j]; arr[j] arr[j 1]; arr[j 1] temp; swapped true; } } // 这一轮没有发生交换说明整体有序提前结束 if (!swapped) { break; } } }这个优化在最好情况数组已经有序下能把时间复杂度降到O(n)。面试时能写出优化版通常会让面试官印象分加一些因为这说明你理解了这个算法每一步在做什么而不是只背了个模板。实际开发中我几乎不会手写冒泡排序因为Arrays.sort在数组长度小于47时会走插入排序大于写阈值时走快速排序的双轴版本性能和稳定性都远超手写。但练习阶段必须手写因为排序是训练双层循环元素交换边界控制的最佳载体。3.2 二分查找手写版本和Arrays的现成实现二分查找的前提是数组有序。它的核心思维是每次缩小一半搜索范围时间复杂度O(log n)。手写版本如下public static int binarySearch(int[] arr, int target) { if (arr null) { return -1; } int left 0; int right arr.length - 1; while (left right) { // 注意用 left (right - left) / 2 防止溢出 int mid left (right - left) / 2; if (arr[mid] target) { return mid; } else if (arr[mid] target) { left mid 1; } else { right mid - 1; } } return -1; // 没找到 }这里有几个细节值得讲清楚。为什么用left (right - left) / 2而不是(left right) / 2因为当数组长度极大时left right可能超过int的表示范围产生溢出。虽然日常练习很少遇到那么大的数组但这个写法是业界标准习惯面试官会注意。while循环的条件为什么是left right而不是left right因为我想让搜索区间永远是闭区间[left, right]。当left right时数组里还剩最后一个元素没判断必须再进一次循环。如果你写的是left right循环结束时left和right可能落在target的位置需要额外判断。Arrays.binarySearch()是Java提供的现成API但它的返回值有个坑找到了返回下标找不到返回的是-(插入点) - 1插入点指如果要把目标插入到有序数组里的下标位置。比如在{1, 3, 5, 7}里搜索4返回-3含义是插入点是2取负再减1得到-3。写代码判断结果时要记住if (index 0)代表找到了index 0代表没找到并且可以算出插入位置。3.3 数组去重三种思路对应三种复杂度数组去重是我在面试里最爱出的热身题因为它解法多、考察面广。这里介绍三种思路。思路一双重循环暴力去重。外层遍历元素内层检查它是否已经保存在结果区里。空间复杂度O(1)时间复杂度O(n²)。适合没学过集合类的基础阶段但不推荐在工程中使用。public static int[] removeDuplicatesByLoop(int[] arr) { int n arr.length; int[] temp new int[n]; int count 0; for (int i 0; i n; i) { boolean isDuplicate false; for (int j 0; j count; j) { if (arr[i] temp[j]) { isDuplicate true; break; } } if (!isDuplicate) { temp[count] arr[i]; } } return Arrays.copyOf(temp, count); }思路二利用集合去重。把数组转成List再放进HashSet最后转回数组。代码只有三行时间复杂度O(n)空间换时间。这是工程中最常用的做法我会在第五章详细分析它是怎么回事。思路三有序数组去重这也是LeetCode 26题的经典解法。如果数组是有序的重复元素一定相邻可以用双指针原地去重空间复杂度O(1)时间复杂度O(n)public static int removeDuplicatesSorted(int[] nums) { if (nums.length 0) { return 0; } // slow指向已去重部分的最后一个位置 int slow 1; for (int fast 1; fast nums.length; fast) { if (nums[fast] ! nums[slow - 1]) { nums[slow] nums[fast]; slow; } } return slow; // 新的长度 }这个解法背后的思考值得反复体会既然重复元素相邻那我只需要一个指针fast往前探路另一个指针slow负责在不重复的位置落子。每次fast发现一个新值就把它写到slow的位置上slow再往后走一格。最后slow的值就是去重后的长度。这个双指针思想贯穿了大量数组/字符串题目比如移动零、移除元素、合并有序数组练熟这一题等于练熟一类题。3.4 数组反转双指针的入门第一课数组反转也是最基础的操作之一它天然适合双指针解法public static void reverse(int[] arr) { int left 0; int right arr.length - 1; while (left right) { int temp arr[left]; arr[left] arr[right]; arr[right] temp; left; right--; } }这个代码很短但有几个点容易出错交换用临时变量时要注意交换完后指针必须移动否则死循环循环条件是left right不是left right因为两个指针相遇时不需要交换如果题目要求反转前k个元素或反转区间[left, right]需要把左右边界作为参数传入而不是在方法内部硬编码0和arr.length - 1。反转这个操作看似简单但它是很多复杂题目的零件。比如旋转数组LeetCode 189、判断回文字符串、翻转字符串里的单词底层都能拆成局部反转整体反转的组合。建议练反转时顺便想一个额外的问题如果数组里是对象比如String[]反转逻辑需要改吗不需要因为交换的只是引用不是对象本身。4. 二维数组数组的数组以及它带来的三重认知升级二维数组是数组学习的第一个坎。很多人能理解一维数组是一条线但二维数组到底在内存里长什么样、为什么遍历方式不同效率差很多往往说不清楚。4.1 二维数组的本质每个元素本身是一个一维数组Java的二维数组在概念上可以这样理解int[][] matrix是一个数组它的每一个元素都是int[]类型。也就是说层面上存在一个外层数组里面装的是一堆内层数组的引用。创建二维数组的常见方式// 方式一标准矩形二维数组 int[][] matrix new int[3][4]; // 3行4列默认值全是0 // 方式二静态初始化 int[][] matrix2 { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} }; // 方式三不规则数组锯齿数组 int[][] triangle new int[3][]; triangle[0] new int[4]; triangle[1] new int[2]; triangle[2] new int[5];方式三里要留意new int[3][]只创建了外层数组内层数组都是null必须逐个new出来才能用。这种不规则数组在Java里是合法的每一行的列数可以不同。这和C语言完全不同——C的二维数组是连续内存块每一行列数必须相同Java因为是引用数组天然支持锯齿结构。实际工程里锯齿数组用得不算多但在动态规划记忆化搜索、三角形路径求和这类算法题里经常出现值得认识它。4.2 行列遍历的顺序两个for循环的顺序没那么随意二维数组最常见的遍历写法for (int i 0; i matrix.length; i) { // 遍历行 for (int j 0; j matrix[i].length; j) { // 遍历列 System.out.print(matrix[i][j] ); } System.out.println(); }注意matrix.length得到的是行数matrix[i].length得到的是第i行的列数。不能用matrix[0].length代替每一行的列数尤其在锯齿数组里各行长度不同必须用matrix[i].length。按行优先遍历和按列优先遍历实际性能有差异。因为在JVM内存布局里同一行的元素在堆内存里是连续的按行访问可以尽量命中CPU缓存按列访问则每次都要跳到另一行缓存命中率下降。数据量大时这两种遍历的耗时差可以非常明显。我在一次处理上千行数据的矩阵计算中实测过按列遍历比按行遍历慢了大约30%。这个点面试官偶尔会问理解这个原因对你理解遍历顺序影响性能很有帮助。4.3 实操题目矩阵转置与螺旋遍历手写矩阵转置是二维数组最重要的练习题之一。转置的核心规则是result[j][i] matrix[i][j]public static int[][] transpose(int[][] matrix) { int rows matrix.length; int cols matrix[0].length; // 转置后行列互换注意新数组的行数是原来的列数 int[][] result new int[cols][rows]; for (int i 0; i rows; i) { for (int j 0; j cols; j) { result[j][i] matrix[i][j]; } } return result; }这里最容易出错的地方是创建新数组时把行列写反原数组是rows x cols转置后一定是cols x rows赋值时result[j][i]。一旦写成result[i][j]要么越界要么结果全错。螺旋矩阵遍历是二维数组里较进阶的练习它要求按右-下-左-上的顺序绕圈打印。虽然代码较长但核心思想是维护四个边界top、bottom、left、right每走完一条边就收缩对应的边界直到全部遍历完。这个题的难点在于边界条件的判断——什么时候停止、什么时候收缩、什么时候走出死循环。建议先自己在纸上画一个3×3的矩阵模拟一遍再动手写。5. 数组和集合怎么选从ArrayList的底层逻辑看可变数组5.1 数组和ArrayList的对比固定长度换来的性能很多初学者纠结过一个基础问题既然ArrayList功能更全、还能动态扩容为什么还要用数组我的回答是因为固定长度本身就是一个特性。数组在创建时就知道自己的容量JVM可以一次性分配连续内存没有扩容检查没有装箱拆箱如果是基本类型数组访问速度更快内存占用更紧凑。ArrayList虽然方便但它内部每次扩容时都要新建数组再拷贝拷贝本身是O(n)的如果频繁扩容代价会被放大。两者选择的经验法则维度数组ArrayList长度固定动态扩容基本类型存储支持int[]无装箱必须用Integer有装箱拆箱成本随机访问O(1)O(1)中间插入/删除手动搬移元素麻烦API自带但也要搬移O(n)额外内存开销几乎没有会有扩容预留的空位和泛型结合不能直接创建泛型数组完美支持泛型在数据量明确、不需要增删、偏性能敏感的场景比如固定大小的缓冲区、图算法的邻接表表示、DP数组直接用数组在业务开发里数据量不确定、频繁增删、需要使用集合API时用ArrayList。我个人的习惯是算法题和底层工具优先数组日常业务代码优先List。5.2 数组转List和List转数组两个藏坑的API数组和List互相转换是高频操作但两行代码里都有坑。数组转List的标准写法String[] arr {a, b, c}; ListString list Arrays.asList(arr); // 注意不要用 list.add(d)会抛 UnsupportedOperationExceptionArrays.asList()返回的是一个固定长度的List视图它直接套在原数组上所以不能调add和remove。而且视图里的元素和原数组共享引用修改list里的元素原数组也会变。如果你想得到一个真正独立的、可增删的ArrayList要这样写ListString list new ArrayList(Arrays.asList(arr));List转数组有两个重载ListString list new ArrayList(); list.add(a); String[] arr1 list.toArray(new String[0]); // 推荐 String[] arr2 list.toArray(new String[list.size()]); // 也可以关于toArray(new String[0])和toArray(new String[list.size()])的争论业界目前的共识是传new String[0]更清晰而且从JDK 6起当传入数组容量不足时toArray会通过反射重新创建合适大小的数组性能差异几乎可忽略。至于toArray()无参版本它返回的是Object[]通常需要强转不建议用来转泛型List。5.3 手写一个简易ArrayList理解扩容机制最直接的方式如果在理解了数组之后想真正弄懂ArrayList的扩容原理最好的办法是自己手写一个极简版public class SimpleArrayList { private int[] data; private int size; // 当前有效元素个数 public SimpleArrayList() { data new int[10]; // 初始容量10 size 0; } public void add(int value) { if (size data.length) { grow(); } data[size] value; } private void grow() { // 经典的1.5倍扩容 int newCapacity data.length (data.length 1); data Arrays.copyOf(data, newCapacity); } public int get(int index) { if (index 0 || index size) { throw new IndexOutOfBoundsException(越界); } return data[index]; } public int size() { return size; } }这个简化版把ArrayList最核心的两个设计体现出来了size和data.length是两回事data.length是容量size才是实际元素个数扩容时选择新容量是原来的1.5倍这是为了平衡总扩容次数和每次浪费的空间。测一下运行过程会发现当元素不断添加时扩容操作发生的频率越来越低——从10扩到15再扩到22再扩到33每次扩容后能容纳更多新元素。这就是动态数组均摊O(1)复杂度的来源虽然单次扩容是O(n)但n次添加的总代价大约仍是O(n)级别平均下来每次添加是O(1)。6. 练习题验收清单从入门到面试级的自测题目写了这么多最后给一个可以直接自测的练习题清单。每道题我都会标注它考察的核心能力、常见解法思路和易错点。建议别急着看答案先在编辑器里把代码写出来跑一遍。6.1 入门级数组求和、最大值、平均值这三道题本质是同一类——遍历数组并维护一个累积结果变量。求和就是累加最大值就是逐个比较并更新平均值是求和除以长度。它们考察的基本功是循环和变量的生命周期理解。最大的易错点是空数组处理长度为0时求最大值会报错要先判断arr.length 0。这类题的进阶变化是不取整个数组而是取某个子区间的和那就需要引入前缀和思想不过那是另一个话题了。6.2 进阶级移动零、合并两个有序数组、两数之和移动零LeetCode 283是双指针练习题里最典型的一道。要求把数组里的0全部移到末尾同时保持非0元素的相对顺序。解法是用一个慢指针指向下一个非0元素应该放的位置快指针遍历遇到非0就放到慢指针位置并后移。循环结束后慢指针后面的位置全部填0。合并两个有序数组LeetCode 88考的是从后往前填充的思想避免了额外开数组。思路是两个数组都从末尾开始比较较大的放到结果数组的末尾然后指针前移。核心易错点是循环结束后可能有剩余元素没有处理完——如果第一个数组剩了没问题因为它们本来就在前面如果第二个数组剩了需要把剩余元素逐一拷过来。两数之和LeetCode 1有很多解法最暴力的是双重循环O(n²)更好的做法是借助HashMap把找差值变成O(1)查询整体降为O(n)。这道题虽然用到了HashMap但它的核心能力是把查找问题转化为哈希问题。你如果数组这个基础没学好连为什么能用哈希都理解不了。所以这道题适合放在数组学习的中后期做一次综合检验。6.3 面试中的数组题答题节奏和边界条件自查清单面试遇到数组题时我建议按这个节奏推进先和面试官确认输入范围数组可不可以为空可不可以是null元素可不可以重复要求原地修改还是可以开新数组先说暴力解法把逻辑理顺保证正确性。再提优化方向能不能排序能不能用双指针能不能用哈希把时间复杂度和空间复杂度说清楚。写代码时下意识检查这些边界数组长度为0、长度为1、所有元素相同、已经排好序、反转导致死循环、越界访问。这份清单是我在实际面试中总结出来的。很多候选人代码写不出来往往不是不懂算法而是没确认清楚数组元素能不能重复能不能用额外空间这些前提导致写着写着发现思路走偏了。6.4 这轮练习做完后我对数组的几个实际体会最后分享几个我在实际编码中沉淀下来的、关于数组的使用经验。第一能用Arrays工具类的地方别手写。排序用Arrays.sort、拷贝用Arrays.copyOf、填充用Arrays.fill、比较用Arrays.equals、转字符串用Arrays.toString。这些API经过大量优化和测试比大部分手写实现更可靠。手写是为了理解理解完之后日常开发要敢于用工具。第二基本类型数组和包装类型数组的选择要想清楚。需要高性能和低内存时用int[]需要放进泛型集合时用Integer[]。从int[]转到ListInteger没有优雅的单行API需要循环或者借助Stream的boxed()这是一道很常见的基础题但很多工作两三年的开发者第一次见到也卡壳。第三二维数组的matrix[0].length这个写法在特殊场景会带来隐患。比如一个矩阵一行都没有matrix.length 0直接访问matrix[0].length就会越界。处理二维数组时第一件事总是判断行数是否为0然后再判断第一行是否为空这两个判断缺一不可。第四数组的复制要留意深拷贝和浅拷贝。基本类型数组拷贝后互不影响引用类型数组拷贝后新数组和旧数组共享同一批对象。如果需要连对象内容都独立拷贝那不能只拷贝数组容器要逐个元素做深拷贝这在处理多维数组或者对象数组时尤其重要。数组这个主题如果只当基础语法学确实半天就能掌握但如果把它当作理解JVM内存、数据结构、甚至面试算法题的钥匙它能串起的东西就太多了。希望这篇数组练习题的梳理能帮你把这块最底层的基石打牢。跟着上面的清单练一遍再回去看那些Java面试题里的数组相关题目你会发现自己的视角已经完全不一样了。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询