别人问起“Java SE 到底该怎么学”,我一般不会先甩知识清单,而是反问一句:你学完它之后想干什么?是想考个证、找一份后端开发的实习,还是纯粹想弄明白别人写的 Java 项目为什么长那个样子。目标不一样,学习路径和用力方向差别很大。这篇内容就是基于我自己带新人、自己啃源码的经历,把 Java SE 学习这件事从头到尾拆一遍,从 JDK 版本选择、环境搭建,到面向对象怎么才算真学会,再到实战路径和排错技巧,尽量把那些文档里没人明说、但实际开发天天踩的东西讲透。
如果你正准备系统学 Java SE,或者学了一段时间总觉得知识点是散的、像背了一堆概念却写不出像样的代码,这篇文章适合你。下面内容默认你有最基本的编程概念(比如变量、循环、函数),不需要任何 Java 基础。我会把关键步骤讲得比较啰嗦,因为很多坑就是“当初没人多解释一句”造成的。
1. Java SE 学习地图:先看清全貌再动手
1.1 拆开 Java SE 这个名字
Java SE 三个字母好理解,Java Standard Edition,Java 标准版。它本质上是一条 JDK 加上一套标准类库。JDK 再往下拆,是 JRE(Java 运行时环境)、JVM(Java 虚拟机)和一堆开发工具。初学者最先要建立的一个观念是:Java 不是靠操作系统直接执行的,而是靠 JVM 执行的。
这意味着你写的代码会被 javac 编译成字节码(.class 文件),字节码再由 JVM 逐条解释或即时编译成机器码。跨平台就是这么来的:同一个 .class 文件,放在装了 JDK 的 Windows、Linux、macOS 上,都能跑。代价是启动时多了一层 JVM,内存占用、启动速度天然比不上 C/C++ 编译出的原生程序。但换来的是极其成熟的生态和工程化能力,后端领域项目基本都认它。
Java SE 范围具体包括:
- 基础语法:变量、数据类型、运算符、流程控制,这部分什么语言都差不多,区别只在细节
- 面向对象:类、对象、继承、接口、多态、封装,这是 Java 的灵魂
- 核心类库:String、集合框架、泛型、异常处理、IO/NIO、多线程、日期时间 API、反射、注解
- JVM 相关基础:内存区域的划分、类加载机制、垃圾回收的基本印象、性能调优的入门参数
很多人学到集合、IO 就开始晕,觉得东西太多记不住。我建议你先别慌,把核心类库当作“工具库”来学——不需要背每个方法,但要清楚用到某个场景时去哪个包里找答案。
1.2 为什么先说清楚学什么、不学什么
有个很现实的点:Java SE 的内容量很大,但并不是所有块对初学者都同等重要。如果目标是一两个月后能做点小项目、能读懂主流开源项目的代码,优先级应该是:
| 优先级 | 内容 | 理由 |
|---|---|---|
| 高 | 基础语法、面向对象、集合、字符串、异常 | 写任何 Java 代码都绕不开 |
| 高 | IO 与文件读写、多线程基础 | 后端开发、工具类开发天天用 |
| 中 | 泛型、注解、反射 | 读框架源码必须懂,但初学阶段能看懂概念即可 |
| 中 | 枚举、日期时间 API、正则 | 实际开发常用,可以边用边学 |
| 低 | Swing/AWT 图形界面 | 除非做桌面端,否则现阶段不用深入 |
| 低 | 网络编程、JVM 调优、类加载细节 | 属于进阶内容,学到一定程度再补 |
这样安排的原因很朴素:人的注意力有限,先在最短时间内获得“能写、能跑、有反馈”的正循环,比把知识体系画得大而全更重要。我先学 Swing 的那批同学,折腾半天窗口控件,后来工作里基本没用上。
1.3 JDK 版本选型:到底装哪个
JDK 版本是新手第一个容易纠结的点。市面上主流的说法是:
- JDK 8:虽然老,但大量存量项目还跑在它上面,Spring Boot 2.x 时代的主流
- JDK 11:有了很多新特性,但处于一个略显尴尬的过渡位置
- JDK 17:目前主流新项目的热门选择,LTS 版本,Spring Boot 3.x 要求的最低版本
- JDK 21:最新 LTS,虚拟线程等新特性非常亮眼,适合想尝鲜的同学
我的建议很直接:如果你是零基础学习,直接装 JDK 17 或 JDK 21,不要回头去装 JDK 8。原因有二:第一,17 和 21 在语法上兼容绝大多数老代码,学习期写的代码不会踩版本坑;第二,Oracle 对 JDK 8 的公开免费更新早停了,很多还在用 8 的项目其实是基于 OpenJDK 或者第三方发行版在维护。你学新不学旧,未来对接新框架也更顺利。
装完 JDK 后,留意发行版选择。理论上 OpenJDK 和 Oracle JDK 在功能上差别已经很小,我给新人的建议是直接用开源发行版,比如 Eclipse Temurin(Adoptium 社区维护的 OpenJDK 构建)。下载解压、配置好 PATH 就能用,没有授权方面的心理负担。
提示:配置 PATH 时,把
JAVA_HOME/bin加到系统 PATH 的最前面。否则如果机器上装了别的软件自带 JRE,可能命令行执行的不是你要的版本。配置完在终端输入java -version和javac -version验证,确认两个命令显示的版本一致。
2. 跑通第一个程序:从环境搭建到理解机制
2.1 JDK 安装与环境变量:一次配好的实操指南
这里直接给一份可复制的步骤,Windows 和 macOS 略有差异,但思路一样:
- 访问 Adoptium 官网下载对应系统的 JDK 17/21 安装包(.msi、.pkg 或者 tar.gz 均可)
- 安装到一个目录。Windows 常见路径
C:\Program Files\Eclipse Adoptium\jdk-17.0.x,macOS 装完一般在/Library/Java/JavaVirtualMachines/下 - 配置
JAVA_HOME环境变量,指向 JDK 安装根目录 - 把
%JAVA_HOME%\bin(Windows)或$JAVA_HOME/bin(macOS)加入 PATH - 终端执行
java -version,看到类似openjdk version "17.0.x"的输出,说明环境 OK
有个小细节:JAVA_HOME 一定要配置,不只是 PATH。因为很多工具(Maven、Gradle、Tomcat、IDEA)启动时直接找 JAVA_HOME,而不是靠 PATH。java -version能跑不代表 JAVA_HOME 配对了,很多人后面装 Maven 才发现问题,归根结底是当初偷懒少配了一个变量。
配置完环境变量之后,新手最容易兴奋地去双击 .java 文件,这里要纠正一下:双击不可能运行 Java 程序,Java 代码先要编译,再用 JVM 加载。下面这段流程非常重要,我建议每个人在 IDE 开发之前,先用记事本手动完成一次:
// HelloJava.java public class HelloJava { public static void main(String[] args) { System.out.println("Hello, Java SE!"); } }在命令行里:
javac HelloJava.java java HelloJavajavac编译后生成HelloJava.class,java命令执行它。你会看到控制台输出Hello, Java SE!。
这里最关键的一点是:类名必须与文件名一致。因为 JVM 在加载类时,是严格按照全限定类名去找对应文件的。想省几步取名“Hello”结果文件名存成“HelloJava”,编译时立刻报错。这也是初学者第一个“看不懂”的报错。
2.2 编译与运行到底发生了什么
在上面手动编译运行的过程中,脑子要跟着形成一条完整链路:
.java 源文件→ 词法分析/语法分析 → 生成字节码.class 文件→ 由 JVM 类加载器加载 → 字节码校验 → 解释/即时编译 → 执行
这段过程不用去背名词,但要理解几个性质:
- 字节码不是二进制机器码,所以它不能直接在物理机上运行,必须借助 JVM
- JVM 一次只跑一个 main 方法,入口就是
public static void main(String[] args)。public表示对外暴露,static表示不依赖对象实例就能调用,void表示不返回值,args用来接收命令行传入参数。这个签名以后会一直陪着你 - 字节码是惰性加载的,用到哪个类才加载哪个类,所以程序启动时不会把所有类全读进内存
很多人学完 Java 还是搞不清 IDE 里的“运行按钮”到底做了什么。说到底,IDE 只是帮你自动拼好了javac和java这两条命令,然后捕获输出回显在控制台。把底层链路想清楚后,遇到“模块不存在”“ClassNotFoundException”这类问题,就不会两眼一抹黑。
2.3 IDE 选择与第一个工程
环境通了之后,我建议直接用 IntelliJ IDEA Community Edition。免费、跨平台、对 Java 的支持最全面,社区版对学习阶段完全够用。Eclipse 和老牌 MyEclipse 不是不好,只是 IDEA 的补全、重构、快捷键设置更接近现在主流团队的真实使用习惯。
导入工程时有一个细节:一个“Project”里可以建多个 Module,别每次新建都重新建一个窗口。新人常见操作是每写一个小练习就 New Project,结果 IDEA 里堆了几十个相互独立的窗口,管理混乱,还容易搞混 JDK 版本。正确做法是:新建一个空 Project 作为你的学习仓库,再往里面挂多个 Module,每个小练习一个 Module,这样目录整齐、JDK 配置统一、将来想上 Git 也方便。
我典型的学习目录结构是这样:
my-java-study/ ├── pom.xml # 如果没引入 Maven 可以先忽略 ├── module01-basics/ │ └── src/main/java/ ├── module02-oop/ │ └── src/main/java/ └── module03-collection/ └── src/main/java/IDEA 里运行代码也很简单:在写了main方法的类上右键,选择 Run。底层还是执行刚才那套 javac + java 的流程,但省去了手敲命令的麻烦。
心得:从第一天就养成“手工命令行编译运行 + IDE 运行”都试一遍的习惯。不是让你以后都敲命令行,而是至少要理解 IDE 那一个绿色箭头背后发生了什么。否则以后排查"打包能过、本机跑不起来"的问题会很痛苦。
3. 核心语法进阶:从“会用”到“懂了”
3.1 面向对象不是概念,是编码习惯
Java 里的面向对象被很多教程讲成了三个词:封装、继承、多态。这没错,但太抽象了。用实际场景来说,面向对象的第一目标是把数据和对数据的操作绑在一起。
我举一个最常给新人讲的例子:
public class Student { private String name; private int score; private int age; public Student(String name, int score, int age) { this.name = name; this.score = score; this.age = age; } public boolean isAdult() { return age >= 18; } public String getGrade() { if (score >= 90) { return "优秀"; } else if (score >= 60) { return "及格"; } else { return "不及格"; } } }对比完全没有封装的写法——把name、score、age全用 public 字段暴露,然后在外部到处 if 判断。一开始两种写法的代码量差不多,但项目一旦变复杂,外界可以直接改student.score = -1之类非法数据时,灾难就来了。封装的意义是不让外部直接触碰内部数据,而是通过方法(getter、setter、行为方法)来操作。
继承关注的是复用。比如除了Student,你还有Teacher,两者都有name、age,都有introduce()方法,那就可以抽一个公共父类Person。但继承要克制,经验法则是:能用组合(一个类持有另一个类的引用)就不要优先用继承。因为继承会让类层级越来越深,改父类容易误伤一堆子类。这不是理论洁癖,是我见过太多“三层继承还不够、非要四层”最后维护到崩溃的代码。
多态的价值在“面向接口编程”。接口定义一组能力约定,具体实现由不同的类提供。比如:
public interface MessageSender { void send(String to, String content); } public class EmailSender implements MessageSender { @Override public void send(String to, String content) { System.out.println("发送邮件给" + to + ":" + content); } } public class SmsSender implements MessageSender { @Override public void send(String to, String content) { System.out.println("发送短信给" + to + ":" + content); } }调用方只需要握着一个MessageSender引用,运行时具体是邮件还是短信,由外部决定。软件开发里最常说的“开闭原则”——对扩展开放,对修改关闭——就是用多态实现的。我第二年带项目时,把一批硬编码的 if-else 分支改成这种接口分发之后,代码清爽程度完全不是一个量级。
3.2 集合框架:选型比背方法重要
Java 集合是实际开发中使用频率最高的类库。先梳理体系:
Collection接口下有List、Set、QueueList常用实现:ArrayList、LinkedListSet常用实现:HashSet、LinkedHashSet、TreeSetMap常用实现:HashMap、LinkedHashMap、TreeMap、ConcurrentHashMap
很多初学者把精力花在死记ArrayList和LinkedList的区别上。我的建议是理解关键差异就够:
ArrayList底层是数组,随机访问快,中间插入删除慢LinkedList底层是链表,中间插入删除相对有优势,但随机访问慢- 业务代码 90% 情况下用
ArrayList就行,LinkedList别主动炫技
HashMap则是重中之重。它底层是数组 + 链表/红黑树,根据 key 的hashCode()定位到数组下标,哈希冲突时用链表(长度达到阈值转红黑树)挂载。用它的核心原则就一句话:key 对象必须正确重写equals()和hashCode()。如果只用 String、Integer 这些现成类做 key,没问题;一旦自定义对象做 key,两个方法不重写,相同的业务主键可能被当成两个不同 key,导致 get 永远返回 null。
再强调一个遍历时的高频坑:边遍历边删除集合元素,必须用迭代器的 remove() 或直接使用removeIf()。直接list.remove()会触发ConcurrentModificationException,因为集合的修改次数与迭代器预期次数不一致。这个异常可能很多新手第一次遇到会一脸懵,其实原理就一句话:迭代器创建时记录了一个期望modCount,你每通过集合自身方法改一次结构,modCount就变一次,迭代器发现不一致就立刻抛异常保护数据。
3.3 异常处理与 IO:程序世界的“安全带”
异常机制的本质是把“错误处理”从正常业务逻辑中分离出去。Java 的异常体系顶层是Throwable,下面分Error和Exception。Error(如OutOfMemoryError)一般不用捕获;Exception里又分运行时异常(RuntimeException子类)和受检异常(编译期要求必须处理的那一类)。
我给新人定一条非常实用的边界:
- 受检异常(比如
IOException、SQLException):必须 try-catch 或方法上 throws,这是编译器在提醒你“这个操作可能会失败”,比如读文件时文件不存在、连接数据库时网络不可达 - 运行时异常(比如
NullPointerException、IllegalArgumentException):编译器不强制处理,但需要靠代码编写习惯避免。绝不该用catch (Exception e) {}空吃掉,这种代码在线上会掩盖真正的问题
异常处理的标准姿势是先尽量缩小 try 块的范围,只包裹可能抛异常的代码,别把十行业务逻辑全塞进去。同时捕获异常后要记录日志,至少把异常堆栈打出来,别只e.printStackTrace()然后假装没事。
再来看 IO 和文件操作。Java 的 IO 体系老版本确实繁琐,InputStream、OutputStream、Reader、Writer四个抽象根加上各种包装类。JDK 7 之后引入了 NIO.2 的Path、Files,日常读写文件清晰多了:
Path path = Path.of("notes.txt"); String content = Files.readString(path); Files.writeString(path, "新的内容");这里最想提醒的是try-with-resources语法:
try (BufferedReader reader = Files.newBufferedReader(path)) { String line; while ((line = reader.readLine()) != null) { System.out.println(line); } } catch (IOException e) { // 处理异常 }使用这个语法后,reader会在 try 块退出时自动调用 close() 释放资源,不需要手动在 finally 里判断 null。这在老代码里是大量样板代码的来源,也是资源泄漏的高发区。学到这里的时候,把“字符流、字节流、缓冲流、转换流”这几个概念过一遍即可,不用把每个包装类组合都背下来。
IO 最后绕不开编码问题。读取文本文件时强烈建议直接指定字符集,比如Files.newBufferedReader(path, StandardCharsets.UTF_8)。很多人程序里中文乱码,原因就是 JVM 默认字符集和文件保存的字符集不一致。这是个悲哀的地图炮问题,但踩过的都懂。
4. 实战路径:用代码串联知识点
4.1 从零到一:用面向对象做一个图书管理器
理论说得再漂亮,不写代码都是白搭。我建议第一个完整小项目就做一个基于内存的图书管理器,覆盖范围包括:类设计、List/Map 操作、方法拆分、基本异常处理。
首先要做的是领域建模。可以设计这样几个类:
Book:表示一本书,包含 id、书名、作者、价格、是否借出Library:表示图书馆,内部持有一个Map<String, Book>或List<Book>Main:作为程序入口,提供菜单循环,接收用户输入
Book类长这样:
public class Book { private String id; private String title; private String author; private double price; private boolean borrowed; public Book(String id, String title, String author, double price) { this.id = id; this.title = title; this.author = author; this.price = price; this.borrowed = false; } public boolean isBorrowed() { return borrowed; } public void borrow() { if (borrowed) { throw new IllegalStateException("这本书已经被借出了"); } this.borrowed = true; } public void returnBook() { if (!borrowed) { throw new IllegalStateException("这本书没有被借出,不能归还"); } this.borrowed = false; } // getter、toString 省略 }Library类负责管理书籍:
public class Library { private final Map<String, Book> books = new HashMap<>(); public void addBook(Book book) { if (books.containsKey(book.getId())) { throw new IllegalArgumentException("书籍ID已存在: " + book.getId()); } books.put(book.getId(), book); } public void borrowBook(String bookId) { Book book = books.get(bookId); if (book == null) { throw new IllegalArgumentException("没有找到ID为 " + bookId + " 的书"); } book.borrow(); } public List<Book> searchByTitle(String keyword) { List<Book> result = new ArrayList<>(); for (Book book : books.values()) { if (book.getTitle().contains(keyword)) { result.add(book); } } return result; } }上面这段看起来平平无奇,但把几件事练到位了:
- 封装:
Book的borrowed字段是私有的,只能通过borrow()、returnBook()修改 - 集合使用:库存用
HashMap保证按 id 快速查找,搜索结果用ArrayList存放 - 异常语义:用
IllegalArgumentException和IllegalStateException区分“调用方参数错误”和“对象状态不合法” - 防御式检查:
borrowBook里先get再判断 null,避免空指针
做完这个项目,再给它加几个迭代需求:支持按作者筛选、支持修改书籍价格、支持统计未借出数量。每一项新需求都会逼着你重新思考自己的设计,这才是学习的真正过程。
4.2 多线程基础:什么时候才需要并发
多线程是 Java SE 里不算容易但必须碰的一块。学习前先建立观念:线程是为了让多个任务并行/交错执行,但不是越多越好。多线程带来的问题是数据竞争、死锁、上下文切换开销,复杂度是指数级上升的。
先掌握三种创建线程的方式:
- 继承
Thread,重写run()方法(不推荐,因为 Java 单继承限制,继承了 Thread 就不能继承别的类) - 实现
Runnable接口,传入Thread构造器(推荐) - 实现
Callable<T>接口,配合FutureTask获取返回值
日常开发里,直接手动 new Thread 的频率很低,更多是把任务丢给线程池。但学习阶段理解Thread本身依然很重要,因为它牵出几个核心问题:
- 共享变量的可见性:一个线程改了变量,另一个线程不一定立刻看到,需要用
volatile或加锁保证 - 原子性问题:
count++不是一行原子操作,底层是“读取-修改-写回”三步,多线程并发时可能丢更新 synchronized的原理:锁对象、锁代码块、锁方法三种用法分别是锁什么东西
一个典型并发示例:
public class Counter { private int count = 0; public synchronized void increment() { count++; } public synchronized int getCount() { return count; } }synchronized保证同一时刻只有一个线程能执行increment(),从而避免原子性问题。它的底层是基于对象监视器(monitor)实现的,原理在初学阶段不明白也可以先会用,等学到 JVM 再回头补。
练习并发最推荐的小项目是模拟多窗口售票:多个线程同时从同一票池减票,要求最终不能超卖。这个题目把可见性、原子性、锁全都串起来了。
4.3 调试与单元测试:写出可信赖的代码
很多自学者跳过了 debug 和单元测试,导致项目一复杂就“不知道错在哪”。我强烈建议从学习第一天就把 IDEA 的 Debug 模式用起来。Debug 的核心操作就四个:
- 在行号左侧点一下,打上断点
- 以 Debug 模式运行
- 程序执行到断点处暂停,在 Debugger 面板查看变量的当前值
- 用 Step Over(单步执行)、Step Into(进入方法内部)逐步推进
理解这些操作真正价值的一句话:调试的本质是把黑盒执行过程变成可见的白盒流程。你觉得程序不对劲,但不知道哪里不对,那就打断点看每一步变量的变化,总能找到第一个“值不符合预期”的位置。与其靠瞎猜加打印日志,调试器的效率高得多。
再就是 JUnit。在 Java SE 学习阶段不用学太深,掌握几个注解就够了:
import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; public class BookTest { @Test void testBorrowSuccess() { Book book = new Book("1", "Java编程思想", "Bruce Eckel", 99.0); book.borrow(); assertTrue(book.isBorrowed()); } @Test void testBorrowTwiceThrows() { Book book = new Book("1", "Java编程思想", "Bruce Eckel", 99.0); book.borrow(); assertThrows(IllegalStateException.class, book::borrow); } }单元测试的本质是“把对程序的信任从靠感觉转为靠验证”。每当你给自己写的类加一个新方法,顺手补一个测试用例,长期积累下来的信心是惊人的。我见过很多转行的朋友,代码能力不差,但写的代码不敢重构、不敢改,一改就怕破坏功能。有单元测试兜底,重构的勇气和效率完全不一样。
5. 学习中常见问题与排错实录
5.1 新手最容易踩的坑清单
我整理了一份高频问题速查表,基本覆盖了我带过的人出过的洋相:
| 现象 | 根因 | 正确做法 |
|---|---|---|
比较两个 String 内容相等却用== | ==比较的是引用地址,不是内容 | 用str1.equals(str2)或Objects.equals |
声明了数组变量,访问arr[0]时报空指针 | 数组只声明未初始化 | new int[5]后再赋值访问 |
遍历 List 时删除元素报ConcurrentModificationException | 使用集合自身的方法删除了元素,修改计数与迭代器预期不一致 | 用迭代器的remove()或removeIf() |
自定义对象放进HashMap后 get 返回 null | 没有重写equals()和hashCode() | 依据业务主键重写这两个方法 |
浮点数比较0.1 + 0.2 == 0.3为 false | 二进制浮点表示存在误差 | 使用BigDecimal或比较差值绝对值 |
| catch 块里捕获异常后什么都不做 | 吞异常,掩盖问题 | 至少打印日志,最好将业务可恢复逻辑处理掉 |
| finally 块中写了 return,覆盖 try 中的返回值 | finally 在 return 前执行,最后返回的是 finally 里的值 | 不要在 finally 里 return |
| 访问数组或 List 时越界 | 下标从 0 开始,结束位置是 length-1 | 遍历时注意边界,优先用增强 for 或者流 |
这张表里的问题,我几乎每带一批新人都会见一次。建议直接把这张表存在笔记里,写代码时遇到诡异的现象先对一遍。
5.2 编译错误与运行错误的排查思路
把错误分成两大类来定位,效率会高很多:
第一类是编译错误。IDEA 里通常直接红波浪线,错误信息会精确到“哪个文件第几行,是什么问题”。这类错误最好解决,因为编译器的提示非常明确。常见的有:忘记写分号、括号不匹配、方法签名写错、访问权限不足(比如访问了 private 成员)、类型不兼容。唯一的建议是读完整错误信息,不要只看前几个单词。cannot find symbol这种报错,下面会附带着"fmtPl"或"变量名",一眼就能看出是不是拼写错误。
第二类是运行错误。程序能编译但一跑就崩,常见这几类:
NullPointerException:某个引用是 null,你还调了它的方法。排错时先看异常堆栈的第一行,它会告诉你“哪个类的哪一行”抛出来的。去那一行检查什么引用可能为 nullArrayIndexOutOfBoundsException:下标越界ClassCastException:把对象强转成不兼容的类型。比如把Object转成String,实际运行时是IntegerNumberFormatException:Integer.parseInt("abc")这种IllegalArgumentException:方法参数非法,通常是自己的防御检查生效了
排查运行错误不能靠猜。我教给新人的标准流程是:先看异常堆栈最顶部的三行,找到具体行号;在那一行加断点,用 Debug 模式看变量当前值;确认哪一个值不符合预期;从数据源头开始往前追。大多数空指针问题,本质是一个引用链上的某个中间环节返回了 null,往下追一定能找到根因。
5.3 学习节奏与资源推荐
我踩过的最大的坑是“收藏了一堆教程而不动手”。收藏的瞬间会有一种“我学会了”的错觉,实际上代码能力和收藏数量成反比。
学习节奏上,我建议按下面这个节奏安排:
- 第 1~2 周:环境搭建、基本语法、流程控制、数组、方法调用。每天写至少三个小练习
- 第 3~4 周:面向对象核心,类和对象、构造器、继承、接口、多态。做一个小系统(比如上面的图书管理)
- 第 5~6 周:集合、异常、IO、泛型。综合使用这些 API 改造前面的小系统
- 第 7~8 周:多线程基础、网络编程基础,配合 JUnit 测试前面写的代码
在学习资源的选取上,官方文档Oracle Java Tutorials是最权威的参考,适合当你需要某个类的 API 细节时查。新手阶段可以配合一本系统的书进行学习,比如《Java 核心技术卷Ⅰ》或《Head First Java》。前者更系统,后者更轻松,挑一本完整读下来比翻十本强得多。
心得:一定要写博客或者用笔记软件把学到的知识点用自己的话整理一遍。我自己带过的学得最好的那些人,不是看教程多的,而是天天把自己的理解写下来的。输出是最好的输入,这句话放在 Java SE 学习上一点不假。
结语:说点关于坚持的题外话
从我个人的实际体会来说,Java SE 学习最难熬的不是某一个知识点,而是中间那段“好像会了、又好像什么都不会”的时期。语法刚上手时觉得挺简单,学到集合和异常开始有点懵,再往后发现每个知识点单独看都能看懂,但合在一起写代码就是不知道从哪下手。这是再正常不过的阶段,说明你正在从“记忆知识”过渡到“组织知识”,跨过这一关靠的只有一个字——写。
当初带我入行的师傅说过一句我到现在都很认同的话:不要追求把所有内容都学完再开始写项目,而是在写项目的过程中不断发现自己的盲区,再回头补知识点。Java SE 学习没有终点线,它是一条越走越宽的路径。如果你现在正卡在某个报错里怀疑自己,那就把报错信息完整贴出来,把代码拆成最小可复现的形式,一步步追下去。
最后再分享一个小技巧:准备一个记“报错日记”的文档。每次碰到一个困扰超过二十分钟的问题,把你怎么排查的、最终原因是什么、怎么解决的三行写下来。半年后翻一翻,你会发现在同一个坑里跌进去的次数少了很多。这比收集任何学习资料都有用。