Java的一些基础知识

本文详细解释了Java中的IncompatibleClassChangeError,该错误通常发生在运行时,由于类的定义发生变化导致。通过一个实例展示了如何复现此类错误,包括将类变量改为静态、修改访问权限以及删除基类方法等场景,导致IllegalAccessError和NoSuchMethodError。这些错误都是类不兼容变化的结果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

补充一些java基础知识

接口和抽象类的区别

特性抽象类(Abstract Class)接口(Interface)
是否可实例化❌ 不可实例化❌ 不可实例化
构造方法✅ 可以有构造方法❌ 不能有构造方法
字段类型✅ 可以有任意类型的字段(public、protected、private 等)✅ 字段默认为 public static final(常量)
方法实现✅ 可以有具体方法(Java 8+ 支持默认方法)✅ Java 8+ 支持默认方法和静态方法,否则仅允许抽象方法
继承方式✅ 一个类只能继承一个抽象类✅ 一个类可以实现多个接口
访问修饰符✅ 方法可以定义为 public、protected、private✅ 方法默认是 public,不能定义为 private(Java 9+ 允许 private 方法)
多重继承❌ Java 不支持类的多重继承✅ 接口支持多重继承(通过 extends)
设计目的✅ 定义类的共享行为和状态✅ 定义行为契约(能力),不涉及状态

int、Integer区别

类型本质存储方式
int基本数据类型(Primitive Type)直接存储数值,占用 4 字节
Integer包装类(Wrapper Class)存储对象引用,包含一个 int 字段
Integer i = 10;      // 自动装箱(int → Integer)
int j = new Integer(20);  // 自动拆箱(Integer → int)
特性intInteger
类型基本数据类型引用数据类型(类)
默认值0null(作为对象时)
内存占用4 字节对象头 + 4 字节(约 16 字节,取决于 JVM 实现)
是否可为 null❌ 不可为 null✅ 可为 null
比较方式== 比较数值== 比较引用地址,.equals() 比较数值
适用场景数值计算、性能敏感场景集合类(如 List)、泛型、反射

java泛型

Java 泛型的核心实现原理是 类型擦除,其设计目标是兼容旧代码并简化 JVM 实现。尽管牺牲了运行时类型信息,但通过编译期类型检查和桥接方法,Java 泛型在安全性与灵活性之间取得了平衡。理解这些原理有助于避免常见陷阱(如泛型数组、类型捕获),并在实际开发中高效使用泛型。

类型擦除(Type Erasure)

基本原理

  • 编译阶段:泛型信息被完全擦除,替换为原始类型(Raw Type)。
    • 类型参数(如 T、E)被替换为 Object 或其上界(如 T extends Number 替换为 Number)。
    • 泛型方法中的类型参数也会被擦除。
  • 运行时:JVM 无法感知泛型的具体类型,泛型信息仅在编译期存在。

优势与代价

  • 优势:
    • 兼容旧版本 Java 代码(无需修改原有非泛型类)。
    • 避免为每个泛型类型生成额外的类文件(如 C++ 模板)。
  • 代价:
    • 运行时无法获取泛型类型信息(如 List 和 List 在运行时被视为同一类型)。
    • 限制泛型数组的创建(如 new T[10] 非法)。
特性Java 泛型C++ 模板
实现机制类型擦除(编译期擦除)实例化(编译期生成具体类型代码)
运行时类型信息无(完全擦除)有(保留具体类型信息)
性能无额外开销(运行时无泛型实例)可能有冗余代码(每个类型生成一份代码)
兼容性兼容旧版本 Java 代码无兼容性问题
  1. 桥接方法(Bridge Method)
  • 2.1 问题背景
    由于类型擦除,子类覆盖的方法签名可能与父类方法不一致,导致多态失效。
  • 2.2 解决方案
    编译器自动生成 桥接方法,确保方法调用的正确性。
  • 2.3 示例
class Parent<T> {
    public void set(T value) { ... }
}

class Child extends Parent<String> {
    @Override
    public void set(String value) { ... } // 被扩展为桥接方法
}

