第一篇:Java基础——一切从“类“和“Map“开始

第一篇:Java基础——一切从“类“和“Map“开始

别再背定义,你只需要知道"这东西是干嘛用的"

写在前面

Java基础是面试的"入场券"。很多人一上来就背"封装继承多态"、“重载重写区别”、“String和StringBuilder的区别”……背得滚瓜烂熟,面试官一问"为什么这样设计",直接卡壳。

这篇文章的任务只有一个:让你用常识理解Java基础,而不是用记忆力。

一、先搞清楚JDK、JRE、JVM到底是什么关系

面试官可能不会直接问"JDK和JRE的区别",但这个概念贯穿整个Java学习。

大白话:

  • JDK= 开发工具包,给程序员用的。里面有编译器(javac)、调试工具、还有下面那个JRE。
  • JRE= 运行环境,给用户用的。里面有JVM + 运行Java程序需要的核心类库。
  • JVM= Java虚拟机,负责把字节码翻译成机器码执行。Java"一次编译到处运行"靠的就是它——不同操作系统装不同版本的JVM,但跑同一个.class文件。

一句话记住:写代码用JDK,跑代码靠JRE,真正干活的是JVM。

面试如果问到"Java为什么能跨平台",就答:“因为不同操作系统有不同版本的JVM,JVM屏蔽了底层差异。”

二、面向对象:说白了就三件事

面向对象不是什么玄学,它就是对现实世界的模拟。

封装:你家的钱不能让别人随便翻,得通过你提供的接口(比如"妈,给我点钱")来操作。类里面的private字段加public的getter/setter,就这意思。

继承:你继承了你爸的姓和房产,但你可以有自己的新东西。子类继承父类的属性和方法,还能扩展自己的。

多态:同一个方法,不同对象干不同的事。“动物叫”——狗汪汪,猫喵喵,羊咩咩。代码里就是Animal a = new Dog(); a.sound();,运行时才知道是哪个具体实现。

面试追问:多态的实现原理是什么?

答:父类引用指向子类对象,方法调用时看实际对象的类型。底层靠的是虚方法表(vtable)——每个类有个表记录实际应该调用哪个方法,运行时去查这个表。

三、重载 vs 重写,永远有人搞混

重载(Overload):同一个类里,方法名相同,参数列表不同(数量、类型、顺序)。跟返回值无关,跟访问修饰符也无关。

voideat(Stringfood){}voideat(Stringfood,intcount){}// 参数数量不同voideat(intcount,Stringfood){}// 参数顺序不同

重写(Override):子类重新实现父类的方法。方法签名必须完全一样(方法名+参数列表),返回值可以是子类类型(协变返回),访问权限不能比父类更严格。

// 父类protectedAnimalget(){returnnewAnimal();}// 子类publicDogget(){returnnewDog();}// 返回值是子类,权限放大了,OK

面试官真正想听的是:

“重载是编译时决定调用哪个方法(静态分派),重写是运行时决定调用哪个方法(动态分派)。” —— 这才是区分两者的本质。

四、抽象类 vs 接口:Java 8之后区别不大了,但核心区别还在

经典背法(也是对的)

  • 抽象类可以有构造方法、成员变量、普通方法;接口在Java 8之前只能有常量和抽象方法。
  • 类只能单继承,但可以多实现接口。

面试官真正想听的:

"为什么Java设计成单继承多实现?"

因为菱形问题。如果支持多继承,两个父类有同名方法,子类该用哪个?C++有这个问题,靠虚继承解决,但很复杂。Java干脆不让多继承,改用接口——接口没有状态(没有成员变量),只有方法声明,所以不存在冲突。

但注意:Java 8之后接口可以有default方法和static方法了,多实现时如果两个接口有同名的default方法,子类必须重写。本质上,接口越来越抽象类化,但核心区别依然在"能不能有状态"——抽象类可以有成员变量(状态),接口不行(只能有static final的常量)。

选型建议:要描述"是什么"用抽象类(比如Animal),要描述"能做什么"用接口(比如Runnable)。

五、== 和 equals():比的是什么?

==

  • 基本类型:比
  • 引用类型:比内存地址(是不是同一个对象)。

equals()

  • 默认(Object类里的)就是==,比地址。
  • 但很多类重写了它(比如String、Integer),比的是"内容"
Strings1=newString("abc");Strings2=newString("abc");s1==s2// false,两个不同对象s1.equals(s2)// true,内容一样

铁律:重写equals必须重写hashCode()。

为什么?HashMap、HashSet这些集合用hashCode判断对象放哪个"桶",如果两个对象equals相等但hashCode不等,你存进去就找不到了。约定是:equals相等的两个对象,hashCode必须相等。

面试官追问:两个对象hashCode相等,equals一定相等吗?

不一定。哈希冲突嘛,两个不同的key可能落到同一个桶里。反过来才是对的:equals相等 → hashCode一定相等。反过来不成立。

六、String/StringBuilder/StringBuffer:不变 vs 可变,安全 vs 不安全

String:不可变(final修饰的char数组)。每次修改都会创建新对象。优点:线程安全、可以缓存hashCode(所以String适合做HashMap的key)。缺点:频繁修改时性能极差。

