2. 开头day03到底该学什么先给这颗定心丸如果你正在按“java学习路线”自学大概率已经经历过这样的场景day01装好JDK、配好环境变量Hello World跑通那一刻觉得自己行了day02学完变量和数据类型开始能算点简单的东西感觉Java也不过如此。然后到了day03很多人突然就懵了——网上教程东一榔头西一棒子今天讲运算符明天讲流程控制后天又跳到数组学得零零散散代码能抄但不会写面试题一翻全认识但一问就卡壳。这篇内容就是来治这个毛病的。java-day03这个阶段核心任务只有一个彻底拿下运算符、流程控制、数组、方法这四块语法地基并且开始用“面向过程”的完整逻辑去解决实际问题。这是从“能看懂代码”到“能写出代码”的转折点也是后续学面向对象、集合框架、异常机制之前必须夯实的台阶。适合刚学完变量与数据类型、想系统巩固基础语法的新手也适合准备java基础面试题但在运算符和数组细节上总丢分的同学。我在带新人踩过无数次坑之后可以负责任地说day03学得好不好直接决定你后面学面向对象时是“顺理成章”还是“一脸懵”。这篇就把这一天的内容拆开揉碎把原理、实操、避坑全给你捋清楚。3. 整体设计与思路拆解3.1 day03在整个Java学习路线里的定位如果给Java基础学习画一张路线图大概是这样的顺序环境配置 → 变量与数据类型 →运算符与表达式→ 流程控制 → 数组 → 方法 → 面向对象 → 常用API → 集合 → 异常 → IO → 多线程。day03正好卡在“变量”和“面向对象”之间全部属于**结构化编程面向过程**的范畴。我见过太多人试图跳步变量刚学完就去看集合、看Stream流、看Lambda函数结果连for循环里i和i的区别都说不清写出来的代码逻辑一团乱麻。day03这四块内容看似零散实际上是同一个能力的不同侧面——用代码描述“过程”的能力运算符让你能表达计算规则流程控制让你能表达分支和循环数组让你能批量处理同类型数据方法让你能把过程封装起来复用。组合起来你才能独立写出一个完整的小程序比如冒泡排序这才是day03真正要解决的问题。这个阶段的设计逻辑遵循的是“最小闭环”原则用尽可能少的语法点构建出一个能独立运行的完整程序。千万别小看这种“小闭环”训练它训练的是你拆解问题→设计步骤→编码实现→调试修复的完整链路这个链路是后面做任何项目都通用的元能力。3.2 为什么先学运算符和流程控制再学数组和方法这个顺序不是随便定的背后有很强的依赖关系。运算符是表达式的最小组成单元你必须先知道是短路与、%是取余才能去写一个判断闰年的分支条件。流程控制依赖运算符产生的结果true/false来做决策for循环的核心三要素初始化、条件判断、迭代里条件判断就依赖关系运算符。数组呢天生和循环绑定——你几乎不可能遍历一个数组而不写循环所以先有循环思维再有数组操作会顺滑很多。而方法要放在数组之后是因为方法设计的核心之一就是“参数传递”而参数最常见的载体就是数组和基本类型。先学会怎么创建一个数组并操作它再理解为什么方法内部修改数组会影响原数组引用传递有了数组这个“引用类型”的例子方法传参的两种模型值传递vs引用传递才讲得明白。这也是java面试题里最喜欢挖坑的地方day03就把根子埋好。3.3 day03学习的一个核心策略以练代学以错促学说句实在话前面三节概念再多都不如自己动手敲两段代码来得实在。我带人时对day03的要求就一句话每天至少独立写完3个不带参考答案的小程序。第一个照着抄练手感第二个参考思路半独立完成第三个完全脱稿写。错得越多越好——day03的报错基本都是红波浪线IDE会直接告诉你哪里有问题改一处就长一个记性。这种“以错误为教材”的方式比反复看书高效多了。4. 核心细节解析与实操要点4.1 运算符不只是“算数”更是逻辑判断的工具Java运算符分六大类算术运算符、赋值运算符、关系运算符、逻辑运算符、位运算符、三元运算符。很多新手只把注意力放在算术上觉得 - * / %会了就算过了这是day03最大的误区。真正在后续开发里高频使用的是逻辑运算符和三元运算符判断条件、状态切换、空值兜底全得靠它们。先看最容易出错的几个点自增自减/--的前置与后置区别这个点面试必考。口诀是a先使用a的原始值再自增a先自增再使用新值。看这段代码int a 5; int b a; // b5a6 int c a; // 先a7再c7 System.out.println(b , c); // 输出5,7短路运算和||不是普通的“与”和“或”它们有短路特性——左边的结果已经能决定整个表达式时右边就不再执行。这不仅是性能优化更是控制程序逻辑的常用技巧// 防止除零异常的经典写法 if (divisor ! 0 dividend / divisor 10) { System.out.println(商大于10); }如果用而不是当divisor为0时右边照样会算直接抛ArithmeticException。取余%和整除/的分工这俩特别容易搞混。整数相除7 / 2结果是3因为Java整数除法直接截断小数7 % 2结果是1拿到的是余数。判断奇数偶数、判断闰年、数组循环索引都得用取余。位运算符这一块新手阶段我建议了解即可不必深挖。 | ^ ~ 的运算规则有个印象就行真正的高频使用场景在权限系统用位掩码存多状态和高效乘除2x 1等于x*2里那些等你做到java高级计算器这类实战项目再回来看也不迟。三元运算符条件 ? 表达式1 : 表达式2简化if-else的利器。我平时写代码非常喜欢用它做简单的值赋值比如String result score 60 ? 及格 : 不及格; // 等价于 String result; if (score 60) { result 及格; } else { result 不及格; }学运算符有个技巧做一张速查表把每个运算符的符号、作用、示例、优先级自己填一遍。填的过程比看十遍书都管用因为你在强制自己回忆和辨析。4.2 流程控制顺序、分支、循环程序的三块积木流程控制解决的是“程序怎么走”的问题。Java里只有三种结构顺序结构默认、分支结构if/switch、循环结构for/while/do-while。所有复杂逻辑拼来拼去都是这三块积木的组合。if-else的注意点条件表达式必须是boolean类型这是Java和C语言最大的区别之一C里if(1)合法Java里必须写成if(true)。else的匹配采用就近原则else总是和最近的if配对。链式else if要注意顺序比如判断成绩等级要从高往低排if (score 90) { grade 优秀; } else if (score 80) { grade 良好; } else if (score 70) { grade 中等; } else if (score 60) { grade 及格; } else { grade 不及格; }如果顺序反了先判断score 60那么90分也会落入“及格”分支因为第一个分支就成立了。switch-case是新手最容易踩坑的地方。三件事必须记住第一switch支持的类型是byte、short、int、char、StringJDK7、枚举不支持long、float、double第二每个case结尾必须有break否则发生case穿透后面的case会无脑执行直到遇到break或结束第三case的值必须是编译期常量不能是变量。我写项目时switch有个妙用处理菜单选项、状态机流转比如订单状态待支付→已支付→已发货、枚举驱动的策略分发代码比一串if-else清爽太多了。for循环是最核心的循环它的三要素经典格式是for (初始化语句; 条件判断; 迭代语句) { 循环体; }执行顺序是初始化 → 判断 → 循环体 → 迭代 → 判断 → 循环体 → 迭代……循环。特别强调一点迭代语句是循环体执行完才跑的不是条件判断前。这个顺序搞错循环次数就全错了。while和do-while的本质区别while先判断后执行可能一次都不执行do-while先执行后判断至少执行一次。写菜单程序时我经常用do-while因为菜单至少会显示一次用户选完“退出”才结束。break和continuebreak直接跳出当前整层循环continue跳过本次循环的剩余语句进入下一轮迭代。带标签的break/continue可以跳出外层循环这个语法出现频率极低但面试题偶尔问记住格式即可outer: for(...) { break outer; }。4.3 数组批量数据的容器引用类型的第一个硬骨头数组是Java中第一个真正意义上的引用类型。理解“引用”二字对后续学习字符串、集合、对象至关重要。数组的声明和初始化有两种形式// 方式1静态初始化声明时直接给值 int[] arr {1, 3, 5, 7, 9}; // 方式2动态初始化先指定长度后赋值 int[] arr2 new int[5]; // 默认全为0 arr2[0] 10;注意一个细节Java里推荐int[] arr这种写法把类型和标识符放一起读起来更顺int arr[]这种C风格写法语法上也对但不符合Java规范。数组的必要知识点清单下标从0开始最后一个元素的下标是length - 1每个数组都有一个length属性注意这是属性不是方法不带括号访问越界会抛ArrayIndexOutOfBoundsException这是java基础面试题里数组部分出现频率最高的异常数组一旦创建长度不可变。这个特性决定了后面为什么要学集合ArrayList可以动态扩容数组默认值int为0double为0.0char为\u0000boolean为false引用类型为null数组的复制新手常犯的错是直接赋值int[] a {1, 2, 3}; int[] b a; // 这不是拷贝b和a指向同一个数组 b[0] 100; // a[0]也变成了100正确的拷贝方式有几种Arrays.copyOf()、System.arraycopy()native方法性能高、clone()、遍历逐个赋值。其中Arrays.copyOf最常用示例int[] original {1, 2, 3}; int[] copy Arrays.copyOf(original, original.length); // 完全拷贝排序和查找是数组的经典应用。冒泡排序几乎是每个Java初学者的必修课它的核心思想是相邻元素两两比较大的往后沉每轮确定一个最大值的位置外层循环控制轮数内层循环控制比较次数public static void bubbleSort(int[] arr) { int temp; // 外层轮数n-1轮 for (int i 0; i arr.length - 1; i) { // 内层比较每轮比较 n-1-i 次 for (int j 0; j arr.length - 1 - i; j) { if (arr[j] arr[j 1]) { temp arr[j]; arr[j] arr[j 1]; arr[j 1] temp; } } } }这里有个优化点如果某一轮没有任何交换说明数组已经有序可以直接break结束这就是冒泡排序小优化面试聊起来能加分。面试考冒泡排序时除了要写对还得能说清楚时间复杂度O(n²)——最好情况已经有序O(n)最坏情况逆序O(n²)空间复杂度O(1)稳定排序。二维数组跟一维数组类似区别是每个元素本身又是一个一维数组。声明方式int[][] matrix { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} }; // 取值 matrix[行][列] System.out.println(matrix[1][2]); // 6注意二维数组可以是不规则的比如每一行的长度不同new int[3][]然后每行分别new这在存储稀疏数据时很有用。4.4 方法把逻辑装进“黑盒子”实现代码复用方法函数的本质是把一段完成特定功能的代码封装起来给它一个名字通过参数传递数据通过返回值拿回结果。写方法的目标是“一次编写多处调用”避免代码重复。方法定义的完整结构public static int max(int a, int b) { return a b ? a : b; }逐段拆解public是访问修饰符static表示静态方法main方法里直接调用的方法通常得是staticint是返回值类型max是方法名驼峰命名动词开头(int a, int b)是形参列表return返回结果。方法的命名规范很讲究动词或动词短语开头首字母小写比如getMax、calculateSum、isEven。boolean返回值的方法习惯命名为isXxx或hasXxx读起来语义明确。**方法重载Overload**是方法部分最重要的考点同一个类中方法名相同参数列表不同参数个数、类型、顺序任一不同就是重载。注意只跟参数列表有关跟返回值类型无关——两个方法如果参数列表一样但返回值不同不能构成重载编译直接报错。public static int add(int a, int b) { return a b; } public static double add(double a, double b) { return a b; } public static int add(int a, int b, int c) { return a b c; } // 这三个方法构成重载参数传递是java面试题的重灾区。基础数据类型是值传递——方法内部修改形参不影响实参引用数据类型数组、对象、String传的是“引用副本”即两个引用指向同一个对象。但特别要注意Java里没有引用传递只有值传递。这个说法让很多人懵实际就是说你传的是内存地址的值不是对象本身。所以方法内部通过这个引用修改对象的属性会影响原对象但如果重新给形参赋值一个新对象原引用不受影响。方法递归是day03的进阶内容核心思想是“方法自己调自己”。递归必须有递归头终止条件和递归体递归表达式否则栈溢出StackOverflowError。经典例子是阶乘public static int factorial(int n) { if (n 1) return 1; // 递归头 return n * factorial(n - 1); // 递归体 }递归虽然代码简洁但性能不一定是最好的因为每次调用都会压栈层级太深容易耗尽栈空间。工程上对大数据量的递归要谨慎能改循环就改循环。5. 实操过程与核心环节实现5.1 环境准备day03开工前的最后检查开始动手前先把环境确认一遍JDK版本建议用8以上现在主流项目基本是8或11或17IDE能用IntelliJ IDEA就优先它社区版免费够用或者VSCode配好Java插件也能写。命令行里敲java -version和javac -version都能正常输出版本号说明环境没问题。很多人会遇到“Java环境变量配置详细教程”搜了半天还是配不好的情况。核心点就是三个变量JAVA_HOME指向JDK安装目录Path里加上%JAVA_HOME%\binCLASSPATH一般不需要手动设。配完之后必须重新打开命令行窗口才生效这是90%“配了但没生效”的原因。5.2 核心代码示例day03的完整练习串台我建议day03用下面的思路串一遍所有语法点写一个数组工具类包含最大值、最小值、求和、平均值、冒泡排序、查找元素等方法。这样写一遍运算符、流程控制、数组、方法全练到了写完还获得一个以后能直接复用的工具类。示例框架如下package day03; public class ArrayUtil { // 求最大值 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; } // 求最小值 public static int getMin(int[] arr) { int min arr[0]; for (int i 1; i arr.length; i) { if (arr[i] min) { min arr[i]; } } return min; } // 求和 public static int getSum(int[] arr) { int sum 0; for (int num : arr) { sum num; } return sum; } // 求平均值 public static double getAverage(int[] arr) { return (double) getSum(arr) / arr.length; } // 冒泡排序升序 public static void bubbleSort(int[] arr) { int temp; for (int i 0; i arr.length - 1; i) { boolean swapped false; for (int j 0; j arr.length - 1 - i; j) { if (arr[j] arr[j 1]) { temp arr[j]; arr[j] arr[j 1]; arr[j 1] temp; swapped true; } } if (!swapped) { break; } } } // 顺序查找元素返回下标找不到返回-1 public static int indexOf(int[] arr, int target) { for (int i 0; i arr.length; i) { if (arr[i] target) { return i; } } return -1; } }再写一个main方法调用这些工具方法public class ArrayUtilTest { public static void main(String[] args) { int[] numbers {34, 12, 89, 5, 67, 23}; System.out.println(最大值 ArrayUtil.getMax(numbers)); System.out.println(最小值 ArrayUtil.getMin(numbers)); System.out.println(总和 ArrayUtil.getSum(numbers)); System.out.println(平均值 ArrayUtil.getAverage(numbers)); ArrayUtil.bubbleSort(numbers); System.out.print(排序后); // 增强for循环遍历 for (int num : numbers) { System.out.print(num ); } System.out.println(); int index ArrayUtil.indexOf(numbers, 67); System.out.println(67所在位置 index); } }这里用了(double) getSum(arr) / arr.length把整数除法转成浮点除法不然两个整数相除会得到截断后的整数这一行是新手最容易写错的地方务必重视。还使用了增强for循环for-each它的语法是for (元素类型 变量名 : 数组或集合)适合只需要遍历取值、不需要修改下标的场景。注意增强for循环里无法直接修改数组元素的值因为循环变量只是元素的拷贝。5.3 核心参数与遵循的底层原理说明day03涉及的计算逻辑虽然基础但理解背后的原理能让你少走很多弯路。i和i在循环里的选择for循环第三部分写i还是i对单条语句来说结果一样但底层字节码有细微差别纯性能上几乎可以忽略。我更建议统一用i可读性更好团队协作时大家也都习惯别在这种地方搞特立独行。为什么数组下标越界报ArrayIndexOutOfBoundsException这是Java对安全的强制保障。C语言里数组越界会直接读内存垃圾数据轻则乱值重则段错误甚至安全漏洞。Java虚拟机在每次数组访问时都会做边界检查越小越安全代价是性能略低但这笔安全账在JVM层面的取舍上是划算的。理解了这一点你就明白为什么能写出越界代码但跑起来才报错其实JVM已经帮你挡了一道灾。整数除法丢精度(double) getSum(arr) / arr.length这行强转优先级高于除法所以先转double再除。如果你写成getSum(arr) / arr.length结果一定是整数比如某个数组5个数总和是38平均值你会得到7而不是7.6。这是计算类程序最典型的隐蔽bug——结果看着像对的实际差了一截。String类型变量拼接Java中用在String上就是字符串拼接最大值 ArrayUtil.getMax(numbers)会先执行getMax返回int再转成String拼一起。这涉及一个底层点字符串拼接会创建新的String对象循环里大量拼接会产生很多临时对象影响性能。day03阶段不用太担心但到后面学StringBuilder时回来对比体会会更深。5.4 三个综合实操小项目检验day03成果练完数组工具类我建议趁热打铁再做三个小项目都是能独立完成才算真正过关的难度项目1九九乘法表。经典练手程序。优化点是用\t控制列对齐注意内层循环的遍历范围是j i只打印下三角public static void multiplicationTable() { for (int i 1; i 9; i) { for (int j 1; j i; j) { System.out.print(j × i (i * j) \t); } System.out.println(); } }项目2数组元素去重。给定一个数组把重复元素去掉返回新数组。方法不止一种双重循环标记法O(n²)、借助Set后面学到集合再实现、先排序再相邻去重O(nlogn)。day03建议用双重循环把逻辑练扎实public static int[] removeDuplicates(int[] arr) { int[] temp new int[arr.length]; int count 0; boolean isDuplicate; for (int i 0; i arr.length; i) { isDuplicate false; for (int j 0; j count; j) { if (arr[i] temp[j]) { isDuplicate true; break; } } if (!isDuplicate) { temp[count] arr[i]; count; } } return Arrays.copyOf(temp, count); }这个方法里用到了嵌套循环、布尔标记、break跳出内层循环、Arrays.copyOf截断数组一个练习串起好多知识点。项目3菱形图案打印。输入行数n打印一个n行的菱形。这个练习的核心是用外层循环控制行数内层分别打印空格和星号找出行号与空格数、星号数之间的规律。写完之后你对for循环的掌控力会上一个台阶。6. 常见问题与排查技巧实录6.1 “找不到或无法加载主类”怎么办这是Java初学者遇到最多的报错之一。通常有三个原因一是类名和文件名不一致Java要求public类名必须与文件名相同二是包路径与目录结构不一致编译时应该回到包根目录执行javac运行时要带完全限定类名比如java day03.ArrayUtilTest三是CLASSPATH配置有问题。排查顺序先看文件名和类名是否一致再看编译命令是否在正确的包路径下执行最后确认环境变量没配错。6.2 逻辑运算符“不等于”总是踩坑新手常把“不等于”写成!之外的样子比如false、!都见过。!是关系运算符里最容易被忽视的一个写条件时我建议优先写“正向条件”比如if (isSuccess ! false)改成if (isSuccess)更优雅。如果出现逻辑取反复杂的情况括号加足不要吝啬!((a b) || c)比!a !b || !c可读性强太多。6.3 增强for循环遍历数组想改值怎么改不了之前提到增强for循环的循环变量是数组元素的拷贝。很多新手写成for (int num : arr) { if (num 0) { num 0; // 你以为改了数组实际上num只是个局部拷贝 } }结果数组一点变化没有。想修改数组元素只能用普通for循环配合下标for (int i 0; i arr.length; i) { if (arr[i] 0) { arr[i] 0; } }理解这个问题的关键是分清“基本类型变量存值”和“引用类型变量存地址”这两个概念它在后续学Java容器列表、Map时还会反复出现。6.4 常见错误速查表错误代码含义排查建议ArrayIndexOutOfBoundsException数组下标越界检查循环条件确认最后一个元素下标为length-1NullPointerException空指针异常数组未初始化就使用确认new过了整数除法丢精度结果少了小数部分运算前把其中一个操作数转doubleswitch case穿透多个case同时执行检查每个case是否遗漏break栈溢出StackOverflowError递归没有终止条件检查递归头是否能被触发编译报错“不兼容的类型”类型转换错误检查int转double需要强转double转int必须强转且会丢精度6.5 一个面试预热为什么说字符串比较要用equals而不是day03末尾我习惯抛出一个引子String a hello; String b hello; a b的结果是true还是false答案是true但String c new String(hello); a c结果是false。原因涉及字符串常量池和对象引用比较这是java面试题八股文里的常客。day03阶段不需要完全掌握但先在心里埋个种子学到字符串API时自然就通了。我在带人时发现只要day03能把数组、方法、运算符的细节吃透后面不管是学面向对象还是刷java面试题都会有一种“地基稳了楼上怎么盖都不慌”的感觉。相反如果这天的内容囫囵吞枣后面每次遇到数组传参、循环边界、运算符优先级的问题都要回头翻笔记效率直线下降。6.6 最后分享一个我自己当年踩过的坑我学day03的时候作业里有一个“交换数组两个元素”的方法我自作聪明写了一个public static void swap(int a, int b) { int temp a; a b; b temp; }然后调swap(arr[i], arr[j])发现数组纹丝不动。后来才明白Java传的是值的副本方法内部的交换根本影响不到外面的变量。这就是参数传递的经典陷阱也是我为什么在正文里反复强调基础数据类型值传递、引用类型传引用副本这个知识点。后来我带新人第一件事就是让他们亲手复现这个“bug”做过一次比听我讲十遍都管用。这个坑如果你在day03就踩过后面学方法的时候会感谢这次教训的。