编译后(伪代码):

// Child 类中的桥接方法
public void set(Object value) {
    set((String) value); // 调用重写的方法
}

List为什么不能int,要Integer

关键点说明
泛型限制Java泛型仅支持引用类型,int无法直接用于集合。
自动装箱/拆箱简化了基本类型与包装类的转换,但底层仍依赖Integer对象。
null值支持Integer允许null,而int不能,适合处理可能缺失的数据。
性能与陷阱装箱拆箱带来轻微开销,需注意缓存范围和空指针异常。
替代方案使用基本类型数组或专用集合库(如Trove)可优化性能。

lambda表达式

在 Java 中,Lambda 表达式 是一种简化函数式接口实现的语法结构。其底层实现涉及编译器的转换机制、JVM 的 invokedynamic 指令,以及运行时的动态绑定策略。

  1. 函数式接口
    Lambda 表达式必须绑定到一个 函数式接口(Functional Interface),即只包含一个抽象方法的接口。例如:
@FunctionalInterface
interface MyFunction {
    String apply(String input);
}
  1. Lambda 转换为方法
    当 Java 编译器遇到 Lambda 表达式时,会将其转换为一个 私有静态方法,例如:
private static String lambda$main$0(String input) {
    return input + " - processed";
}

此方法包含了 Lambda 表达式的实际逻辑。


invokedynamic 的作用

  1. 调用链:Lambda 表达式在字节码中被表示为 invokedynamic 指令。
  2. 引导方法(Bootstrap Method):该指令会调用一个引导方法(由 JVM 提供,如 LambdaMetafactory),动态生成实现函数式接口的类。
List<String> names = Arrays.asList("Alice", "Bob");
names.forEach(name -> System.out.println(name));

对应指令如下:

INVOKEVIRTUAL java/util/List.forEach(Ljava/util/function/Consumer;)V
INVOKESTATIC java/lang/invoke/LambdaMetafactory.altMetafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;

LambdaMetafactory 的作用
JVM 提供的 LambdaMetafactory 是 Lambda 表达式实现的关键组件。它负责:

  1. 根据目标函数式接口(如 Consumer)动态生成一个实现了该接口的类。
    将 Lambda 表达式转换为该接口的一个实例。
    将 Lambda 方法(如 lambda$main$0)绑定到接口的方法(如 accept)上。
  2. 实现类的生成
    LambdaMetafactory 会生成一个匿名类(如 java/lang/invoke/LambdaForm D M H 或 c o m / e x a m p l e / M y L a m b d a DMH 或 com/example/MyLambda DMHcom/example/MyLambda$Lambda$1),其结构如下:
class com.example.MyLambda$$Lambda$1 implements Consumer<String> {
    private final MyLambda this$0; // 捕获的外部对象
    private final String capturedVar; // 捕获的外部变量

    public void accept(String s) {
        lambda$main$0(s); // 调用 Lambda 方法
    }
}

Lambda 表达式的底层实现可以概括为以下几步:

  1. 编译器转换:将 Lambda 转换为私有静态方法。
  2. 字节码生成:使用 invokedynamic 指令触发动态绑定。
  3. 运行时实现:通过 LambdaMetafactory 动态生成实现类。
  4. 变量捕获:根据上下文生成闭包逻辑(静态或非静态)。

这种设计使得 Lambda 表达式既保持了语法简洁,又具备高性能和线程安全的特性,是 Java 8 函数式编程的核心支撑。

Java多态

多态的实现需要满足以下三个条件:

  1. 继承关系:存在父子类或接口与实现类的继承关系。
  2. 方法重写:子类对父类的虚方法进行重写。
  3. 父类引用指向子类对象:通过父类引用调用方法时,实际执行的是子类的实现。

eg:

Animal a = new Dog();
a.speak(); // 实际调用的是 Dog.speak()

