如果让我给刚转 Java 的同学递一份自学资料,我大概率会先把笔记里标着“java学习笔记1.5”的这几页翻出来。这个位置特别尴尬:能看懂一点点代码,但脑子里还没形成完整的语法地图;环境装好了,第一行输出也跑通了,可一旦问“刚才到底发生了什么”,整个人就懵了。这份笔记就是为这个阶段写的。它适合零基础自学的朋友,也适合在学校上完课、但没来得及消化细节的初学者。下面这些内容不是教科书的复读,而是把我当时踩过的报错、验证思路和记录习惯都整理出来,你可以直接照着做。
1. 为什么是 Java?我的入门思路
1.1 起步之前,先想清楚三个问题
我发现零基础同学最容易卡在动手之前,反复纠结三件事:学哪个版本、用什么工具、要不要先学别的语言。这三个问题其实都可以很快解决。
版本上,别太较真。只要选一个长期支持版就行,不用追最新。Java 这么多年下来,核心的东西——类、对象、方法、内存分配——基本没变过。新版本带来的主要是语法糖、性能优化和库层面的更新,初学阶段感受不到太大差别。与其纠结版本数字,不如把时间花在把某个版本的基础语法练熟。
工具选择上,我建议反着来:先用记事本或系统自带的文本编辑器写代码,用命令行编译运行,最后再切回主流 IDE。这不是自虐,而是让你亲眼看到 javac 和 java 这两条命令分别干了什么。很多同学一上来就点 IDE 的绿色运行按钮,代码里的隐式依赖、类路径问题根本看不见。等你能自己解释“我按运行之后,工具到底替我做了哪几步”,再回去用 IDE,你会更清楚它的价值。
至于要不要先学别的语言,完全不用。Java 不需要什么前置语言,唯一的常识准备是:把内存想象成一排带编号的储物柜,每个变量对应一个格子,格子大小不同,能放的数据种类和范围也不同。后面讲数据类型时,你八成要靠这个比喻解决理解障碍。
1.2 环境准备:从 JDK 到命令行都跑通才叫装好
环境安装这一步我在第一次学的时候吃了大亏,只关注“能不能运行”,根本不看装完之后的命令是否齐全。后来我把验收标准固定成一条:打开命令行窗口,敲下面两条命令,都有反应:
java -version javac -version如果 java 有反应,javac 提示“不是内部或外部命令”,说明你只装了运行环境,没装完整开发包。这里有个基本概念必须分清:JDK 是完整开发环境,自带编译器 javac,能把你写的源码变成字节码;JRE 只是运行环境,只能跑编译好的 class 文件,不能编译源码。想写代码,必须装 JDK。
环境变量也常出问题。你需要在 PATH 里加入 JDK 对应目录下的 bin 路径,这样命令行才能在任意目录找到 java 和 javac。改完环境变量之后一定要重新开一个命令行窗口再试,因为窗口启动时才会读取一次环境变量。我当时就是忘了这茬,改了半小时 PATH 都没见效,差点以为自己抄错了教程。
还有一个很容易被忽略的工具叫 jshell,它是 JDK 自带的交互式命令窗口,输入一行代码马上出结果。遇到“这个语法到底会输出什么”的疑问,不用开整个项目,直接在 jshell 里敲几行验证就行。这个习惯能让学习成本降到极低。
1.3 给自学者的一份学习节奏建议
我见过太多人一上来就打算每天刷五个小时视频,结果三天就看不下去了。学习节奏比学习时长重要得多。我更推荐“短输入 + 高频动手”的循环:看二十分钟概念,立刻写一个最小例子,第二天不看书、凭记忆把例子默写一遍。“看懂”和“能写出来”完全是两回事,这句话听起来像废话,等你真正默写失败一次就懂了。
这份笔记里的很多细节,比如 float 字面量要加 f、long 要加 L,我第一次看都觉得自己会了,结果手写时全错。后来我规定自己:每个知识点配一个五分钟能复现的最小例子,在旁边标注“当初为什么记错”,这才把基础一点点焊死。学习笔记不是抄书,是记录你和代码之间的真实碰撞。
2. 第一个 Java 程序:把 HelloWorld 每个词都吃透
2.1 手写文件、编译、运行,完整走一遍
先写一个最经典的程序。新建一个文件,命名为 HelloWorld.java,内容如下:
public class HelloWorld { public static void main(String[] args) { System.out.println("Hello, Java!"); } }保存时注意扩展名必须是 .java。记事本默认会隐藏扩展名,很多人实际保存成了 HelloWorld.java.txt,然后怎么编译都报“找不到文件”。先在文件管理器里打开“显示文件扩展名”,再确认最终文件名。
接着打开命令行,进入文件所在目录,执行:
javac HelloWorld.java java HelloWorld第一行是编译。如果没有任何输出,说明编译成功,目录里会多出一个 HelloWorld.class 文件。这个 class 文件就是字节码,Java 虚拟机认的就是它。第二行才是真正启动虚拟机运行字节码。这里注意,运行命令只写类名,不要带 .class 后缀。如果你写成java HelloWorld.class,反而会报错找不到主类。
初学阶段我强烈建议你观察三件事:编译前后目录里的文件发生了什么变化;javac 报错时第一行提示的是哪个文件、第几行;同样一个程序,用不同目录运行会有什么差异。很多人把编译过程当黑盒子,看到红字就打怵,其实编译错误是最善良的错误,它精确告诉你写错了位置,改掉就好。
2.2 main 方法签名为什么长这样
main 方法那一串修饰符,第一次见确实容易晕。为什么要“public static void”?还要带一个 String[] args?拆开解释其实不难。
“public”是权限修饰符,表示外部可以访问。JVM 启动程序时并不是程序内部的事,它是从外部把类加载进来再调用的,所以入口方法必须公开。“static”让方法可以不创建对象就被调用。JVM 启动的时候还没法去 new 你的类,它需要一个直接能用的入口。“void”表示方法执行完之后不回传任何数据,运行到 main 的结束花括号,主线程就结束了,程序随之退出。
至于 String[] args,它是命令行参数数组。你可以这样给程序传数据:
java HelloWorld hello 123这时 args 数组里有两个元素:args[0] 是 hello,args[1] 是 123。需要动态参数时就从这里读取。很多面试题会绕着 main 方法问,比如它能不能重载、能不能不写,本质都是在考察你是否理解它为什么是固定约定。
我自己的记忆方法是不背修饰符,只想象一个场景:别的程序要启动你这个 Java 应用,总得有个统一入口来找你。想通这一层,public static void 就不是天书了。
2.3 类名与文件名的硬规则,以及第一印象里的传参
Java 要求公共类的类名必须和文件名一致,包括大小写。也就是说,类名是 HelloWorld,文件名也必须叫 HelloWorld.java。改错大小写,编译阶段就会直接报错。这个规则看起来很繁琐,但它是给人和编译器设定的一条清晰协议:看到文件名,就能大概猜到里面有什么类。
为了理解入口和传参,再往前迈一小步。先接受“数组”这个概念就是一连串同类型数据,用下标从 0 开始取。主方法里的 args 就是一个字符串数组。虽然现在还没彻底弄懂数组,但可以先运行这样一段代码:
public class HelloWorld { public static void main(String[] args) { for (String s : args) { System.out.println(s); } } }这段代码用 for-each 循环把传入的每个参数依次打印出来。先不用完全掌握语法,你只需要知道:Java 程序不是只能输出固定内容,它可以接受外部输入,这是程序与外界交互的第一步。后面学数组、学集合,再回头看这段,会发现入口函数和参数传递被串成了一条线。
3. 变量与数据类型:把内存当成带编号的储物柜
3.1 八种基本类型、默认值与选择思路
Java 类型分两大类:基本类型和引用类型。基本类型一共八种,先列表认清:
| 类型 | 占用空间 | 默认值 | 典型使用场景 |
|---|---|---|---|
| byte | 1字节 | 0 | 二进制流、文件字节处理 |
| short | 2字节 | 0 | 很少直接用 |
| int | 4字节 | 0 | 默认整数类型 |
| long | 8字节 | 0L | 大数值、时间戳 |
| float | 4字节 | 0.0f | 省内存但精度要求低的小数 |
| double | 8字节 | 0.0d | 默认浮点类型 |
| char | 2字节 | '\u0000' | 单个字符 |
| boolean | 不固定 | false | 逻辑判断 |
表里的“默认值”概念只针对类的成员变量。局部变量完全不一样,声明后不赋值直接用,编译阶段就会被拦下来。这是新手最容易踩的区别:在类里写一个 int count; 不赋值,它是 0,能直接用;但是在 main 方法里写 int a; 然后加一,报错“可能尚未初始化变量”。原因在于成员变量会被系统自动初始化,而局部变量没有这个待遇。
选择类型有个简单原则:整数默认 int,小数默认 double,拿不准的时候用更大的类型。但要注意 long 字面量要带 L,float 字面量要带 F,否则编译不过。不要把“默认值 0”和“局部变量没初始化”混为一谈,那完全是两套规则。
3.2 字面量、类型转换与溢出,敲一遍比背十遍管用
类型转换的规则是“小范围自动转大范围,大范围转小范围必须强制”。这个说法很抽象,直接看代码:
int a = 100; long b = a; // 自动转换,int 一定装得进 long int c = (int) b; // 强制转换,手动承担风险自动转换不会丢数据,强制转换可能丢。强制转换的本质是截断高位,比如一个很大的 int 强转成 byte,结果往往不是你以为的数。能用小范围,就别强行塞进更小范围。初学阶段只要记住:需要显式写一对括号加类型名,就是在做有风险的事。
更隐蔽的问题是溢出。int 最大能表示 2147483647,再加一,会循环回最小值:
int max = 2147483647; max = max + 1; System.out.println(max); // -2147483648这个结果看起来像 bug,其实是整数在内存里按补码存储、自然溢出的结果。实际开发里很多人用 int 存时间戳或金额,后面加一加就变成负数,排查很久才怀疑到溢出。避免的办法很简单:一开始就选 long,别等出问题再全局替换。
字面量方面,还有一个雷是小数默认类型。直接写float pi = 3.14;会报错,因为 3.14 默认按 double 处理,把 double 放进 float 变量是降级操作,必须写成3.14f。类似地,long n = 10000000000;也会报错,因为超出 int 范围又没加 L。这些规则背起来很容易,但只有亲手敲一遍报错,才能在脑子里留下记号。
3.3 先认识 String:最常用的引用类型
学完基本类型,马上会遇到 String。String 不是基本类型,它是一个类,属于引用类型。两个关键点提前打预防针:第一,字符串用双引号,字符用单引号。第二,字符串变量保存的并不是字符串本身,而更像是字符串的“门牌号”。
最开始的代码里已经写过System.out.println("Hello, Java!"),这里双引号包的就是字符串。如果想表示单个字符 A,应该写char c = 'A';。符号写错,编译直接报错。
关于引用类型,你现在只需要建立一个印象:String s = "abc"; 这行代码里,s 是保存在内存栈里的地址,真正的字符串内容在内存其他区域。后来学数组、学对象,会发现所有引用类型都是这个套路:变量不是东西本身,而是指向东西的门牌号。这个认知越早建立,后面学空指针越不慌。
4. 流程控制与代码习惯:先学会写直的代码
4.1 if/else 与 switch 怎么选
条件判断是程序的分岔口。if 适合范围判断,比如 x 大于 0 且小于 10;switch 适合某个变量只有几个固定取值的情况。判断标准很简单:能不能列出一个确定的取值清单?能,优先用 switch;不能,就用 if。
switch 的典型写法:
int status = 2; switch (status) { case 1: System.out.println("处理中"); break; case 2: System.out.println("已完成"); break; default: System.out.println("未知状态"); }每个 case 结尾必须有 break,否则代码会继续执行下一个分支,这叫“坠落”现象。它有时候是技巧,但初学阶段请把 break 写全,等以后真需要坠落时再刻意使用。
写 if 时,我有个习惯:能不用 == true 就不写。比如变量叫 isLogin,直接写if (isLogin)就够,多写 == true 反而累赘。还要注意赋值运算符 = 和比较运算符 == 的差别,Java 编译期会拦截一部分错误,但逻辑层面的错误仍然防不胜防。代码的缩进必须统一,花括号要么都在行尾,要么都独占一行,不用纠结哪种风格最好,只要全程一致,就能避免大半“括号不匹配”的问题。
4.2 循环边界、break 与 continue
循环是新手喜欢写错的地方,最常见的是“差一个数”。看这段:
for (int i = 0; i < 10; i++) { System.out.println(i); }它会输出 0 到 9,一共十个数字。这里用的是半开区间习惯:包含起始值 0,不包含结束值 10。以后遍历数组时,写< 数组长度,天然不会越界。很多“数组越界异常”就是因为在循环里写了 <= 条件。
while 适合循环次数不确定的场景,比如一直读输入直到遇到结束标记。do-while 至少执行一次,与普通 while 的区别就这一点,但行为差异可以直接影响程序结果。初学阶段最容易死循环:循环变量忘了更新。程序刷屏停不下来时,先看循环变量有没有变化,再看条件是否永远成立。
break 是跳出整个循环,continue 是跳过本次迭代继续下次。这两个词光背定义容易混,写一个“打印 1 到 10 中能被 3 整除的数”的小脚本,用 continue 跳过不符合条件的数,立刻就能分清。循环里的边界注释很值得写,比如“i 从 1 到 99,步长 2”。三周后你回读代码不会记得当时的意图,注释不是解释语法,是保留当时的思考路径。
4.3 命名、缩进和注释:基础但回报率最高
流程控制熟悉之后,下一步不是学更多语法,而是建立代码习惯。命名用驼峰式,类名首字母大写,变量名和方法名首字母小写。这种约定没有强制到由语言报错,但团队协作时别人看到你的变量名 BookName 会被逼疯。规范命名最大的受益者是不久后的自己。
缩进要一致。IDE 都有自动格式化快捷键,保存前按一下,代码立刻整齐。零基础学 Java 的头两周,与其纠结怎么写高级算法,不如先把每个花括号、每个缩进都弄整齐。代码整洁度直接影响你排查 bug 的速度。
注释方面,忌讳写废话。int count = 0; // 定义count为0这种注释毫无价值。真正有价值的注释是“这一段为什么要这么写”,比如“因为这里要按时间倒序输出,所以先反转列表”。写注释时假装自己在给三天后的自己留便签,写出来的内容自然有用。
5. 方法与面向对象第一站:先从“模板”理解类
5.1 方法:从复制粘贴走向复用
方法设计的核心是复用。要是发现两处代码结构一样,只是参数不同,就该抽方法。一个最简单的例子:
public int add(int a, int b) { return a + b; }方法由修饰符、返回值类型、方法名、参数列表、方法体组成。调用时 add(1, 2) 里的 1 和 2 是实参,方法签名里的 a 和 b 是形参。初学阶段最难理解的是参数传递到底传了什么:Java 传参是值传递。基本类型传的是副本,所以方法里改参数不会影响外面的变量。引用类型传的是对象引用,方法里通过引用改对象属性,外部能看到变化。
有一个经典例子,新手总是想写一个 swap 函数交换两个整数,结果发现外面根本没变:
public static void swap(int a, int b) { int tmp = a; a = b; b = tmp; }这是正确行为,因为方法内部改的是形参副本。等学到数组和对象的参数传递,你会反过来利用这个特性。方法返回值方面,还有个观念要纠正:return 不会“崩溃”,它只是结束当前方法回到调用处。用“前置校验 + 提前返回”替代一长串嵌套 if-else,代码会清爽很多。
5.2 类与对象:图纸、房子和门牌号
面向对象最需要一次生活类比。类就是建造图纸,对象就是按图纸造出来的房子。图纸定义了一栋房子有哪些房间、几个门,但它本身不是房子。对应到代码:
class Student { String name; int score; void introduce() { System.out.println("我叫" + name); } }这个 Student 是模板,还没有实际姓名和分数。要真正使用,必须 new:
Student s1 = new Student(); s1.name = "某同学"; s1.introduce();new 的过程,就是在内存里找到一块空间,按照模板把它初始化成具体对象。s1 这个变量不是对象本身,而是对象的引用,你可以理解成门牌号。门牌号能带你去找到房子,但门牌号不是房子。
后续学数组时会发现一个高频雷区:Student[] arr = new Student[3]只是建了能放三个门牌号的架子,架子本身没有房子,数组元素默认全是 null。如果不逐个 new,直接访问 arr[0].name,就会报空指针异常。这个错误的根源就是“对象还没创建就急着通过引用访问属性”。有了图纸、房子、门牌号这个三层关系,空指针其实很好理解。
5.3 构造方法:初始化不是玄学
类里面还能写一个和类名同名的方法,叫构造方法。它用来在 new 的瞬间把对象初始化到位。比如:
class Student { String name; Student(String n) { name = n; } }创建时就传入名字:
Student s1 = new Student("某同学");这样就不用先 new 再一行行赋值。构造方法没有返回值类型,也不能写 void,这是它和普通方法的显著区别。初学阶段经常有人问“构造方法到底能不能不写”,答案是如果类里不写构造方法,编译器会自动加一个无参构造方法;但一旦你自己写了带参构造方法,原来的默认无参构造就不会自动生成了。这个细节经常导致 new User() 突然报错,排查时一头雾水。
顺着这个点,建议把初始化流程的固定顺序记一下:类加载、分配内存、字段默认值、执行构造方法体内代码。不用背教科书术语,记住“先有默认值,再被你的代码覆盖”就够用。这能解释为什么成员变量可以先不赋值,也能解释构造方法里为什么能直接给成员变量赋值。
6. 我踩过的坑和排查顺序
6.1 编译错误、运行异常、逻辑 bug:先分清敌友
自学没人帮忙看代码,最烦的是报错看不懂。我建议把所有问题先分成三类。
第一类,编译错误:语法没写对,javac 报错。比如漏分号、括号不匹配、大小写写错、类名和文件名不一致。这类错误最好修,改成 javac 提示的位置即可。记住一点:从第一个报错开始改,不要从最后一个开始,因为后面的错误经常是第一个错误引发的连锁反应。
第二类,运行错误:编译通过,运行到某一行才抛异常。比如除数为零、数组越界、空指针。这时最关键的是看异常堆栈。异常信息第一行给出异常类型和描述,下面一排“at 某类第几行”。你要找的分界线是:找到你项目代码里第一次出现的 at 行,那里通常就是问题真正的起点。别被后面几十行框架代码吓住,往下翻到自己的代码再看。
第三类,逻辑错误:最麻烦,程序不报错,运行也顺畅,结果就是不对。这类问题不能靠读报错信息解决,只能靠分段验证。常见的是整数除法问题,比如输出 5 / 2 期望得到 2.5,结果得到 2,因为两个整数相除会截断小数。改成 5.0 / 2 或 5 / 2.0 才算真小数计算。这类细节不分类,永远等于在猜谜。
6.2 我最常用的排查顺序,看完就能抄
我总结了一套傻瓜式的排查流程,出问题按顺序来,基本都能兜住:
- 把出问题的那段代码单独抽出来,放到一个临时的小文件里。不要在满屏项目里翻来翻去,能独立就独立。
- 在关键位置打印中间变量。System.out.println 是最土的调试器,但定位速度真的快。
- 注释掉一半代码,看问题还在不在。在的话说明出在剩下那一半里,继续二分,几次就能定位到具体一行。
- 修完之后不要立刻跑下一个功能,先问自己“为什么之前没考虑这种情况”,把教训补回笔记。
这个流程不止适用于 debug,也适用于学习新知识点。遇到一个看不懂的概念,我反对囤视频反复看,更主张看一个最小例子,亲手跑通,然后把报错信息记下来。时间久了,你会积累一份属于自己的“报错速查表”,那比任何完整教程都珍贵。
6.3 两个低门槛自测练习和笔记技巧
最后留两个低成本的自测练习。第一个,写一个程序,计算从 1 加到 100 的和,分别用 for 循环和 while 循环各写一遍。这个练习能验证你对循环边界和累加器的掌握程度。第二个,写一个猜数字程序,程序随机生成一个 1 到 100 的数,用户输入猜测,程序提示猜大了还是猜小了,直到猜中。这个练习会用到条件判断、循环、变量经验和基本输入输出,做完基本能把前面所有零散知识点串起来。
笔记方面,我强烈建议不做“百科全书式记录”,而是做“踩坑日记”。每个知识点配一个最小例子,旁边标注当初为什么记错。比如我自己写着写着旁边冒出一句:“long 不加 L 居然编译不过,因为 100 默认是 int”。这种笔记不需要排版精美,但回头看时,每一个字都能唤起当时的真实记忆,学习效率反而高。
最后分享一个小技巧:这份“java学习笔记1.5”并不是一次写完的,而是反复翻看才慢慢补厚的。每次重新读自己的笔记,我都会顺手改掉几个已经过时的例子,或者把某个语法描述写得更通俗。最有用的部分不是那些整齐的语法总结,而是我记下的各种“原来我当时在这里想岔了”。当你也能在笔记里写出这句话,说明那一处基础基本焊死了。接下来可以放心往集合框架、异常处理和更深入的面向对象方向走,但走之前,建议先把这份笔记里的代码亲手复现一遍,而不是急着刷下一课。