ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Java基础全覆盖:语法、集合、JVM与多线程面试实战指南

Java基础全覆盖:语法、集合、JVM与多线程面试实战指南 提到Java基础很多人第一反应是“语法简单、背一背就行”但真到了面试和写项目的时候才发现自己连环境变量为什么要配都没搞明白。这篇文章就是写给准备系统学Java、准备校招社招、或者工作中需要补基础的人。我把这些年从零带新人、自己面试和被面试的经验都揉进来不仅讲语法更讲“为什么”尽量让每个知识点都能落到实际开发和面试里。1. 从零开始先搞懂Java到底是什么1.1 为什么是Java而不是直接上框架新同学最喜欢问的一句话是现在都SpringBoot了我直接学SpringCloud不行吗我的回答一向是不行。框架是Java生态的一部分但框架解决的是“怎么组织代码”Java解决的是“计算机怎么执行你的指令”。你连JVM内存区域都说不清楚排查线上OOM就只能靠重启连集合扩容机制都不懂写出来的接口可能数据量一上来就卡死。Java能活这么多年核心优势是三点第一跨平台靠JVM屏蔽操作系统差异第二生态庞大从后端到大数据到安卓到处都是Java的痕迹第三静态强类型编译期就能发现一大批低级错误。这些优势都是建立在扎实的语言基础之上的。基础这层不打牢后面学多线程、JVM调优、分布式全都是空中楼阁。1.2 环境搭建JDK安装和环境变量最好不要跳过去很多教程让你直接下载JDK然后一路下一步连环境变量都不解释。结果到IDEA里能跑命令行一敲java -version就报错。原因很简单操作系统不知道去哪里找java.exe。安装JDK本身不复杂关键在于理解三个环境变量JAVA_HOMEJDK的安装根目录很多框架和工具比如Maven、Tomcat、IDEA都靠它定位JDK。PATH把%JAVA_HOME%\bin追加进去这样操作系统能在任意目录下找到java、javac这些命令。CLASSPATH老版本需要手动配置JDK 5以后其实可以不管但面试偶尔会问。它的作用是告诉JVM去哪里找类文件设置为.表示当前目录。配置完成后打开新终端输入java -version能看到类似“openjdk version 17.0.8”的输出就说明成功了。这里有个坑Windows配置完环境变量必须重新打开命令行窗口才能生效不是配置完立刻就能用。顺带说一个高频报错错误: 源发行版 17 需要目标发行版 17。这个问题的本质是编译版本和运行版本不一致。通常发生在你用JDK 17写的代码但IDEA里Project Structure的SDK设成了JDK 8或者Maven的maven.compiler.source/target没配对。解决办法是统一三处File - Project Structure - Project SDK、Settings - Build Tools - Maven - Runner - JRE、pom.xml里的java.version三处都改成同一个版本。2. 语法地基面向对象和数据类型2.1 基本类型和引用类型内存里的差别很大Java有8种基本类型byte、short、int、long、float、double、char、boolean。它们直接存值存在栈上用完就弹栈效率高。而String、数组、对象这些引用类型变量名存的是地址真正的内容在堆里。很多新手搞不清“基本类型变量怎么传、对象怎么传”面试题里经典的“值传递还是引用传递”就是从这来的。一定要记住Java只有值传递。传基本类型传的是值的副本方法里改了不影响外面传对象传的是引用地址的副本方法里通过这个地址改对象内容外面会看到变化。但如果方法里把这个引用重新指向一个新对象外面的引用不会变。这题在面试里至少有一半人答错。举例说明public class PassTest { public static void main(String[] args) { StringBuilder sb new StringBuilder(hello); change(sb); System.out.println(sb.toString()); // hello world } static void change(StringBuilder s) { s.append( world); s new StringBuilder(modified); } }输出是hello world因为s new StringBuilder(...)只改了方法内复制出来的引用原来的sb还是指向原来的对象。2.2 面向对象三件套封装、继承、多态面向对象是Java面试八股文里绕不开的题目。封装的核心就是“隐藏内部细节对外暴露受控的方法”比如类的字段设成private通过getter/setter访问。继承解决的是“类与类之间的共性抽取”用extends实现。多态则要满足三个条件有继承、有重写、父类引用指向子类对象。多态最典型的例子Animal a new Dog(); a.speak(); // 执行的是Dog重写后的speak很多人不理解为什么非要这样写。实际开发里多态的意义在于“面向接口编程”你可以写一个方法接收Animal然后传入任意子类方法不用改。比如支付场景里定义Payment接口AliPay和WeChatPay各实现一遍后续加新支付方式核心业务代码不用动。还有个高频考点是static方法能不能重写。答案是不能。方法重写是运行时动态绑定的而static方法属于类编译期就绑定好了。如果你在子类写一个和父类同名的静态方法那叫“隐藏”不是重写。调用时看引用类型而不是对象类型。2.3 字符串、equals和hashCode的坑Java开发中String是最常用的类但也是坑最多的。先记住字符串常量池。用String s1 abc这种方式创建JVM会先去常量池找找到就复用用new String(abc)则是强制在堆里创建一个新对象。String a abc; String b abc; String c new String(abc); System.out.println(a b); // true同一个常量池对象 System.out.println(a c); // false堆上新建对象 System.out.println(a.equals(c)); // true比较内容所以字符串比较必须用equals判空用isEmpty()或isBlank()之前先判断是否为null否则空指针。equals和hashCode的约定也是面试必问两个对象用equals比较相等它们的hashCode必须相等。反过来说hashCode相等equals不一定相等。为什么因为HashMap这类集合先用hashCode定位桶再用equals找具体元素。如果你重写了equals不重写hashCode同一个对象放进HashSet可能被当成两个元素这就是线上诡异Bug的来源。金额计算同样不能忽视。用double直接做加减乘除0.10.2会出现一长串小数因为二进制无法精确表示部分十进制小数。正确做法是使用BigDecimal构造时用new BigDecimal(0.1)不要用new BigDecimal(0.1)后者其实也是个不精确的近似值。3. 核心API与进阶概念3.1 集合框架面试重灾区也是开发高频区Java集合框架的核心接口是Collection和Map。List、Set、Queue都继承了CollectionMap是独立的键值对体系。面试必考的包括ArrayListvsLinkedListArrayList底层是数组查询快、增删慢尾插很快LinkedList底层是双向链表头部插入删除快但随机访问要遍历反而慢。HashMap的原理数组链表红黑树。默认容量16负载因子0.75扩容阈值12。当链表长度大于等于8且数组长度大于等于64时链表转红黑树。put流程是先算hash(key)定位桶如果桶空直接放否则遍历链表找相同key找到就替换值找不到就尾插最后判断是否超过阈值超过就扩容。JDK 8以后改成尾插法就是为了避免并发扩容时链表成环。ConcurrentHashMapJDK 8以后用CASsynchronized锁桶头节点把锁粒度从整张表降到单个桶并发度大幅提升。HashSet底层其实就是HashMapvalue统一用一个固定的Object占位。开发中选集合的原则很简单单列数据用ArrayList需要去重用HashSet需要键值对应用HashMap需要线程安全再用CopyOnWriteArrayList或ConcurrentHashMap。别一上来就上Vector和Hashtable这两个虽然线程安全但性能差属于被淘汰的类。这里补充一个容易被问到的点为什么重写equals必须重写hashCode。还是用HashMap的get流程来说它先算key的hash找到桶再用equals比较如果两个对象内容相同但hashCode不同它们就落在不同桶里get的时候根本找不到逻辑就错了。3.2 异常体系别用try-catch兜底所有问题Java的异常分为受检异常checked和运行时异常unchecked。Exception类下除了RuntimeException之外的都是受检异常比如IOException、SQLException编译器强制你处理。RuntimeException及其子类如NullPointerException、ArrayIndexOutOfBoundsException是运行时异常编译器不强制但程序运行时就可能炸。正确的异常处理姿势有三个原则能精确捕获就精确捕获不要只catch一个Exception不然日志里看不到真实问题。使用finally或try-with-resources释放资源。JDK 7以后推荐try-with-resourcesAutoCloseable的类可以自动关闭代码少且不会漏关。记录日志时用log.error(操作失败userId{}, userId, e)一定要把异常对象e传进去否则打不出堆栈。不要在循环里catch异常。如果循环体里的代码会抛异常尽量在循环内处理完继续或者收集异常结果到列表里统一处理。否则一个数据出错整个批量任务就中断了。3.3 泛型、枚举和注解写优雅代码的基础泛型没有很多人想的那么神秘它本质上是“类型参数化”。写成ListString编译器在编译期就帮你限定类型不用像以前那样从List里取出来还要手动强转。通配符? extends T和? super T是面试点extends规定了上界只能读super规定了下界只能写。这个规则一句话概括就是PECS生产者用extends消费者用super。枚举用得好代码能清爽很多。Java的enum不只是常量列表它可以带字段、带方法、带构造器。比如状态机里定义订单状态public enum OrderStatus { CREATED(1, 已创建), PAID(2, 已支付), SHIPPED(3, 已发货), COMPLETED(4, 已完成); private final int code; private final String desc; OrderStatus(int code, String desc) { this.code code; this.desc desc; } public int getCode() { return code; } public String getDesc() { return desc; } }需要用状态码反查枚举时可以加一个static方法遍历values()注意不要每次调用都遍历整个枚举可以初始化一个Map缓存性能更好。用枚举替代魔法数字最大的好处是代码可读性强别人一看就知道状态含义switch也不会写错case。注解则是一种元数据机制本身不改变程序行为但框架会读取它做各种处理。最常见的Override是给编译器看的Transactional是给Spring做事务增强的。自定义注解需要配合反射或AOP才有意义这一步学Spring的时候会理解得更深。4. JVM、并发与数据一致性4.1 JVM内存分区排查OOM的第一步JVM的内存区域是面试必问也是排查线上问题的前提。按规范分为程序计数器当前线程执行的字节码行号线程私有。Java虚拟机栈每个方法对应一个栈帧局部变量、操作数栈都在这里线程私有。栈深度不够就抛StackOverflowError。本地方法栈为native方法服务。堆几乎所有对象实例都在这里线程共享是GC的主要战场。方法区存放类信息、常量、静态变量、JIT编译后的代码。JDK 8以后方法区改名为元空间使用本地内存。高频面试题是“对象在堆里的分配过程”。简单说对象优先在Eden区分配Eden满了触发Minor GC存活的对象进入Survivor的from区经过多次GC还活着会晋升到老年代。大对象直接进老年代为了避免Eden和Survivor之间复制大对象的开销。老年代满了触发Full GC如果Full GC之后还是放不下就抛OutOfMemoryError。再补充一个实战思路线上应用频繁Full GC第一步不是改代码而是先dump堆快照。用jmap -dump:formatb,fileheap.hprof pid导出再用MAT或VisualVM分析大对象都在哪。绝大多数情况是某张表数据被一次性全查出来或者缓存没设过期时间导致内存被占满。4.2 多线程从线程安全到锁的升级多线程是Java进阶的分水岭。实现多线程的三种方式继承Thread、实现Runnable、实现Callable。实际开发中基本只用后两种因为Java是单继承继承Thread就把唯一一次继承机会浪费了。Callable和Runnable的区别在于Callable能返回结果、能抛异常。线程安全的核心是“可见性、原子性、有序性”。volatile解决可见性和一定程度上禁止指令重排但不保证原子性。synchronized保证原子性和可见性JDK 6以后做了锁升级偏向锁-轻量级锁-重量级锁。ReentrantLock功能更丰富支持可中断、可超时、公平锁但需要手动加锁解锁通常配try-finally使用。写并发代码有个经验优先考虑java.util.concurrent包下的工具类而不是自己造轮子。比如用CountDownLatch等待多个线程执行完用Semaphore做限流用BlockingQueue做生产者消费者模型。自己用synchronizedwait/notify实现生产消费很容易写错notify和wait的顺序导致死锁或假死。线程池也是一个必考点。Executors.newFixedThreadPool用起来方便但阿里规范明确不推荐因为LinkedBlockingQueue默认容量是Integer.MAX_VALUE任务积压太多会占满内存。推荐直接用ThreadPoolExecutor构造函数显式指定核心线程数、最大线程数、空闲时间、队列和拒绝策略。ThreadPoolExecutor pool new ThreadPoolExecutor( 4, 8, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue(1000), new ThreadPoolExecutor.CallerRunsPolicy() );CPU密集型任务核心线程数设为CPU核数1IO密集型任务可以设成CPU核数*2具体要根据压测结果调整。4.3 数据一致性事务、锁和分布式单机环境下保证数据一致性靠的是数据库事务的ACID特性。Spring里最常用的是Transactional但用不好会踩很多坑。第一个坑Transactional默认只在RuntimeException回滚try-catch把异常吃了就永远不会回滚。正确的做法是让异常继续往外抛或者在catch里手动TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()。第二个坑Transactional失效。常见原因是方法被this调用而不是通过Spring代理对象调用。因为Transactional是基于AOP动态代理实现的this调用不会走代理。解决方法有两种注入自身代理对象或者把需要事务的方法拆分到另一个Service里。第三个坑事务范围过大。不要在事务里做RPC调用、文件上传、发消息等耗时操作长事务会长时间占用数据库连接和锁拖垮并发性能。事务里只放数据库操作其他事情放到事务外。到了分布式场景单机事务就不够用了。分布式事务的常见方案有XA两阶段提交、TCC补偿、MQ最终一致性、Seata的AT模式。面试问“怎么保证数据一致性”先分清场景强一致性需求用XA或TCC最终一致性需求用消息队列加本地消息表。不要一上来就说分布式事务中间件先解释清楚为什么不直接用数据库事务。5. 框架、面试与实操Java后端完整成长路线5.1 从零到能干活一条少走弯路的路线图如果让人推荐Java学习路线我倾向于分四步走第一阶段是语言基础也就是本文前面所有内容大概需要一个月。包括基本语法、面向对象、集合、异常、IO、反射。第二阶段是数据库和Web基础。先学MySQL的SQL编写、索引原理、事务隔离级别再学HTTP协议、Servlet和JSP。JSP现在项目里用得少了但属于Java的Web基础理解一次有助于后续学SpringMVC。第三阶段是主流框架。Spring、SpringMVC、MyBatis或MyBatis-Plus然后是SpringBoot。学框架不要只看用法要理解IOC容器和AOP思想。SpringBoot把大量配置自动化了新人用起来舒服但出了问题反而不知道从哪排查所以基础的XML配置方式也要能看懂。第四阶段是中间件和微服务。Redis、RabbitMQ或Kafka、Elasticsearch、SpringCloud Alibaba。学到这阶段就可以开始模仿一个电商项目或管理后台把框架和中间件结合起来用。5.2 Java面试八股文与其背答案不如建立知识脉络Java面试题被戏称为“八股文”但真正有效的复习方式不是死记硬背而是把知识点串成脉络。比如问HashMap正常的回答应该从数据结构开始数组加链表再讲hash函数和扩容再讲红黑树化的条件最后讲线程安全替代方案ConcurrentHashMap。这一条线讲下来至少能聊五分钟还能引出JVM、并发、集合等多个话题。可以做一个知识卡片式的自查表主题必会核心点集合框架HashMap底层结构、扩容机制、HashSet原理、ArrayList扩容JVM内存分区、垃圾回收算法、常见收集器、类加载过程并发synchronized与ReentrantLock区别、volatile语义、线程池参数SpringIOC、AOP、Bean生命周期、事务传播机制MySQL索引结构、最左前缀原则、事务隔离级别、MVCCRedis持久化机制、缓存穿透击穿雪崩、分布式锁网络TCP三次握手四次挥手、HTTP与HTTPS、Cookie与Session设计模式单例、工厂、策略、模板方法、代理准备面试的时候挑至少两个知识点能往深处展开比如“你项目里遇到过哪些并发问题”“Redis缓存一致性是怎么做的”。面试官想考察的不是你会背多少定义而是能不能把知识迁移到实际场景中。5.3 构建工具和IDE基础之外的工程化能力基础语法之外工程化工具也算Java基建设施。Maven和Gradle是主流构建工具Maven的核心概念是坐标、依赖、仓库、生命周期。本地仓库默认在.m2目录下依赖下载慢可以配置阿里云镜像。Gradle更灵活构建速度快但学习成本略高。IDEA是当前最主流的IDE。一些提升效率的插件如Lombok、MyBatisX、Alibaba Java Coding Guidelines值得装。不过Lombok虽然简化了代码但也隐藏了equals/hashCode、构造器等细节面试的时候建议能说出Lombok的Data在编译期做了什么事。工程化能力还包括学会用Git管理代码学会用Maven打包学会看日志排查问题学会用jstack看线程状态、用jstat看GC情况。这些不是面试必考但入职后是日常必备。5.4 设计模式和代码质量基础能力的试金石设计模式是Java进阶绕不开的一块面试常考的有单例模式、工厂模式、策略模式、模板方法模式和动态代理。单例模式的推荐写法是枚举或静态内部类双重检查锁要加上volatile防止指令重排。策略模式则适合处理多种if-else分支比如根据支付方式选择不同的支付实现。public interface PayStrategy { void pay(BigDecimal amount); } Component(alipay) public class AliPayStrategy implements PayStrategy { Override public void pay(BigDecimal amount) { // 支付宝支付 } } Component(wechatpay) public class WeChatPayStrategy implements PayStrategy { Override public void pay(BigDecimal amount) { // 微信支付 } }如果配合Spring可以注入一个MapString, PayStrategykey就是Bean名称这样将来新增支付方式只需要新增实现类不用改核心逻辑。这就是策略模式的实战价值。代码质量方面推荐遵循阿里巴巴开发手册类名大驼峰、方法名小驼峰、常量全大写、equals常量放前面避免空指针、集合转数组用list.toArray(new String[0])。规范的代码不是给机器看的是给三个月后的自己看的。6. 避坑总结别在这些常见问题上翻车6.1 环境层面的高频故障JDK版本选择是个老生常谈但总有人踩的坑。现在很多公司用JDK 8新项目有上JDK 17的但不同版本的行为有差异。比如JDK 8的String内部是char[]JDK 9以后改成了byte[]并引入COMPACT_STRINGS。写代码时尽量别依赖具体JDK版本特性保证代码可移植性。还有一个隐藏问题Windows系统同时装了JDK 8和JDK 17的话JAVA_HOME改了但PATH里如果还留着旧版本的bin路径命令行执行的还是旧版本。排查时用where java看实际路径比反复改环境变量高效得多。6.2 代码层面的经典Bug字符串判空、金额计算、集合删除这三个是新手最容易写错的点。集合在遍历时删除元素直接list.remove(i)会抛ConcurrentModificationException。正确的是用Iterator的remove或者用JDK 8的removeIf。日期时间处理也值得单独说。JDK 8之前用SimpleDateFormat它是线程不安全的多线程环境下同一个实例执行format会产生脏数据。新代码应该用LocalDate、LocalDateTime和DateTimeFormatter后者是线程安全的。如果和老代码交互可以用Date.toInstant()转成新API。对象深拷贝的问题在面试里也经常出现。Object.clone()是浅拷贝BeanUtils.copyProperties也是浅拷贝如果对象里有List或者可变对象浅拷贝会共享内存。要实现深拷贝可以序列化后反序列化或者手动为每个字段创建新对象。实际项目中像订单这种业务对象我一般直接新建对象再赋值最简单也最可控。6.3 学习方法和心态调整最后聊点学习上的经验。学Java基础最忌讳的是“只看不练”。视频课或者书看一遍觉得都会了打开IDEA写代码才发现连import都不知道在哪。建议每学一个知识点就写一个十几行的小Demo验证。比如学完HashMap自己写一个统计单词频率的程序顺便练练文件读写学完多线程写一个简单的生产者消费者Demo把BlockingQueue用起来。另一个亲测有效的方法是费曼学习法。每个技术点用自己的话讲给同事或写进个人博客。你能把一个概念讲得让小白听懂说明你是真懂了。这也是我这些年坚持写文章、整理笔记的原因输出倒逼输入效率远高于单纯看资料。根据我个人的实际体会Java基础的学习不需要一次追求完美第一次学能写CRUD就够了第二次学能理解Spring底层第三次学能定位复杂问题。每轮学习都带着新的实践问题回头翻基础每次都有新收获。这就是Java这个语言的特点同时也是它真正值钱的地方——基础知识学得越扎实后面的并发、分布式、调优之路才能走得越稳。
返回列表