news 2026/10/15 6:40:27

Java入门第4天:方法、数组与引用传递的踩坑复盘

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java入门第4天:方法、数组与引用传递的踩坑复盘

今天是学习Java第4天。

说实话,前三天我一直在“跟着感觉走”:第一天装环境、配置JDK、敲了几个Hello World的变体;第二天啃变量、数据类型和运算符,练到逻辑运算符时一度怀疑自己小学数学没学好;第三天学会了if、switch和三种循环,晚上睡觉满脑子都是for循环的“i++”。到了第四天,我意识到一个问题——光靠“看教程+跟着敲”已经撑不住了。Java的核心语法开始进入“需要动脑子想为什么”的阶段,尤其是方法、数组、引用传递这几个概念,如果还停留在“照着例子写一遍”的层面上,后面学到面向对象、集合框架时大概率要翻车。

这篇文章不是教程文档,也不是什么权威路线图,就是我第4天实际学习过程中的记录和踩坑复盘。我会把当天学的核心内容、代码示例、遇到的报错、排查思路,以及我自己总结的一套“动手练习题”都放出来,给同样刚起步、正卡在Java入门阶段的同学做一个参考。如果你也是“学了几天Java但总觉得没入门”,这篇应该能帮你把零散的知识点串起来。

1. 第4天到底该学到哪:我的路线规划与当天的现实感受

1.1 前三天学完,第四天为什么成了一个分水岭

先说说我自己的情况。前三天属于“语法蜜月期”,每敲一行代码都有反馈,运行成功就很有成就感。但到了第四天,知识点开始变抽象了:方法里参数的传递规则、数组在内存里的布局、多个方法之间怎么互相调用……这些东西没法靠肉眼直接观察到,只能先接受规则,再慢慢验证。

我当天给自己定的学习路线是这样的:上午主攻方法(Method),搞清楚什么是参数、什么是返回值、方法之间怎么调用;下午主攻数组(Array),弄明白数组的声明、初始化、遍历和常见操作;晚上把两者结合起来,写一些“用方法操作数组”的小练习。

这个安排不是拍脑袋定的。我在网上看了不少入门路线,发现一个共性规律:方法解决的是“如何把重复代码收起来”的问题,数组解决的是“如何批量处理同类型数据”的问题。这两个知识点一旦打通,写代码的抽象能力会明显上一个台阶。反过来,如果第四天还在纠结swich的写法、或者被for循环嵌套困住,那说明前面的基础还不牢,最好先回头扫一下盲区。

1.2 当天的真实学习状态:从自信到卡壳

现实和计划总是有差距的。上午学方法时还算顺利,概念一听就懂,无非就是“把一段代码打包,起个名字,需要时调用”。但等我兴奋地开始写一个“计算数组平均分”的练习时,直接卡住了。

卡住的地方不是方法本身的语法,而是“数据怎么从调用方传进去,又怎么把结果带回来”。我写了半天,编译一次报一次错,不是提示“找不到符号”,就是“不兼容的类型”。那一刻我突然意识到:学Java,或者说学任何一门编程语言,真正难的不是记住语法,而是理解数据在代码块之间流动的逻辑。这也是我为什么觉得第四天是个分水岭——前面的语法像认字,第四天开始像造句了。

为了让自己不再原地转圈,我做了一个表格,把当天需要掌握的核心题目和验收标准列出来,每完成一项就打勾:

核心知识点验收标准当天完成情况
方法定义能独立写一个带参数、带返回值的方法,并理解调用过程完成,但返回值逻辑用错两次
方法重载知道同名方法如何靠参数列表区分被动学会(因为写错了被迫查资料)
数组声明与初始化能用静态/动态方式创建数组,并取出指定位置的数据完成,越界报错一次
数组遍历能用for循环和for-each遍历数组,知道两种写法的差别完成
引用类型的传递理解“数组变量存的是地址”,不直接把数据复制来复制去半懂,后文细说
方法与数组结合编写一个案例:方法接收数组参数,处理后返回结果完成,但第一次写成了用return返回两个值的错误代码

说实话,这张表里的“完成”两个字,对刚学4天的我来说其实是虚的。很多知识点我只是“能跑”,远达不到“讲明白”的程度。但我觉得,入门阶段先把“能跑”的范围铺开,再去深挖“为什么”,比一步到位更实际。

2. 数组与方法的运行底牌:理解了栈和堆,代码不再玄学