StringBuilder:可变,线程不安全,但快。单线程环境下用它拼字符串。

StringBuffer:可变,线程安全(方法加了synchronized),但慢。多线程环境下用它。

面试高频场景:字符串拼接循环里用+=会怎样?

Strings="";for(inti=0;i<10000;i++){s+="a";// 每次循环创建新的String对象,旧的变成垃圾}

每次+=都new一个StringBuilder再toString,循环10000次创建10000个临时对象。正确做法是循环外用StringBuilder。

七、泛型:编译完了就不存在了

泛型就是"类型参数"。你写List<String>,意思是这个List只能装String。

但关键是:Java的泛型是"伪泛型"——类型擦除。

编译之后,List<String>List<Integer>在字节码里都是List,类型参数被擦掉了,替换成Object或对应的上界。

List<String>list1=newArrayList<>();List<Integer>list2=newArrayList<>();list1.getClass()==list2.getClass()// true,都是ArrayList.class

面试官追问:为什么Java要设计成类型擦除?

为了向后兼容。Java 1.5之前没有泛型,如果直接改成真泛型(像C++模板那样),老代码就没法在JVM上跑了。擦除式泛型保证老代码和新代码能共存。

泛型中的通配符:

  • ? extends T:T或T的子类,可以读,不能写(因为不知道具体类型)。
  • ? super T:T或T的父类,可以写,读出来是Object

PECS原则(Producer Extends, Consumer Super):生产者用extends(往外取),消费者用super(往里放)。

八、反射:运行时扒开类的衣服看内部

反射就是在运行时获取类的信息(构造器、方法、字段),并且能动态调用。

能干吗?

  • 获取Class对象:Class.forName()对象.getClass()类名.class
  • 创建实例:clazz.newInstance()constructor.newInstance()
  • 调用方法:method.invoke()
  • 访问字段:field.get()/set()

最关键的——反射能打破封装:你可以把private字段改成accessible然后读写它。

实际应用场景:

  • Spring的IoC容器:通过反射创建对象、注入依赖。
  • MyBatis:通过反射把数据库查询结果映射成Java对象。
  • 注解处理器:通过反射读取注解信息。

性能问题:反射比直接调用慢,因为有额外开销(检查访问权限、动态解析)。但Spring这种框架只在启动时用反射,不影响运行时性能。

面试官追问:反射破坏了什么原则?

破坏了封装性——明明private的字段,反射能强行访问。但框架需要这种"特权"来实现通用功能,所以这是一种"设计上的权衡"。

九、序列化:把对象变成"流动的"字节

序列化:把Java对象转成字节流(存文件、传网络)。

反序列化:从字节流恢复成Java对象。

怎么用?实现Serializable接口(标记接口,没有任何方法),用ObjectOutputStream写,ObjectInputStream读。

关键点:

  • transient修饰的字段不会被序列化(比如密码、敏感信息)。
  • serialVersionUID:版本号。反序列化时会校验,不一致就抛异常。强烈建议显式声明,否则类变了就反序列化失败。
  • 静态变量不会被序列化——它属于类不属于对象。

实际工作中,很少直接用Java原生序列化(性能差、跨语言困难)。生产环境更多用JSON(Jackson/Fastjson)或Protobuf。

十、面试官真正想听什么

背完上面这些,你已经有80%的知识储备了。但面试官想听的不是定义,是"为什么"和"怎么用"

举个例子。

面试官问:“String为什么设计成不可变的?”

普通回答:“因为String是final的,底层char数组也是final的。”

面试官想听的:

第一,为了线程安全,不可变对象天然线程安全;第二,为了性能,String的hashCode可以缓存,适合做HashMap的key;第三,为了安全,比如数据库连接的用户名密码用String存,防止被意外修改。这也是为什么String被设计成final——防止子类破坏不可变性。”

再举个例子。

面试官问:“抽象类和接口的区别?”

普通回答:“抽象类可以有实现方法,接口不行。”

面试官想听的(2026年):

“Java 8之后接口也能有default方法了,所以从语法上区别在缩小。但从设计层面看,抽象类强调的是’是什么’(is-a),接口强调的是’能做什么’(can-do)。比如人继承哺乳动物,人实现跑步接口。另外,单继承是为了避免菱形问题,这是Java设计者权衡后的选择。”

小结

知识点本质面试考点
JVM虚拟电脑跨平台原理、内存管理
封装/继承/多态现实世界的模拟设计原则、多态实现原理
重载/重写编译时选/运行时选静态分派 vs 动态分派
抽象类/接口是啥 vs 能干啥为什么单继承多实现
== / equals地址 vs 内容hashCode约定
String系列不可变 vs 可变性能、线程安全
泛型类型参数类型擦除、PECS
反射运行时扒类Spring底层、封装破坏
序列化对象变字节流transient、serialVersionUID

记住一句话:Java基础从来不考"背功",考的是"你知不知道这东西造出来是解决什么问题的"。

知道了"为什么",就不用背"是什么"了。

下一篇我们讲集合框架——从HashMap开始,你会发现整个集合体系就是"一堆数据结构的Java实现"。