== 和 equals 都是用来做比较的,用法完全不同。== 对于基本数据类型,直接比较变量的值是否相等;对于引用数据类型,比较的是对象在内存中的地址是否一样。而 equals 是 Object 类的方法,默认效果和 == 完全相同,但很多类(比如 String、Integer)都重写了这个方法,改成比较对象的内容。日常开发中,比较字符串、包装类的内容一定要用 equals,避免空指针可以用 Objects.equals()。简单总结:基本类型比数值用 ==,引用类型比内容用重写后的 equals。
JVM 的四种引用强度依次递减,决定了对象被垃圾回收的时机。强引用是最常用的,new 出来的对象就是强引用,只要引用存在,GC 绝不会回收,内存溢出也不会动它。软引用在内存充足时保留,内存快溢出时才会被回收,适合做图片、缓存等场景。弱引用优先级更低,只要触发 GC,无论内存是否充足都会被回收,常用在 ThreadLocal、缓存容器中。虚引用是最弱的,无法获取对象,唯一作用是跟踪对象的回收时机,主要用于管理堆外内存。四种引用帮我们灵活控制对象生命周期,避免内存泄漏。
接口和抽象类都是不能直接实例化的抽象类型,核心区别很大。抽象类用 abstract class 定义,支持单继承;接口用 interface 定义,支持多实现。抽象类可以有普通成员变量、构造方法、普通方法;接口在 JDK8 后只能有常量、抽象方法、默认方法和静态方法,没有构造方法。设计用途上,抽象类用于抽取子类的公共属性和行为,体现 is-a 关系;接口用于定义行为规范,体现 like-a 关系。简单说:想复用代码用抽象类,想定义规则用接口。
多态就是父类引用指向子类对象,编译看左边类型,运行执行右边子类的方法。它的底层靠 JVM 的动态分派和虚方法表实现。类加载时,JVM 会为每个类创建一个虚方法表,存储所有方法的内存地址。当调用对象方法时,JVM 不会直接根据引用类型找方法,而是根据对象实际的类型,去虚方法表里查找对应的方法地址,最终调用子类重写后的方法。静态方法、私有方法、final 方法不会进入虚方法表,也就不支持多态,这也是多态的核心限制。
绑定就是确定方法调用的具体实现,分编译期和运行期两种。静态绑定(早绑定) 在编译阶段就确定了方法,速度快,比如 private 方法、static 方法、final 方法、构造器,这些方法无法被重写,编译器能直接确定调用目标。动态绑定(晚绑定) 在运行阶段确定,是多态的核心,普通的实例方法都会用动态绑定。JVM 会根据对象的实际类型,去虚方法表中查找方法。简单说:编译能确定的是静态绑定,运行才能确定的是动态绑定,动态绑定支持多态,静态绑定效率更高。
final 是最终的意思,用来限制修改,保证代码的安全性和稳定性。修饰类:类不能被继承,比如 String 类就是 final 的。修饰方法:方法不能被子类重写,防止核心逻辑被篡改。修饰变量:变量变成常量,只能赋值一次。修饰成员变量必须初始化,修饰局部变量可以延迟赋值,修饰方法参数则参数不能被修改。除此之外,final 还能帮助 JVM 优化代码,同时不可变的 final 对象天然线程安全,在并发编程中非常实用。
这三个名字很像,但完全不是一个东西。final 是关键字,用来限制类、方法、变量不可修改。finally 用在异常处理中,和 try/catch 搭配,无论代码是否抛出异常,finally 里的代码一定会执行,专门用来释放流、关闭连接等资源。finalize 是 Object 类的方法,对象被 GC 回收前会调用,用来释放资源,但它执行时机不确定、有内存泄漏风险,性能极差,现在已经被废弃了。三者用途天差地别,千万不要混淆。
Java 有 4 种访问修饰符,用来控制类、方法、变量的访问权限,权限从小到大。private:仅当前类内部可以访问,是封装的核心。default(不写修饰符):同一个包下的类可以访问。protected:同一个包 + 不同包的子类可以访问。public:全局访问,所有类都能用。日常开发中,成员变量尽量用 private 保证封装,方法根据需求选择权限,对外提供功能用 public,继承相关用 protected,包内通用用 default。
内部类就是定义在类内部的类,一共 4 种,各有特点。成员内部类:属于外部类对象,能访问外部类所有私有成员。静态内部类:用 static 修饰,只能访问外部类的静态成员,最常用。局部内部类:定义在方法里,作用域仅限当前方法,很少用。匿名内部类:没有类名,一次性使用,常用于创建接口 / 抽象类的实例,现在基本被 Lambda 表达式替代。内部类最大的优势是可以直接访问外部类的私有成员,简化代码,适合写回调、监听逻辑。
Java 有 8 种基本数据类型,不能直接存进集合,也不能用面向对象的方法,所以设计了对应的包装类(比如 int→Integer)。包装类是引用类型,支持泛型、可以为 null、自带工具方法。自动装箱是基本类型自动转包装类(Integer a=10),自动拆箱是包装类自动转基本类型(int b=a),由编译器自动完成。使用时要注意:包装类为 null 时拆箱会抛出空指针异常,这是最容易踩的坑。
枚举用 enum 定义,用来表示固定不变的常量集合,比如性别、季节、订单状态。相比静态常量,枚举优势巨大:类型安全,不会传入非法值;不可实例化,线程安全;自带遍历、比较方法,代码可读性极高;底层是 final 类,实例固定,不会被篡改。开发中,凡是有限的固定取值,都推荐用枚举替代魔法值和静态常量,让代码更规范、更优雅,还能避免很多 bug。
三者都是字符串操作类,核心区别是可变性和线程安全。String 是不可变类,底层是 final 数组,修改字符串会生成新对象,线程安全但效率极低。StringBuilder 是可变类,非线程安全,拼接字符串效率最高。StringBuffer 也是可变类,用 synchronized 保证线程安全,效率比 StringBuilder 低一点。日常开发:单线程大量字符串拼接用 StringBuilder,多线程用 StringBuffer,少量固定字符串用 String。
数组和集合都是存储数据的容器,差别很大。数组长度固定,创建后不能修改;集合可以动态扩容,长度灵活。数组可以存基本类型和引用类型;集合只能存引用类型,基本类型需要包装类。数组只有 length 属性,功能单一;集合提供了增删改查、排序、过滤等丰富的方法。数组查询性能极高,集合使用更灵活。固定长度、高性能用数组,动态数据、复杂操作用集合(List/Set/Map)。
两者都是 Throwable 的子类,代表程序出现的问题,但本质不同。Error 是 JVM 层面的严重错误,比如内存溢出、栈溢出,程序无法捕获和处理,只能通过优化代码、调整 JVM 参数避免。Exception 是程序运行的问题,分为编译期异常(必须捕获处理)和运行时异常(空指针、数组越界),可以通过 try/catch 处理,让程序继续运行。简单说:Error 救不了,Exception 能处理,开发中只需要关注和处理 Exception。