2.1 为什么第4天就应该接触内存模型

我最初觉得“内存分配”是学到JVM阶段才需要碰的东西。但第4天学数组时,一个现象让我不得不提前面对它:我写了两个数组变量,把它们直接赋值,然后改动其中一个,另一个也跟着变了。

int[] a = {1, 2, 3}; int[] b = a; b[0] = 99; System.out.println(a[0]); // 输出什么?

我当时以为是99,但不知道为什么;甚至有一瞬间觉得,应该是1才对,毕竟我改的是b不是a。实际运行结果是99。这个结果在逻辑上非常“反直觉”,如果不理解数组变量存的是“引用”而不是“实际数据”,后面会一直踩同类的坑。

后来我找了一个特别生活化的类比才搞明白:数组变量像是一个门牌号,门牌号指向一栋房子,房子里的房间才是实际数据。int[] b = a;不是给b盖了一栋新房子,而是把a手里那个门牌号又抄了一份给b。你用b这个门牌号进屋改了一楼的灯,a进门看到的当然也是改完的灯。

2.2 栈、堆和方法调用过程

为了彻底搞懂这个问题,我第4天晚上补了一节“迷你内存模型课”,把栈(Stack)和堆(Heap)这两个名词提前学了。

Java程序运行时,基本可以粗分成两大块内存区域:栈和堆。栈里主要放局部变量和方法调用的记录,堆里放new出来的对象和数组数据。局部变量如果存的是基本类型,那栈里存的就是值本身;如果存的是引用类型,那栈里存的是“指向堆的地址”。

拿刚才的代码举例:

  • int[] a:a这个变量在栈里,存的是一个地址,指向堆里某块数组空间。
  • new int[]{1,2,3}:实际数组数据在堆里。
  • int[] b = a;:把a里的地址复制给b,b也指向同一个堆空间。
  • 改b[0],本质是通过地址找到了堆里的同一块空间,改了那个位置的数据。

方法调用也是类似逻辑。每次调用一个方法,Java会把本次调用的参数、局部变量、返回地址打包成一个“栈帧”,压进栈里;方法执行完,栈帧弹出,控制权回到调用处。我第4天做方法练习时,一开始理解不了“为什么方法里改了参数,外面不受影响”,学完栈帧后想通了:传参本质上是把实参的值复制一份给形参,栈里是两份互不相干的东西,除非传的是引用类型,两份都指向同一个堆空间。

这一块对刚学第4天的人而言,其实不必深到去读字节码,但一定要明白一个结论:基本类型传的是值,引用类型传的是地址。这个结论几乎贯穿着后面所有的Java学习,早一点接受,后面少走很多弯路。

3. 今天踩的报错坑:数组越界和返回值迷宫

3.1 坑一:ArrayIndexOutOfBoundsException,我的排查链路

当天下午写数组练习,我写了一个“求数组最大值”的方法,第一次运行直接报错:

public class MaxFinder { public static void main(String[] args) { int[] scores = {75, 89, 92, 68, 79}; int max = getMax(scores); System.out.println("最高分:" + max); } public static int getMax(int[] arr) { int max = arr[0]; for (int i = 1; i <= arr.length; i++) { if (arr[i] > max) { max = arr[i]; } } return max; } }

报错信息是:

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 5 at MaxFinder.getMax(MaxFinder.java:12) at MaxFinder.main(MaxFinder.java:5)

我当时的第一反应是“我数组里明明有5个数,怎么会越界?”后来我逼着自己一行一行读代码,而不是急着改,把排查步骤写在这里,后面的人可以照着这个思路来:

第一步,先看报错信息里的行号。它明确告诉我是第12行出问题,也就是if (arr[i] > max)这一行。

第二步,算一下i的取值范围。我在for循环里写的是i <= arr.length,而数组索引是从0开始的,长度为5的数组,合法索引范围是0到4。所以当i等于5时,arr[5]就已经超出数组空间了。

第三步,验证修复方案。把i <= arr.length改成i < arr.length,重新编译运行,程序正常输出89。

这个坑的本质不是我不会写for循环,而是没有建立“数组长度和最大索引差1”的条件反射。那次报错之后,我只要写遍历数组的代码,脑子里就会自动过一遍:长度是元素的个数,最后一个索引是长度减一。

3.2 坑二:length是属性,不是方法

