很多零基础的朋友学Java,最容易卡住的地方往往不是那些看起来高深的框架和中间件,反而是day03这堂课的内容。我见过不少同学,第一天第二天觉得变量和输出很简单,到了第三天学运算符、成员变量、局部变量、方法这四个概念凑在一起的时候,突然就开始懵了。代码能看懂,但自己动手写就不知道从哪下手,编译报错更是一头雾水。
这非常正常。因为day03这四个知识点,是Java从"跟着例子抄"到"自己设计代码"的分水岭。前两天的代码是一个"直来直去的流水账",从第三天开始,你需要理解数据存在哪、运算怎么算、代码块怎么组织,这其实是你第一次用程序员的大脑去思考问题。这篇文章我就把这一天的内容完整拆开揉碎,把我自己学习和带人过程中积累的经验、踩过的坑、总结出来的排查方法全部写出来,希望能帮你把这一关顺利跨过去。
1. 为什么四个知识点要放在同一天讲 —— 先搞清楚学习的主线
很多初学者拿到课件之后习惯从头到尾听,听完就完事,从来不去想编排逻辑。但我建议你停下三分钟,先想想为什么day03要把运算符、成员变量、局部变量、方法这四个东西放在一起。这不是巧合,这四条线拧在一起,才是Java编程最底层的骨架。
1.1 变量解决的是"数据往哪放"的问题
先说变量。你在程序里处理任何数据,不管是一个年龄数字、一个姓名文本,还是一个商品的价格,都需要一个东西把它装起来,这个东西就是变量。
day03把变量拆成"成员变量"和"局部变量",核心目的不是让你背概念,而是让你理解一个极其重要的区分:这个数据是"属于谁"的、它"活多久"、在"哪些地方能被访问到"。我举个生活例子,你在家里放了一个储物盒,放在公共客厅的和放在你自己卧室的,使用权限和存放时间完全不一样。成员变量就像客厅里的盒子,类的所有方法都能动它;局部变量就像你卧室抽屉里的东西,只有你这个方法内部才知道它的存在。
这个区分往大了说,直接关系到你后面学面向对象、学内存分析、学多线程并发时能不能建立起正确的数据观。很多人在学JVM内存模型的时候一脸蒙圈,就是因为在变量这一课没有建立"数据有生命周期"的意识,到了那时候就总感觉各种概念在飘。
1.2 运算符解决的是"数据怎么算"的问题
变量只是存储数据,数据在程序里是要流动、要计算、要比较的。年龄要加1、价格要打个折扣、两个字符串要拼起来、分数要判断是否及格,这些操作全部依赖运算符。
运算符这一块看起来零碎,实际上它是一个完整的体系:算数运算、赋值运算、关系运算、逻辑运算、三元运算。很多初学者把重点放在了记符号上,认为记住了+ - * /就算学会了。但其实真正拉开差距的是三件事:类型转换的细节、短路逻辑的执行顺序、自增自减的边界陷阱。这些点恰恰是后面写复杂业务逻辑时最容易出bug的地方,也是面试和笔试最爱的考点。
1.3 方法解决的是"代码怎么组织"的问题
有了变量存储数据,有了运算符处理数据,接下来最自然的问题就是:一段处理逻辑写完之后,我怎么在别的地方复用它?答案就是方法。
方法的重要性不用我多说,你去看任何一个成熟的Java项目,代码都是由无数个大大小小的方法组成的,一个方法只做一件事,方法之间互相调用。day03为什么把方法放在这?因为前面两步教你的都是"零件怎么造",方法这一步才教你"零件怎么组装"。当你用方法把一个复杂的流程拆分成多个清晰的小步骤,程序的可读性、可维护性会立刻提升一个档次。
所以现在你可以看出来了,这一天内容的主线就是一条线:先有数据(变量),再让数据动起来(运算符),最后把数据处理流程打包成可复用的单元(方法)。搞懂这条主线,你学起来就会有一种"每一步都是为了下一步"的连贯感,而不是在被动的堆砌知识点。
2. 运算符:算得快不算本事,算对才算 —— 新手最容易忽视的细节
运算符这一节,单看符号,任何一个人一天之内都能认全。但把运算符真正用对、用得稳,需要抠很多细节。我把这些年实际开发中踩过、以及看到新人频繁踩的坑,一个一个给你过一遍。
2.1 算数运算符:除法、取余和类型陷阱
整数相除的问题,是我在无数初学者代码里看到过的头号陷阱。两个整数相除,结果还是整数,小数部分直接被丢掉,这不是四舍五入,是直接截断。
int a = 7; int b = 2; System.out.println(a / b); // 输出 3,而不是 3.5 System.out.println(a % b); // 输出 1,取余运算拿到的是余数我看到很多新人写平均值的时候直接(score1 + score2) / 2,结果遇到两个整数加起来是奇数,算出来就少了一截。正常的商品折扣计算、成绩平均分场景,你必须要让其中一个操作数变成浮点数,才能得到带小数的结果:
double avg = (score1 + score2) / 2.0; // 除以2.0,结果就是double这里面的原理是Java的类型提升机制:当int和double做运算时,int会自动被转成double,再参与计算。反过来,如果你非要让double变成int,那就必须强制转换,例如(int) 3.99,结果是3,不是4,依旧是把小数部分直接扔掉。
第二个容易翻车的地方是精度问题。如果你要算钱,千万不要用double。0.1 + 0.2在double类型里不是精确的0.3,而是一个无限接近0.30000000000000004的数。我建议初学者在这一天就养成一个习惯:涉及精确数值的运算,优先考虑用整数(以分为单位的金额)或者后续学习BigDecimal来处理。这个习惯越早建立,后面踩坑越少。
2.2 自增自减:i++和++i到底差在哪
i++和++i这个知识点,几乎是每个初学者都要被绊一下的地方。它们的区别一句话就能说清楚:i++是先取当前值参与运算,然后自己加1;++i是先自己加1,再用加完之后的值参与运算。
int i = 5; int result1 = i++; // result1拿到的是5,然后i变成6 int j = 5; int result2 = ++j; // j先变成6,result2拿到的是6我自己教新人的时候,要求他们在初学阶段尽量不要写这种"一条语句里又取值又自增"的代码。不是因为它难,而是因为它在复杂表达式中非常容易让读代码的人产生误解。比如int x = i++ + ++i;这种写法,不同编译器(在C语言里)可能都有不同的执行顺序,在Java里虽然规范是确定的,但读起来极其痛苦。团队协作中,让人一眼能看懂的代码,远远好过让人想半天的"聪明代码"。
2.3 关系运算和逻辑运算:短路是个好东西
关系运算就是>、<、>=、<=、==、!=这六个,结果永远是布尔值true或false,这个好理解。真正值得讲透的是逻辑运算中的"短路"现象。
&&和||这两个运算符,都有短路机制。什么意思?A && B,如果A已经是false,B根本不会执行;A || B,如果A已经是true,B也根本不会执行。这个机制平时帮我们省了很多事,也带来了一种很常见的写法:
if (user != null && user.getName().length() > 0) { // 如果user为null,右边不会执行,就不会出现空指针异常 }这个写法是把安全判断放在前边,利用短路避免后续调用报错。我自己是强烈建议所有初学者把这种"先判断,再操作"的防御式写法变成肌肉记忆。反过来说,如果你在&&右边写了带副作用的代码,比如flag && (count++),短路发生时count不会自增,这就是一个极其隐蔽的bug,排查起来要人命。
还有一点必须强调:逻辑运算两侧必须是布尔值。很多初学者会写出if (a && b)这种代码,然后抱怨编译不过,理由是在有些脚本语言里1 && 2居然能跑。Java是一个强类型语言,对不起,不行。这其实对你是有好处的——类型检查能在编译期帮你揪出一大批低级错误。
2.4 三元运算符:简洁是优点,嵌套是灾难
三元运算符的格式是条件 ? 值1 : 值2。条件为true取值1,否则取值2。非常适合简单的二选一赋值:
String result = score >= 60 ? "及格" : "不及格";但有些人把三元运算符用成了炫技工具,一层套一层,a ? b : (c ? d : (e ? f : g))。这种代码看起来是在展示水平,实际上是给后来的维护者挖坑。我给出的建议非常简单粗暴:任何时候,三元运算符的嵌套层数不允许超过一层,超过就直接改用if-else。代码是写给人看的,不是写给编译器看的,你自己觉得"当时写得很爽",两周之后回来看一样要骂自己。
2.5 字符串拼接中的运算符陷阱
Java里+有两个身份:纯数字运算时是加法,当任意一侧出现字符串时,它就成了字符串拼接。这个特性很灵活,但也经常带来意料之外的结果:
System.out.println(1 + 2 + "3"); // 输出 "33",先算1+2=3,再拼"3" System.out.println("1" + 2 + 3); // 输出 "123",从左到右,先拼成"12",再拼成"123"看到没有,输出结果完全不一样。这里的执行顺序是从左到右,一旦字符串出现,后面的所有数字都被"带节奏"转成了字符串。这个细节,笔试出题人特别喜欢考,日常开发中打印日志、拼接SQL和提示信息时也容易踩到。
顺便提醒一件事:在循环里用+拼字符串,性能很差。字符串是不可变对象,每次拼接都产生新的对象,循环次数多了会产生大量中间对象。初学阶段不用过于在意,但当你学了StringBuilder之后,要立刻把循环内拼接的坏习惯改掉。
3. 成员变量与局部变量:作用域决定命运
变量这一块,我必须说,很多同学一开始对"成员变量"和"局部变量"的理解停留在"一个写在类里面,一个写在方法里面"这种表层区分,这样做题能过关,但一碰到实际代码就分不清了。这一节我会从存储位置、生命周期、默认值、访问权限四个维度帮你彻底理清。
3.1 定义位置决定了所有后续差异
直接从代码说事儿,先看一个完整的小例子:
public class Student { String name; // 成员变量:定义在类中、方法外 int age; // 成员变量:定义在类中、方法外 public void showInfo() { String msg = "学生信息:"; // 局部变量:定义在方法内部 msg = msg + name + ",年龄" + age; System.out.println(msg); } }name和age是成员变量,它们属于Student这个对象本身。你new出一个Student,这个学生就有名字和年龄的属性;不同的Student对象,各有各的name和age,互不干扰。
msg是局部变量,它只存在于showInfo方法执行期间。方法一调用,它就诞生;方法一结束,它就消失了。下一次再调用showInfo,又是一个全新的msg被创建。
一句话概括两者的本质区别:成员变量描述的是"对象有什么",局部变量描述的是"方法做到一半需要暂存什么"。
3.2 默认值:成员变量有,局部变量没有
这是初学者最容易理解错的地方。成员变量即使你不给它赋值,它也有默认值:int默认0,double默认0.0,boolean默认false,引用类型默认null。局部变量则完全没有默认值,不赋值就直接使用,编译器会直接报错。
public class Demo { int a; // 默认是0,可以直接用 public void test() { int b; // 没有默认值 System.out.println(a); // 编译通过,输出0 System.out.println(b); // 编译报错:可能尚未初始化变量b } }为什么设计上有这种差别?因为成员变量是对象状态的一部分,对象被创建时分配堆内存,系统会统一把这块内存清零;而局部变量在方法栈帧中分配,系统不去做清零操作(为了性能),所以如果你没赋初值就拿去用,拿到的是之前残留的垃圾值,编译器为了避免这种不确定行为,直接禁止你使用未初始化的局部变量。这是一个保护性设计,不是故意刁难你。
3.3 堆与栈:变量各自的"住所"
这一部分经常被认为是JVM进阶的内容,但我觉得day03这天就该有一个初步认知,因为它是理解成员变量和局部变量差异的地基。
- 成员变量:随着对象创建而创建,对象存储在堆内存中,成员变量就住在堆里。堆是全局共享的,生命周期较长,由垃圾回收器统一管理。
- 局部变量:方法被调用时,Java虚拟机为方法分配栈帧,局部变量就住在栈帧里。栈帧在方法执行完就弹出销毁,局部变量随之消失。
我用一个生活类比来解释:堆像是一个巨大的公共仓库,你申请了一块空地放东西(new一个对象),只要还有人拿着这个仓库的钥匙引用它,里面的东西就一直在;栈像是一个临时工作台,你把手头的工具放上去,做完这一项工作,工作台直接清空。
为什么要知道这些?因为后面学内存分析、学垃圾回收、学递归调用、学线程安全的时候,你会无数次和堆栈概念打交道。如果到今天还分不清变量去哪了,后面看什么都没有画面感。
3.4 变量遮蔽与this关键字
有一个很经典的场景:在方法中定义了一个局部变量,名字和成员变量一模一样,这时候局部变量会把成员变量"遮蔽"掉。直接按名字访问,拿到的是局部变量:
public class Student { String name = "默认名字"; public void setName(String name) { name = name; // 这里的name都是参数,成员变量没有被赋值! } }这是新手极其容易写出的bug。参数叫name,成员变量也叫name,name = name这行代码实际上是把参数赋给参数自己,类的成员变量纹丝不动。想要给成员变量赋值,必须用this.name明确告诉编译器"我要操作的是这个对象的成员变量":
public void setName(String name) { this.name = name; // 左边的this.name是成员变量,右边的name是参数 }this在这里就是"当前这个对象"的代称。这个用法这么普遍,以至于在很多公司里,给成员变量赋值的setter方法覆盖了参数命名规范,大家都习惯参数和成员变量同名,然后用this区分。所以this不是可学可不学的高级技巧,而是你从写第一个真实对象开始就要掌握的日常语法。
3.5 一个对比表格,建议收藏
我直接把两者最核心的差异整理成表格,方便你复习时快速回忆。
| 对比维度 | 成员变量 | 局部变量 |
|---|---|---|
| 定义位置 | 类中、方法外 | 方法内部或代码块中 |
| 默认值 | 有默认值(0、false、null等) | 没有默认值,不赋值不能使用 |
| 存储位置 | 堆内存,随对象存在 | 栈内存,随方法调用存在 |
| 生命周期 | 与对象相同,对象被回收才销毁 | 与方法相同,方法结束即销毁 |
| 作用范围 | 类的所有方法中都可以访问 | 只在所定义的代码块内有效 |
| 是否允许同名遮蔽 | 可被局部变量遮蔽,用this调用 | 不同作用域可重名,但嵌套作用域会遮蔽外层 |
4. 方法:把代码块变成可复用的积木
前面学的所有内容,最终都要汇入方法这个"容器"里。方法是Java程序最核心的组织单元,这一节我要把方法的语法、参数传递、重载、返回值这些内容全部讲透。
4.1 方法的完整结构:声明头加方法体
一个方法的完整组成是这样的:
[访问修饰符] [static] 返回值类型 方法名(参数列表) { // 方法体 // 具体执行的语句 return 结果; // 如果返回值类型不是void }拿一个真实方法拆解:
public int add(int a, int b) { int sum = a + b; return sum; }public是访问修饰符,表示这个方法谁都能调用;int是返回值类型,说明这个方法执行完会交出一个int值;add是方法名;(int a, int b)是参数列表,调用时必须传入两个int;方法体内部算出sum,然后用return返回。
方法名加参数列表合起来叫做方法签名。这里有一个初学者老忘的点:方法的签名不包含返回值类型。意思是,你不能定义两个名字一样、参数一样、但返回值不一样的方法,它们会被认为是重复定义,直接编译报错。因为在调用时,Java只靠方法名和参数就能确定"你要调哪个方法",返回值根本参与不了判断(你调一个方法完全可以不接收返回值,那编译器就没办法靠返回值区分)。
4.2 方法调用:栈帧的压栈与弹栈
方法怎么被调用,背后是栈帧的压栈弹栈。这句话听起来高深,其实很简单。每调用一个方法,Java虚拟机就在栈上压入一个栈帧,这个栈帧里装着这个方法的局部变量和必要信息;方法执行完,栈帧弹出。方法里再调方法,就再压一个,像叠盘子一样,一层叠一层。
public static void main(String[] args) { int a = 10; int b = 20; int result = add(a, b); // 调用add,压入add的栈帧 System.out.println(result); // add执行完,栈帧弹出,回到main继续执行 }递归之所以难理解,就是因为递归会不停地把同一个方法的栈帧一层层压进去,直到压到最深处满足终止条件再一层层弹出来。如果你在day03就能理解"每次方法调用都是一次独立的栈帧压弹",后面学递归时会轻松非常多。
4.3 参数传递:基本类型传值,引用类型传"值"
这是Java新手最容易形成错误认知的地方,我今天用最清晰的方式讲一遍。
Java的参数传递只有一种机制,就是值传递。但很多人会疑惑:为什么我传一个数组或者对象到方法里,方法里面改了它的内容,方法外面也跟着变了?这不就是引用传递吗?
不是。关键在于,存引用类型变量的盒子里面,装的不是对象本身,而是对象的地址。你把变量传进方法时,传的是地址的副本,两台"地图"上写的是同一个坐标。你通过这个地址副本去修改地址指向的对象内容,当然会影响原对象。但如果你在方法里直接把参数重新指向另一个新对象,外面的原变量不受任何影响,因为两份地址副本只是"值"相同的不同盒子而已。
public static void changeValue(int num) { num = 100; // 外部没有任何变化,因为num是外部值的副本 } public static void changeArray(int[] arr) { arr[0] = 100; // 外部数组第一个元素变成100,因为arr和外部arr指向同一个数组对象 arr = new int[]{7, 8, 9}; // 外部数组引用不受影响,这里只是改了一本地图副本 }我把这个反复讲,是因为它在开发中真的会引出很多诡异bug。比如你用对象作为参数,不小心在方法里更新了不该更新的字段,数据就"悄悄"变了。理解值传递能帮你建立正确的数据防线:基本类型你可以放心改,引用类型你要时刻清楚,你能改的是对象内部的数据,还是你手里的引用本身。
4.4 方法重载:同一个名字,不同参数
重载这个概念,第一次接触会觉得"这不是套路吗,直接换名字不就行了?"等你写的代码多了就明白,重载是为了让调用方有统一、自然的使用体验。
public int add(int a, int b) { return a + b; } public int add(int a, int b, int c) { return a + b + c; } public double add(double a, double b) { return a + b; }同样都是"相加"这个语义,因为参数不同而有不同版本,调用的时候你不用记住addInt、addDouble、addThreeInts这种乱七八糟的名字,统一叫add,Java根据你传的参数自动挑一个合适的版本执行。
这是标准库最常用的设计手法,你以后写代码时,如果发现好几个方法在做"同一件事、只是参数不同",重载就是最好的整理工具。需要注意,重载与返回值无关,前面已经强调过。与重载容易混淆的"重写"是面向对象阶段的概念,现在不用管,先把手头这个搞清楚。
4.5 返回值与return:方法怎么把结果交出来
return的作用有两个:返回结果、结束方法。一旦执行到return,方法立刻结束,后面所有的代码都不会再执行。这一点在编写"提前返回"逻辑时非常实用:
public boolean isAdult(int age) { if (age < 18) { return false; // 不满足条件,提前结束 } return true; }另外,如果方法的返回值类型是void,也就是不返回任何值,你仍然可以使用return;来提前结束方法,只不过后面不跟值。这个技巧在写那些"发现条件不满足就什么都不干"的方法时特别好用:
public void printIfPositive(int num) { if (num <= 0) { return; // 直接退出,不打印 } System.out.println(num); }新手经常犯的错误是:声明了非void的返回值类型,但某个分支忘记写return,导致编译器报"缺少返回语句"。记住一个原则:只要方法声明了返回值,每个分支路径都必须在最后有一个return,否则编译不通过。编译器在这里不给你任何通融。
5. 一天的实战落地:练手题与报错排查速查表
前面讲了大量的概念和细节,但编程这一行,只听不练等于白学。我给一套我常用的、适合day03水平的小练习和目标要求,再附上新手最常碰到的报错解读。这一节建议你读完就把代码都敲一遍。
5.1 一个综合练习:模拟学生成绩处理
需求是这样的:定义一个Student类,成员变量包括姓名和三个科目成绩;类里提供两个方法,一个计算总分,一个计算平均分;在main方法中创建两个学生对象,调用方法打印结果。
我给出一个参考答案,但强烈建议你自己先写一遍再对照。
public class Student { String name; int chineseScore; int mathScore; int englishScore; public int getTotalScore() { return chineseScore + mathScore + englishScore; } public double getAverageScore() { // 注意除以3.0而不是3,否则会丢失小数 return getTotalScore() / 3.0; } public void printReport() { System.out.println(name + "的总分是:" + getTotalScore() + ",平均分是:" + getAverageScore()); } public static void main(String[] args) { Student s1 = new Student(); s1.name = "张三"; s1.chineseScore = 85; s1.mathScore = 92; s1.englishScore = 78; Student s2 = new Student(); s2.name = "李四"; s2.chineseScore = 66; s2.mathScore = 88; s2.englishScore = 95; s1.printReport(); s2.printReport(); } }这个练习覆盖了成员变量的定义与赋值、方法的定义与调用、参数传递(这里还没有参数,但已经在传对象了)、算术运算等一天的精华。写完之后,你可以试着给两个方法加上参数,比如让printReport接收一个字符串表示评价等级,再输出"张三:优/良/及格/不及格",进一步锻炼参数与逻辑运算。
5.2 三个典型报错,提前帮你排雷
我根据多年经验,把day03阶段最常见的三个编译错误/运行时错误给你点出来,并且告诉你排查思路。
第一个是可能尚未初始化变量。这种报错几乎都出现在局部变量上。解决方法很简单:在使用局部变量之前,明确给它赋一个初始值。比如int sum = 0;再往下累加。很多初学者喜欢声明一排变量准备后续赋值,但Java编译器要求"每一次正式使用前必须有明确的赋值路径",所以与其在声明时偷懒,不如在声明时就把默认值写好。
第二个是不兼容的类型。例如你把double赋值给int,或者把字符串赋值给int变量。这是因为Java的类型检查很严格,窄化转换必须显式写出强制转换符号,不能指望它自动帮你舍弃精度。排查时看一下报错那一行的左右两侧类型,确认是否需要加(int)之类的强转。
第三个是找不到符号。这个报错出现的概率最高,原因也五花八门:变量名拼写错误、方法名写错、访问了不存在的成员变量、变量定义在别的类里没有通过对象引用。我建议你看到这个报错时,先花十秒钟检查拼写和大小写,Java是区分大小写的,Name和name是两个完全不同的标识符。
5.3 新手的几个高频疑问,集中解答
我总结几个day03学员问得最多的疑问,一并回答。
问:成员变量和局部变量能重名吗?能。但方法内部使用时,局部变量优先,成员变量被遮蔽,必须用this才能访问成员变量。我不建议在初学阶段故意重名给自己找麻烦,但看到重名代码要能看懂。
问:a = a + b和a += b有区别吗?大多数情况下等价,但有一个隐藏差异:+=会做隐式的强制类型转换。比如short s = 1; s += 1;能编译通过,而short s = 1; s = s + 1;会编译报错,因为s + 1是int,不能自动转回short。这个坑笔试常考,你可以记一下。
问:方法内部能不能定义方法?Java不允许在方法内部直接定义另一个方法。所有方法都必须定义在类中、方法外。如果你发现需要抽出一个内部逻辑,把它写成类的另一个方法即可。这一点和JavaScript不一样,很多有其他语言基础的人第一次接触Java会犯这个错。
问:一个类里方法太多了怎么办?方法多是正常的,一个真实项目里一个类有几十个方法并不稀奇。按照功能分组、保持方法名清晰、一个方法只做一件事,比任何高级技巧都管用。day03这个阶段,不用追求代码简洁到极致,但要把"一个方法只做一件事"当成纪律来执行。
6. 排查逻辑错误的经验之谈
编译错误其实不可怕,因为编译器把问题都指出来了,照着改就行。真正可怕的是编译通过、运行时结果不对的逻辑错误,这种错误需要你自己去"破案"。我分享几个我总结多年的调试经验。
6.1 不要凭感觉改代码,先加打印
新人发现运行结果不对,第一反应通常是"我觉得可能是这里有问题",然后改一行,再跑,不对,再猜一个地方。这是一种巨大的时间浪费。正确的做法是在关键节点打印中间结果,用数据说话。
public double getAverageScore() { int total = getTotalScore(); System.out.println("调试信息:总分=" + total); // 临时打印 return total / 3.0; }打印出来如果total不是三个成绩之和,问题在getTotalScore里;如果total是对的,而平均分不对,问题大概率出在除法或类型转换上。用打印把问题范围一点点缩小,定位到具体某一行。定位到后,记得把调试打印删掉,否则代码会非常乱。
6.2 程序的执行顺序不是从第一行读到结尾
很多初学者理不清逻辑,是因为大脑里默认"代码是从上到下执行"。这个认知只对main方法内部顶多算部分正确。一旦出现方法调用,执行流程会跳来跳去:从main跳到方法里,方法执行完又跳回main的下一条语句。如果这个方法里又调了别的方法,跳转的层数更多。
我强烈建议你在学day03时,用纸笔画出方法的调用流程。竖着写main方法的执行步骤,遇到方法调用就在旁边往右缩进,写出被调方法的执行步骤。这个方法看起来笨,但真的能帮助你建立"程序是一帧一帧按照调用栈推进"的直觉。等后面学到递归和调试器,这个直觉就是你的隐形优势。
6.3 调试器的使用建议
如果你已经装了IDE,我建议从这一天开始就有意识地用调试功能,而不是只靠运行、看结果。在IDE里给代码行打上断点,然后用Debug模式运行,程序会停在断点处,你可以逐步执行并实时查看每个变量的值。
我个人体会是,调试器的价值不只是排查错误,更是"让代码流动起来"的工具。你亲眼看着变量从0变成85、再跳进方法里算出总分,这个过程比读十遍文档都管用。很多概念在调试器里一目了然:成员变量存在对象里,栈帧里的局部变量怎么压入弹出,方法调用的流转过程,全都是可视化的。新人在day03就掌握Debug操作,之后学习效率会有质的提升。
7. 最后再分享一个关于代码习惯的建议
内容写到这里,核心知识点已经全部讲完。按我自己的经验,day03真正难的不是理解这几个概念,而是"第一次开始像一个工程师一样写代码"。我有几个小建议想留给你。
第一,命名。从今天开始,给变量和方法起名时,禁止用a、b、c这种字母,用有意义的英文单词,多个单词用驼峰命名法。如果你以后要走专业路线,这是一个成本最低但收益最大的投资。我看到太多同学写int a = 10; int b = 20; int c = a + b;,看完代码完全不知道他在算什么。命名清楚,代码的阅读成本至少降低一半。
第二,及时自测。写完一个小方法,立刻跑一下看看结果。不要攒了一堆代码再一次性运行,那样报错了你都分不清是哪段代码引入的问题。每一段代码都"小步快跑",问题尽早暴露,你也能收获更多的正反馈。
第三,做笔记不要抄代码。很多新人的笔记把课件代码原封不动抄一遍,合上电脑什么都想不起来。真正有效的笔记方式是:用自己的话描述这段代码做了什么、为什么这样做、如果改一个条件会怎样。知识只有经过你自己的语言重新组织,才算真的变成你的。
day03是你从"看热闹"到"看门道"的转折点。运算符让你学会处理数据,成员变量和局部变量让你理解数据的归属和生命,方法让你开始像搭积木一样组织逻辑。这些能力组合在一起,你已经能写出一个像模像样的小程序了。剩下的,就是耐住性子,一行一行地敲,一个坑一个坑地踩过去。代码这条路没有捷径,但你迈过的每一个坎,都会在后面的学习中加倍地回报你。