多态的底层实现机制

  1. 运行时常量池与符号引用
  • 每个类在编译时会生成一个 运行时常量池(Runtime Constant Pool),其中包含类、方法、字段的符号引用(Symbolic Reference)。
  • 符号引用是逻辑名称(如 com.example.Animal.speak:()V),需要在运行时解析为 直接引用(内存地址)。
  1. 方法调用指令与动态绑定
    Java 提供了多种方法调用指令:
  • invokevirtual:用于调用实例方法(包括虚方法)。
  • invokeinterface:用于调用接口方法。
  • invokestatic:用于调用静态方法。
  • invokedynamic:支持动态语言特性(如 Lambda 表达式)。

对于 虚方法(非 private、static、final 的方法),JVM 会在运行时通过 动态绑定 决定调用哪个方法。

  1. 对象头与类元数据
  • 每个 Java 对象都有一个 对象头(Object Header),其中包含一个 类指针(Class Pointer),指向该对象所属类的 元数据(Class Metadata)。
  • 通过类指针,JVM 可以访问类的方法表(Method Table)和字段信息。
  1. 方法表(Method Table)
  • 每个类在类加载时会生成一个 方法表,记录该类所有虚方法的地址。
  • 方法表的结构如下:plaintext
Method Table for Dog:
* - speak() -> Dog.speak()
* - eat()   -> Animal.eat()
  • 子类重写方法后,方法表中对应的条目会被替换为子类的实现。
  1. 动态绑定流程
    当调用虚方法时,JVM 执行以下步骤:
    1. 获取对象的实际类型:通过对象头中的类指针。
    2. 查找方法表:在实际类型的类元数据中查找方法表。
    3. 调用方法:根据方法表中的地址执行对应的方法。

JVM 的动态绑定优化

  1. 内联缓存(Inline Caching)
    JVM 会缓存最近调用过的类和方法地址,减少方法表查找的开销。
    如果某个引用经常指向同一类(如 Dog),JVM 会直接调用缓存的方法地址,无需每次查找方法表。
  2. JIT 编译优化
    JIT(Just-In-Time)编译器 会将热点代码(频繁调用的方法)编译为本地机器码。
    对于多态调用,JIT 可能通过 方法内联(Inlining)进一步优化性能,前提是能确定调用的目标方法。

Java 多态的底层实现依赖于以下机制:

  1. 运行时常量池:存储方法的符号引用。
  2. 方法表:类加载时生成,记录虚方法的地址。
  3. 对象头与类指针:通过对象头确定实际类型。
  4. 动态绑定:运行时根据方法表查找并调用实际方法。
  5. JIT 优化:通过内联缓存和内联提升性能。

这种设计使得 Java 能够灵活支持多态,同时通过 JVM 的优化机制保持较高的执行效率。

happends before

在 Java 中,Happens-Before 原则 是 Java 内存模型(JMM, Java Memory Model)的核心概念之一,用于定义多线程程序中操作的 可见性顺序性。它确保一个线程的操作对另一个线程是可见的,并且在某些情况下保证操作的顺序不会被重排序。

Happens-Before 原则 是一种 逻辑顺序关系,而非物理时间上的先后顺序。如果操作 A happens-before 操作 B,则:

  1. 可见性:操作 A 的结果对操作 B 可见。
  2. 顺序性:操作 A 在操作 B 之前发生(逻辑上),但不一定在物理时间上先于 B。

Happens-Before 的内置规则

  1. 程序顺序规则(Program Order Rule)

同一线程内的操作按代码顺序执行,即 前一个操作 happens-before 后一个操作。

int a = 1; // A
int b = a + 1; // B
// A happens-before B
  1. 监视器锁规则(Monitor Lock Rule)

解锁操作 happens-before 后续的加锁操作。

synchronized (lock) {
    x = 1; // A (unlock)
}
synchronized (lock) {
    y = x + 1; // B (lock)
}
// A happens-before B

  1. volatile 变量规则(Volatile Variable Rule)

写操作 happens-before 后续的读操作。

volatile int flag = false; // A
...
flag = true; // B (write)
...
if (flag) { ... } // C (read)
// B happens-before C

  1. 线程启动规则(Thread Start Rule)