第二个坑来得莫名其妙。我在练习里想输出数组的长度,随手敲成了scores.length(),结果编译直接报错:

找不到符号 方法 length()

我一愣,String类型取长度不是用.length()吗?为什么数组不行?

查完资料才明白:数组的length是属性(或者理解为固有字段),不是方法,所以不加括号;而String的length是一个方法,所以要加括号。这个区别,在第4天遇到时确实容易混,尤其是同时写过字符串和数组之后。

后来我给自己编了一句口诀:数组跟属性走,字符串跟方法走。遇到取长度,先看前面是什么类型。这个习惯让我在之后的编程里少踩好多坑。

3.3 坑三:把“返回值”当成“输出结果”用

这个坑是在练习方法时踩的。我写了一个判断成绩是否及格的方法:

public static boolean isPass(int score) { if (score >= 60) { return true; } else { return false; } }

当时我觉得自己写得没问题,甚至在main方法里还加了一句:

System.out.println(isPass(75));

输出结果是true,没问题。但问题出在我对返回值语义的理解上:我一直以为“return true”就是把true打印到屏幕上。直到我把方法改成void,然后在方法体内写return true;,编译报错“不兼容的类型: unexpected return value”时,我才真正意识到,return是“把值交回给调用方”,而不是“打印”。

为了彻底搞清楚,我做了个对比实验:

public static void testVoid() { System.out.println("执行到这里"); return; // 这里可以写return,但不带值,表示方法结束 } public static int testReturn() { return 100; // 带值返回,调用方可以用变量接收 }

这个坑对我的启发很大:学方法的时候,很多人光背“有返回值的方法用return返回结果”,但没有理解返回值和控制台的打印是两条完全独立的路径。方法返回值是给程序内部用的,System.out.println是给人看的。两者可以配合,但不能互相替代。

3.4 当天报错整理:一张表格直接对照

我把当天遇到的报错信息汇总成了一张速查表,之后看到类似报错至少心里有数:

报错信息常见原因排查方向
ArrayIndexOutOfBoundsException访问了不存在的索引,通常因为循环条件用了<=检查循环边界,确认最大索引应为 length - 1
找不到符号 length()把数组的length属性当成方法调用数组用length,String用length()
不兼容的类型: unexpected return value在void方法中返回了具体值要么去掉返回值,要么把方法声明为对应返回类型
缺少返回语句方法声明有返回类型,但某些分支没有return检查所有路径是否都有return,或把return提到方法末尾
可能尚未初始化变量局部变量声明了但没有赋初值就使用给变量显式初始值,或在所有分支中完成赋值

做一个这样的表格不需要额外花多少时间,但对我这种刚入门的人特别有用。报错信息看多了之后,我会形成一种“直觉”,一看英文就知道大概是什么问题。这种直觉比死记硬背任何教程都有用。

4. 用一套“动手题”逼自己把知识点用起来

4.1 我给自己定的三道练习题

下午学完基础概念后,我发现如果直接去看下一节,肯定会陷入“一看就会、一写就废”的状态。所以第4天我给自己定了三道练习题,全部要求用“方法 + 数组”完成,不可以用单个main方法硬扛:

第一题:写一个方法,接收一个int数组,返回数组中的最大值和一个标识是否成功的布尔值。这道题的核心是练习数组遍历和返回值设计。

第二题:写一个方法,接收两个int数组,返回两个数组对应位置元素相加后的新数组。这道题考的是数组作为参数和返回值的完整流程。

第三题:写一个程序,统计一个字符串数组中每个字符串的长度,并输出最长字符串的名字。这道题其实涉及一点String知识,重点是为了让我把“遍历数组”这个动作做得足够熟练。

4.2 第一题答案里暴露的问题:贪心地想返回多个值

我先写了第一题,一开始的想法是“既要最大值,又要知道数组是不是空”,于是很自然地写了这样一段错误代码:

public static ??? getMaxWithFlag(int[] arr) { if (arr.length == 0) { return false? 0? } int max = arr[0]; for (int i = 1; i < arr.length; i++) { if (arr[i] > max) { max = arr[i]; } } return max; }

问题在于:我不知道该怎么把“最大值”和“是否成功”这两个信息一起返回。当时的感觉像是手里只有一个外卖袋,却想把饭和汤都放进去,找不到第二个袋子。

后来我查资料知道有两个常见解法:一是返回一个数组,把多个值打包进去;二是把“是否成功”用作返回值,把最大值通过一个长度为1的数组作为参数传进去改(这种做法其实不太优雅,但能加深对引用传递的理解)。我当天用的是返回数组的方案,简单直观:

public static int[] getMaxWithFlag(int[] arr) { if (arr == null || arr.length == 0) { return new int[] {0, 0}; // 第一个值表示flag,0表示失败 } int max = arr[0]; for (int i = 1; i < arr.length; i++) { if (arr[i] > max) { max = arr[i]; } } return new int[] {1, max}; // 第一个值表示flag,1表示成功 }

虽然这个设计并不算精致,但作为第4天的练习,它的价值在于让我亲手把“数组既可以当参数又可以当返回值”这条知识用了起来。现在回看,真正的开发中大概率不会这么写,但“想返回多个值时的处理思路”这个意识就是从这道题建立起来的。

4.3 第二题和第三题:练习中的几个小发现

第二题比较顺利,无非就是创建新数组、for循环逐个相加。但第三题给了我一个惊喜:我在统计字符串长度时,第一次用for-each实现了遍历,发现代码简洁很多。原来for-each特别适合“从头到尾读一遍”的场景,但如果你想控制从第几个元素开始处理、或者需要修改元素的值,还是得用传统for循环加索引。这个区别,不亲自写一遍真的体会不到:

public static String findLongestName(String[] names) { String longest = names[0]; for (String name : names) { if (name.length() > longest.length()) { longest = name; } } return longest; }

这个方法的代码量比我预想的少很多,但它让我意识到一个学习方式上的问题:我太容易陷入“抄教程例子”的状态。教程上给一个例子,我就照着敲一遍,敲完觉得自己会了。但实际写练习题时,才发现很多细节根本没有真正过脑子,比如数组为空时的处理、多个元素相等时取哪一个、方法命名是否清晰。这些“边界问题”,教程里通常不会专门讲,只能靠练习逼自己想清楚。

4.4 做完题之后,我建议你也做一遍“回头看”

三道题全部跑通之后,我并没有急着往前学,而是做了一件自己觉得特别值的事:把代码重新读一遍,每读一行就问自己“这行能不能省略?为什么不能?”、“如果输入是空数组会怎样?”、“如果数组里有负数,我的最大值初始化还有效吗?”

这一遍“回头看”,让我发现自己的最大值初始化方式其实有隐患。如果数组里全是负数,把max初始化为0就会导致结果错误:

int max = arr[0]; // 这样才是对的

教程里经常用int max = 0;来开头,但真实场景里数据的取值范围我们是没法提前预知的。这个问题的核心不在于“到底怎么写”,而在于“写完要主动给代码找毛病”。我第4天培养出来的这个习惯,后面帮我在调试程序时节省了大量时间。

5. 第4天晚上的一小时:我把错误代码改成了“能讲清楚”的版本

5.1 为什么我说“能跑”和“能讲清楚”是两回事

当天晚上,我又做了一次回顾,把所有练习代码重新梳理了一遍。当时脑子里冒出一个想法:如果让我给自己的代码当老师,我能把每段代码都讲明白吗?

试了一下才发现,很多地方我只能说“这样写能跑”,但说不出为什么。比如第二题的返回值类型为什么是int[],我当时的解释是“因为要返回数组”,但如果面试官继续问“为什么数组能当返回值类型”,我就开始支支吾吾了。其实答案很简单:Java中数组本身就是一个对象,引用类型自然可以作为方法的返回类型。可我当时还没有这个概念。

所以我决定,把当天所有练习代码都改成一版“能讲给自己听”的版本。改的时候不只看结果,还要给关键行加注释,把每一步的意图写出来:

public class ArrayPractice { public static void main(String[] args) { int[] nums = {3, -1, 5, 8, -4}; int[] result = maxAndFlag(nums); System.out.println("flag: " + result[0] + ", max: " + result[1]); } /** * 返回数组:第一个元素是成功标志,第二个元素是最大值 * 设计说明:因为Java方法只能有一个返回值,所以把多个结果打包进一个数组返回 */ public static int[] maxAndFlag(int[] arr) { if (arr == null || arr.length == 0) { return new int[] {0, 0}; } int max = arr[0]; for (int i = 1; i < arr.length; i++) { if (arr[i] > max) { max = arr[i]; } } return new int[] {1, max}; } }

当我真正把注释写成文字后,发现以前很多“只觉得应该是这样”的概念,开始变得有逻辑了。这也是为什么我建议所有跟我一样的初学者,不要只满足于代码能运行,要试着向一个虚拟读者解释自己写的每一行。讲不出来,基本说明还没学透。

5.2 当晚随手记下的三条“第4天心得”

最后记录三条我当天晚上写进学习笔记里的心得。这三条不是从哪个教程里抄的,都是当天真实的体会,放在这里作为这篇记录的收尾:

第一,遇到报错不要慌着删代码,先读报错信息里的行号和异常类型。很多人一报错就直接把代码删了重写,其实大部分问题在报错信息里已经明说了。

第二,方法设计阶段就要想清楚输入是什么、输出是什么、异常情况怎么处理。哪怕现在只是练习,这个思维习惯越早养成越好,不然以后写大一点的项目,方法之间会乱成一锅粥。

第三,数组的引用传递是Java入门阶段最值得花时间弄懂的机制之一。第4天如果不把它搞明白,第10天学集合、学对象引用时,一定会在更复杂的场景里再度踩坑。这个比喻虽然简单,但它帮我扛过了后面很多更复杂的代码逻辑。

第4天的Java学习,没有让我变成什么“能独立写项目”的人,但它让我第一次感受到:编程不是背单词,而是在建立一套思维方式。这套思维一旦开始转动,后面每一天的学习都会变得踏实很多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 6:39:57

context-mode:AI辅助开发中的上下文隔离与回收实战

做 AI 辅助开发半年多&#xff0c;我最头疼的问题不是模型不够聪明&#xff0c;而是它总"串味"——早上还在写 A 项目的接口文档&#xff0c;下午打开对话工具问 B 项目的需求&#xff0c;AI 张口就是 A 项目的表结构和代码风格。长期多项目并行的人应该都有同感&…

作者头像 李华
网站建设 2026/10/15 6:38:56

基于RA-AF特征与GMM聚类的声发射裂纹模式识别Matlab实现

简介&#xff1a;针对材料力学与无损检测中的裂纹模式识别问题&#xff0c;这份PDF文档提供了一套基于RA-AF特征与高斯混合模型&#xff08;GMM&#xff09;的Matlab实现代码。核心思路是不预先划分裂纹分界线&#xff0c;而是借助期望最大化&#xff08;EM&#xff09;迭代算法…

作者头像 李华
网站建设 2026/10/15 6:38:33

从零搭建内部数据聚合系统:REA项目的架构决策与踩坑复盘

如果你翻过某团队内部的项目清单&#xff0c;大概率见过一类只有代号、没有详细介绍的项目——我这次负责的 REA 就是其中之一。刚拿到手的时候&#xff0c;除了这个名字&#xff0c;只剩一条需求&#xff1a;“把各处散落的运行数据收拢起来。”听起来很简单&#xff0c;但真正…

作者头像 李华
网站建设 2026/10/15 6:38:10

DINA认知诊断模型:从总分到知识点画像的题诊断研究

简介&#xff1a;这是一篇发表于《中国考试》2013年第1期的认知诊断DINA模型研究综述&#xff0c;作者为南京师范大学张潇、沙如雪&#xff0c;适合心理测量学、教育统计与个性化教学研究者阅读。资源以PDF格式提供&#xff0c;包含期刊原文共1个文件&#xff0c;压缩包约1.31M…

作者头像 李华
网站建设 2026/10/15 6:38:07

堆排序原理解析:完全二叉树、堆化与下沉调整的底层逻辑

1. 为什么堆排序值得你花20分钟真正搞懂——它不是“另一个排序”&#xff0c;而是数据结构思维的分水岭很多人学堆排序&#xff0c;止步于“建堆→交换→调整”这九个字。背过代码&#xff0c;跑过Demo&#xff0c;考试能写&#xff0c;但一问“为什么非得用完全二叉树结构”“…

作者头像 李华
网站建设 2026/10/15 6:37:45

电话号码字母组合:回溯算法入门与代码实战

刷过LeetCode的同学应该都绕不开这道“电话号码的字母组合”。它排在第17位&#xff0c;题面短、一眼能看懂&#xff0c;但它真正教你的不是字符串拼接&#xff0c;而是一整套回溯遍历的思路。很多人在这一步就把“回溯模板”刻进了脑子里&#xff0c;后面做全排列、子集、组合…

作者头像 李华