
最近整理Java学习笔记的时候我翻出了很多刚入门时踩过的坑也看了不少网上流传的《Java自学路线图超全超详细》。说实话那份路线图内容很长知识点列得很全但真要照着学很多人坚持不到两周就放弃了。问题不在于知识不够多而在于路线太“散”不知道哪些该先学、哪些可以缓一缓。所以这份笔记我不打算做成百科式清单而是以一个从零开始自学、后来做过几个真实项目的一线Java工程师视角把基础语法、集合容器、并发、JVM、生态工具常见场景都串一遍。适合准备入门的同学、正在复习Java基础准备面试的人也适合工作中想快速查漏补缺的同事。1. 学习路线怎么定先看清Java的森林1.1 从零基础到能干活路线其实比你想的短网上经常看到有人问“Java自学要多久才能找到工作”我的回答是如果你只是想写出能跑的代码两周足够如果你想进公司干活建议按三个阶段走。第一阶段是Java SE基础包括数据类型、运算符、流程控制、数组、面向对象、集合、IO、异常、泛型、反射、注解。这个阶段的核心是建立“一切皆是对象”的思维。第二阶段是数据库和Web基础SQL、JDBC、MyBatis、Spring Boot、HTTP协议。这时候能做简单的CRUD系统。第三阶段是项目实战比如学生管理系统、订单系统、博客系统重点不是用了什么新技术而是把事务、权限、日志、异常处理这些东西真正落到代码里。我见过很多自学资料把“学习路线图”写成了一个大而全的目录从JDK安装到微服务治理都塞进去。这种“从入门到放弃”的路线说白了就是焦虑贩卖。真正有效的路线应该是短而快的闭环先写代码再补理论最后做项目。每学完一个模块立刻用代码验证而不是一直看视频看文档。1.2 环境准备避坑JDK选择与WIN11环境变量环境配置是劝退新手的第一座山。很多问题不是不会装而是装完发现命令不对、版本冲突。我一般建议用Eclipse Temurin原AdoptOpenJDK或者Oracle JDK。版本选择上企业里JDK 8仍然很多但JDK 11和17的用户也在快速增长学习阶段装JDK 8或11都行不要直接上最新的JDK 21。原因很简单很多老项目还跑在8上教材和面试题也大多基于8。WIN11下配置Java环境大致是下载JDK安装包、设置JAVA_HOME、把%JAVA_HOME%\bin加到Path。装完之后在命令行执行java -version验证。这里有个很隐蔽的坑如果电脑里装过其他JDK比如ClickOnce或系统自带的JRE命令行执行时可能指向了旧版本。先用where java查一下实际路径再用java -version确认。IDE里也要单独配置JDK比如IntelliJ IDEA的Project Structure里选一下SDK路径不然IDEA和命令行用的可能不是同一个JDK。2. 基础语法与常见坑写得对更要写得稳2.1 数据类型、数组越界与编码Java有8种基本类型byte、short、int、long、float、double、char、boolean。对应包装类型是Byte、Short、Integer、Long、Float、Double、Character、Boolean。这里最容易考的问题就是Integer缓存。默认Integer缓存范围是-128到127所以用Integer a 100; Integer b 100;比较a b会返回true但如果超过127会返回false。这个坑刷题的时候很容易碰见。数组越界是新手高频报错就是ArrayIndexOutOfBoundsException。常见原因是循环边界写错比如习惯性写了i length数组下标应该是i length。还有个隐藏问题数组初始化后默认值是0或null如果你遍历时没判空拿到null一调用方法就会NPE。真正排查越界的时候别只盯着异常那行要往上找数组在哪创建、长度是多少、循环用什么变量控制。编码问题也值得提。Windows环境下经常出现中文乱码根源是源文件编码、编译编码、运行控制台编码不一致。现在IDE默认UTF-8还好但老项目或某些命令行工具还是用GBK。建议在Maven或Gradle里显式设置-Dfile.encodingUTF-8并在读取文件时指定Charset.forName(UTF-8)。代码里不要依赖操作系统默认编码。2.2 排序与算法从冒泡排序看懂工程选择每个学Java的人都写过冒泡排序我也写过。冒泡排序的核心就是相邻元素两两比较大的往后移一趟下来最大的元素就到位了。时间复杂度O(n²)稳定。实现起来很简单public static void bubbleSort(int[] arr) { if (arr null || arr.length 2) { return; } 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]) { int tmp arr[j]; arr[j] arr[j 1]; arr[j 1] tmp; swapped true; } } if (!swapped) { break; } } }面试时写冒泡排序没问题但在工程里千万别自己造轮子。Java内置的Arrays.sort()对基本类型用的是双轴快速排序对对象用的是TimSort性能比手写冒泡好得多。对象排序时可以写Comparator.comparing(User::getAge)或Comparator.comparingInt(...)如果要倒序就再reversed()。很多新手容易犯的错误是让实体类实现Comparable却忘记重写equals和hashCode导致排序结果正常但放到Set或Map里行为很奇怪。2.3 Lambda、内部类与深拷贝Lambda表达式是Java 8引入的它本质上是函数式接口的简洁写法。函数式接口就是只有一个抽象方法的接口比如Runnable、Consumer、Predicate、Function。使用起来很简单ListString names Arrays.asList(小明, 小红, 小刚); names.forEach(name - System.out.println(name));这里有个硬性规则如果Lambda表达式引用了局部变量这个变量必须是final或者effectively final赋值后再没有改过。否则编译都过不了。原因在于Lambda底层是把局部变量复制一份到匿名类中如果变量还能被重新赋值就会造成数据不一致。内部类的话题也常和Lambda混在一起。Java内部类有静态内部类、成员内部类、局部内部类、匿名内部类。匿名内部类是最常见的比如new ComparatorInteger() { ... }。Lambda和匿名内部类有个细微区别匿名内部类里的this指向匿名类对象而Lambda里的this指向外部类对象。调试事件监听器时如果发现this不是你预期的对象多半就是用了Lambda。再提一下深拷贝。Object.clone()默认是浅拷贝只复制基本类型和对引用对象的指针。要实现深拷贝简单粗暴的办法是序列化前提是对象所有字段都可序列化ByteArrayOutputStream bos new ByteArrayOutputStream(); ObjectOutputStream oos new ObjectOutputStream(bos); oos.writeObject(source); ObjectInputStream ois new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray())); Object target ois.readObject();但这种写法性能差而且有些字段标记了transient就不会被复制。如果只是复制一个业务对象我建议手动逐个字段拷贝最可控也最不会踩坑。3. 面向对象与设计模式用对象思维写代码3.1 封装、继承、多态不只是背概念面向对象是Java的灵魂但很多人的理解停留在“类是模板对象是实例”这一层。封装的核心是隐藏实现细节对外暴露稳定的接口。继承描述的是“is-a”关系多态则是让同一个方法在不同子类上有不同表现。写代码的时候访问修饰符要养成习惯私有字段用private对外方法用public受保护的用protected。不要为了图方便把所有字段都设为public否则封装就没有意义了。方法重写要注意返回值、修饰符、抛异常的限制都比父类更宽松。重载是同一个类里方法名相同、参数列表不同和多态不是一个概念面试时经常被混在一起问。我踩过最深的坑是继承层次太深。比如原来有个BaseEntity然后子类又派生子类再叠加接口默认方法最后找个字段都不知道是哪个层级定义的。经验是能用接口描述能力就用接口能用组合就用组合。继承本身不是坏事但设计时一定要清楚“为什么继承”。3.2 设计模式在业务中的落地设计模式是提升代码可读性和扩展性的工具。单例模式是面试高频题特别是双重检查锁写法public class Singleton { private static volatile Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance null) { synchronized (Singleton.class) { if (instance null) { instance new Singleton(); } } } return instance; } }volatile在这里必须加因为指令重排可能让其他线程拿到一个未完全初始化的对象。这个细节面试官很爱问。策略模式在业务系统里特别实用。比如支付场景有微信、支付宝、银行卡可以定义一个PayStrategy接口每种支付方式实现一个子类然后用Map存放实现类根据参数路由。这样新增一种支付方式时不用改动原有逻辑只加一个实现类就行。模板方法模式适合那些流程固定、细节可变的场景比如数据导入先校验文件再解析数据最后入库校验规则和解析规则由子类实现。说到行级权限这是很多系统里隐藏的需求。比如一个后台管理系统里普通用户只能看到自己创建的数据部门主管能看到本部门的数据管理员才能看全部。实现行级权限最简单的方案是在SQL里拼user_id ?或者dept_id IN (...)而不是查全表再在内存里过滤。后者不仅慢还容易造成越权。做得好的框架会在MyBatis层面通过拦截器统一拼权限条件代码里就不需要每个SQL都写一遍了。4. 集合与并发容器选择和线程安全4.1 集合容器底层选型Java集合是个大话题。我整理笔记时通常把常用容器分三类List、Map、Set。ArrayList底层是Object数组默认初始容量是10每次扩容为原来的1.5倍。如果提前知道数据量最好在构造时指定容量能减少扩容开销。LinkedList底层是双向链表插入删除快但随机访问慢实际项目里用的不多。HashMap底层是数组链表当链表长度超过8且数组长度超过64时链表转为红黑树负载因子默认0.75。为什么是0.75这是空间和时间的一个折中。线程不安全多线程环境下用ConcurrentHashMap。看源码的时候重点要看HashMap的hash()方法为什么要把高位异或下来以及putVal里什么时候扩容、什么时候树化。这些细节能帮你理解“哈希冲突”到底是怎么处理的。不少面试题会问HashMap和Hashtable的区别最简单的回答是Hashtable是同步的HashMap不是但真正重要的是HashMap允许key为nullHashtable不允许而且Hashtable的并发性能很差已经不建议用了。Set底层大多基于Map实现比如HashSet内部就是HashMap利用键的唯一性实现去重。如果要遍历Map最好用entrySet()而不是keySet()再查一遍否则多了一次get操作。4.2 AQS与并发工具看懂排队规则AQS全称是AbstractQueuedSynchronizer是JUC包下很多并发工具的基础。ReentrantLock、Semaphore、CountDownLatch都是基于它实现的。理解AQS的核心只需盯住两个东西一个volatile修饰的state状态变量一个双向CLH等待队列。当线程尝试获取锁时会调用tryAcquire尝试修改state。如果成功就拿到了锁如果失败就进入等待队列排队。后来有线程释放锁会唤醒队列头部的等待线程。ReentrantLock的可重入逻辑就是state累加同一个线程每加锁一次state加1每解锁一次state减1减到0才是真正释放。很多人背诵AQS源码但面试时讲不清楚“为什么需要队列”。打个比方锁就像卫生间只有一个坑位来的人发现坑位被占就在门口排队当前使用的人出来时确实进入卫生间而不是冲进去抢。AQS就是这套排队机制的Java实现。你要是能把这个过程说清楚比背十行源码都管用。4.3 数据一致性保障说到“Java怎么保证数据一致性”这个问题可以分很多层。单机多线程下靠的是JMMJava内存模型提供的可见性、原子性、有序性保证。synchronized既能保证原子性也能保证可见性volatile只能保证可见性和有序性不能保证原子性。所以用volatile修饰一个计数器多个线程同时count仍然会丢值。乐观锁和悲观锁也是高频考点。悲观锁就是先加锁再访问适合并发冲突高的场景乐观锁就是CAS比较并交换适合冲突不高的场景。CAS有ABA问题解决办法是加版本号或时间戳。在Java中AtomicStampedReference可以解决ABA问题。单机锁只能解决单进程内的线程安全问题。如果是微服务架构需要分布式锁。最简单的实现是Redis的SETNX加过期时间但要注意防止死锁和误删锁的问题。更稳妥的做法是用Redisson的RLock它会自动续期避免业务没跑完锁就过期了。我踩过的坑是业务逻辑里忘了释放锁结果别的线程一直拿不到锁后来锁定代码必须写在finally块里或者用tryLock等方法。数据库层面还有个常见手段是乐观锁在表里加version字段UPDATE ... SET version version 1 WHERE id ? AND version ?更新的同时检查版本号。这样能防止并发更新把数据覆盖掉。事务里还要注意事务传播行为尤其是Transactional失效的场景同类内部方法调用、非public方法、异常被捕获等都会让事务不生效。5. JVM知识Java是静态链接的吗5.1 类加载与动态链接网上有个说法“Java是静态链接的”我每次看到都觉得要澄清一下。C/C的静态编译是在编译期把被调用的库函数直接拼进可执行文件我把这种情况叫静态链接。Java则完全不是这样。Java编译出来的是.class字节码里面存的符号引用而不是实际的内存地址。程序运行时类加载器才把需要的类加载进来在解析阶段把符号引用替换为直接引用这个过程是动态的。所以同一个class文件依赖的类库完全可以在运行时更换只要classpath里有就能加载到。这也是Java生态里依赖管理能这么灵活的基础。类加载器也是面试常客双亲委派模型要理解当类要被加载时先让父加载器尝试父加载器加载不到才轮到子加载器。这样可以保证核心类库不会被篡改。至于为什么会抛出ClassNotFoundException或NoClassDefFoundError大多数原因是classpath缺少依赖或者依赖冲突导致类加载不到对应版本。5.2 内存区域与垃圾回收JVM内存区域我在笔记里画过无数遍图堆、栈、方法区元空间、程序计数器、本地方法栈。堆是对象分配主战场又分新生代和老年代新生代又分伊甸园、From Survivor、To Survivor。绝大多数对象先在伊甸园创建Minor GC后存活对象被复制到Survivor年龄增长到一定阈值才进入老年代。垃圾回收算法常见的三种复制、标记-清除、标记-整理。复制算法适合年轻代因为存活对象少复制成本低老年代对象存活率高通常用标记-整理或标记-清除配合处理。现在的G1收集器把堆划分为多个Region能够做到可预测的停顿时间。调优方面新手不用追太多花哨参数重点理解-Xms和-Xmx。堆大小初始值和最大值如果一样可以减少运行时扩容带来的开销。线上遇到OOM先看是堆溢出还是元空间溢出再配合jmap和jstack分析。这里我有个习惯写项目前就在IDE里配置好-XX:HeapDumpOnOutOfMemoryError出了OOM还能留下dump文件排查。6. Java生态的实用场景6.1 用POI操作Word并生成图表有朋友问“Java POI Word能生成图表吗”答案是能但不像Excel图表那样开箱即用。Apache POI里操作Word的类主要是XWPFDocument支持docx格式。生成图表的API从POI 4.1左右开始提供可以创建XWPFChart并插入简单的柱状图、折线图等。下面是一个创建Word文档并加段落和表格的简单流程try (XWPFDocument document new XWPFDocument()) { XWPFParagraph paragraph document.createParagraph(); XWPFRun run paragraph.createRun(); run.setText(Java学习笔记); XWPFTable table document.createTable(3, 3); table.getRow(0).getCell(0).setText(知识点); table.getRow(0).getCell(1).setText(优先级); table.getRow(0).getCell(2).setText(掌握进度); try (FileOutputStream out new FileOutputStream(note.docx)) { document.write(out); } }如果要生成复杂图表比如带数据标签、图例、多系列折线图原生API会比较吃力。我遇到过的做法是事先在Word里做好模板再用POI替换数据。这种方式可控性强不用和底层OOXML的XML细节死磕。需要注意POI处理大文档时内存消耗较高如果并发导出建议用线程池控制并发数。6.2 网络编程与数据采集Java获取DNS相关信息很简单用InetAddress就行InetAddress[] addresses InetAddress.getAllByName(www.example.com); for (InetAddress address : addresses) { System.out.println(address.getHostAddress()); }但要注意JVM对DNS解析结果默认有缓存。InetAddress的缓存分为正向解析和反向解析缓存时间可以通过安全属性networkaddress.cache.ttl控制。如果程序里改了DNS记录线上还是解析到旧IP多半就是缓存没刷新。说到Java在网络编程和采集网关方面的应用我工作中也写过类似的东西用定时任务从设备接口轮询数据解析后批量写入数据库。这种采集网关要注意几个点第一是超时设置HTTP调用必须配连接超时和读超时第二是重试机制网络抖动是很常见的但重试要考虑幂等性防止重复写入第三是线程池隔离不能因为一个上游接口变慢拖垮整个采集进程。使用ExecutorService或者虚拟线程后可以用虚拟线程处理大量IO等待场景。Java的应用范围真的不只是互联网后端。很多学习管理系统LMS、物联网接入服务、工具类软件比如Minecraft的启动器PCL底层都是Java。学Java的时候没必要把自己束缚在“只有Web开发”这个框架里核心能力扎实了很多场景都能迁移。6.3 环境问题与依赖排查运行Java项目时最容易遇到的问题是Lombok报错提示you arent using a compiler supported by lombok。我第一次遇到时很懵明明代码没错。后来发现是JDK 16以上版本更新了编译接口旧版Lombok不兼容。解决办法就三条路升级Lombok到新版本把JDK降到8或11如果用的是IDEA检查一下Enable annotation processing有没有打开。Maven项目里还可以在maven-compiler-plugin的annotationProcessorPaths里显式声明Lombok版本避免和自有依赖冲突。另一个常见场景是Docker容器里提示找不到java。这个通常不是代码问题而是基础镜像里根本没装JDK。有些精简镜像只包含运行时库并不包含完整的java命令。解决方法是使用带JDK的基础镜像比如eclipse-temurin:17-jdk或者在Dockerfile里安装JDK。还要注意容器里环境变量是否继承PATH是否包含JDK的bin目录。排查时先在容器内执行which java、java -version就能定位到是镜像问题还是路径问题。编码问题也值得一提。不管是Maven项目还是Gradle项目都要确认编译编码是UTF-8。如果项目的pom.xml里没有设置project.build.sourceEncoding在Windows上编译读取中文资源文件时就很容易出现乱码。另一个容易忽略的点是IDE控制台输出乱码很多时候不是代码问题而是控制台编码和系统编码不一致。7. 学习资源与面试准备7.1 免费资源推荐Java入门的免费资源其实很多。我推荐路线是语言基础看廖雪峰Java教程和菜鸟教程这两个适合快速上手。想系统复习和应对面试可以看GitHub上的JavaGuide几乎把Java核心知识点都整理成了问答形式。B站上也有不少高质量全套视频但看的时候要挑播放量高、更新时间近的否则很容易学到过时写法。刷题网站方面LeetCode力扣是练算法的标配牛客网有大量Java面试题和公司真题。如果你喜欢刷简单题练手感可以在力扣按“数组”“字符串”“链表”标签刷每天两三题坚持一个月就有感觉。不要一味追求难题Java后端面试里算法题一般就是中等难度能写清楚思路比AC更重要。免费的入门网站还有一个容易被忽略的Oracle官方Java Tutorial。它是英文的但内容权威适合查缺补漏。7.2 八股文与面试题怎么看“Java八股文”这个说法很有意思很多人背得很熟但一深入问就露馅。我的建议是不要死背答案把每个题当成一个故事来讲。比如面试官问“HashMap底层实现”不要回答“数组链表红黑树”就完了而是讲清楚为什么用数组哈希冲突怎么办链表什么时候转红黑树为什么阈值是8这样才显得你真的理解。面试题可以按模块整理集合、并发、JVM、Spring、MySQL、Redis、消息队列。每个模块准备三到五个核心问题比如“线程池参数怎么设置”“Spring Bean生命周期”“MySQL索引为什么用B树”。准备的时候可以自己写一段demo验证再总结成笔记。等到面试时就能说得既熟练又不背书。刷题和面试虽然重要但最终还是要回到项目本身。面试官更想知道的是你在项目里怎么解决过问题而不是考察你记住了多少标准答案。所以我在笔记里特别强调每学一个新知识点一定要造一个小例子或者看看框架源码里的真实用法否则这个知识只是“知道”不会“会用”。结尾写笔记的意义在于复盘整理完这份Java学习笔记我最大的体会是知识不是记在本子上就属于你的。很多人收藏了无数资源却从不打开代码编辑器最后依然是“一看就会一写就废”。动手敲一遍、踩一次坑、看一遍报错栈比看十篇教程都管用。有件事我一直建议身边新人做每次遇到报错别急着复制粘贴到群里先自己看异常信息的前三行把准确错误信息、操作步骤、环境版本记录下来。时间长了你会发现自己排查问题的速度快得惊人。写笔记也是一样与其抄别人的总结不如记录自己踩坑后的思考那样的笔记才真正有用。