线程的 start() 方法 happens-before 该线程的 run() 方法中的任何操作。

Thread t = new Thread(() -> { 
    System.out.println("Hello"); // A
});
t.start(); // B
// B happens-before A

  1. 线程终止规则(Thread Termination Rule)

线程中的所有操作 happens-before join() 方法的返回。

Thread t = new Thread(() -> { 
    x = 1; // A
});
t.start();
t.join(); // B
// A happens-before B
  1. 中断规则(Interrupt Rule)
    一个线程调用另一个线程的 interrupt() 方法 happens-before 该线程检测到中断。

  2. 终结器规则(Finalizer Rule)
    对象的构造函数执行完毕 happens-before 该对象的 finalize() 方法执行。

  3. 传递性(Transitivity)
    如果 A happens-before B,且 B happens-before C,则 A happens-before C。

Minor gc 和full gc区别

https://doctording.blog.csdn.net/article/details/145661014

大对象为什么直接分配堆中

https://doctording.blog.csdn.net/article/details/145661014

error 和 exception 的区别

java.lang.Throwable
├── java.lang.Exception
│ ├── java.lang.RuntimeException(运行时异常)
│ └── 其他检查型异常(Checked Exceptions)
└── java.lang.Error(错误)

java.lang.Throwable
├── java.lang.Exception
│   ├── java.lang.RuntimeException(运行时异常)
│   └── 其他检查型异常(Checked Exceptions)
└── java.lang.Error(错误)

Exception(异常)

  • 定义:表示程序运行过程中可能出现的可恢复的异常。
  • 特点:
    • 可以通过 try-catch 捕获并处理。
    • 检查型异常(Checked Exceptions):编译器强制要求处理(如 IOException)。
    • 运行时异常(Unchecked Exceptions):由 RuntimeException 及其子类构成,无需显式捕获(如 NullPointerException)。

Error(错误)

  • 定义:表示严重的系统级问题,通常无法通过程序恢复。
  • 特点:
    • 不建议捕获(除非有特殊需求,如日志记录)。
    • 通常由 JVM 或底层系统触发,如内存溢出、栈溢出等。
特性ExceptionError
是否可恢复可恢复(通过异常处理机制)通常不可恢复
是否需要显式处理检查型异常必须处理,运行时异常可选通常不需要处理
典型场景用户输入错误、文件读取失败等JVM 错误、内存溢出、栈溢出等
是否继承自 Throwable
是否继承自 Exception否(直接继承自 Throwable)

Java反射,为什么C/C++没有?

反射(Reflection)是一种 Java 程序运行期间的动态技术,可以在运行时(runtime)检査、修改其自身结构或行为。通过反射,程序可以访问、检测和修改它自己的类、对象、方法、属性等成员。

Java 反射机制的实现与其运行时环境设计密切相关,而C/C++因语言特性限制缺乏原生反射功能。以下是具体原因:

  • JVM的元数据支持
    Java的反射依赖于 JVM 在运行时存储的类元数据(如类名、方法签名、字段信息等),这些数据存储在内存的 方法区 (Method Area)的 Class 对象中。运行时可通过这些元数据动态查询和操作对象结构。 ‌

  • C/C++的设计哲学差异
    C++标准追求零开销编译期反射(如通过编译时注解实现反射),这与Java通过运行时环境动态获取信息的方式本质不同。C++标准委员会更倾向通过工具链(如 Clang )或第三方库(如 Qt 、 UE4 )实现反射功能,而非内置反射机制。 ‌

  • 类型系统差异
    C/C++强调类型安全,其结构体仅存储数据不保留类型信息,导致无法像Java那样通过运行时类型推导实现动态操作。而Java的 类文件格式 (如 Class文件 )本身包含足够元数据支持反射操作。

  • 反射的实现可能性
    虽然C++26标准已加入反射提案(Reflection TS),但目前仍处于开发阶段,尚未成为语言核心特性。部分编译器(如Clang)已通过实验性支持反射功能,但需额外配置编译环境。 ‌

Nio

https://doctording.blog.csdn.net/article/details/145839941

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值