Java基础语法问答

第一阶段:对象构造与销毁

1. 构造器链与初始化顺序

问题: 以下代码输出什么?按顺序写出初始化步骤。

java 复制代码
class A {
    static { System.out.print("S1 "); }
    { System.out.print("I1 "); }
    A() { System Lake.print("C1 "); }
}

class B extends A {
    static { System.out.print("S2 "); }
    { System.out.print("I2 "); }
    B() { 
        super();
        System.out.print("C2 "); 
    }
}

public class Main {
    public static void main(String[] args) {
        new B();
        System.out.println();
        new B();
    }
}

答案:

输出为:

复制代码
S1 S2 I1 C1 I2 C2 
I1 C1 I2 C2

初始化步骤:

  • 第一次执行 new B()
    1. 类加载阶段(只执行一次):
      • 加载类 A,执行静态块:输出 S1
      • 加载类 B,执行静态块:输出 S2
    2. 对象实例化阶段:
      • 创建 B 对象时,先调用父类 A 的构造器链:
        • 执行 A 的实例初始化块:输出 I1
        • 执行 A 的构造器:输出 C1
      • 然后执行 B 的实例初始化块:输出 I2
      • 最后执行 B 的构造器:输出 C2
  • 第二次执行 new B()
    • 类加载已完成(静态块不再执行)。
    • 对象实例化:顺序同上,输出 I1 C1 I2 C2
2. 重载构造器的陷阱

问题: 以下代码是否有编译错误?为什么?

java 复制代码
public class Test {
    private int value;
    
    public Test(int value) {
        this.value = value;
    }
    
    public Test() {
        System.out.println("Default");
        this();  // 这行有什么问题?
    }
}

答案:

有编译错误。

原因:this() 用于调用本类的其他构造器,必须在构造器的第一行执行。这里 this() 位于 System.out.println("Default") 之后,不是第一行,因此编译器会报错。正确做法是:如果无参构造器要调用有参构造器,应将 this() 移到第一行。

3. final成员初始化时机

问题: 以下两种初始化方式有何本质区别?

java 复制代码
// 方式一
class Example1 {
    private final int x = 10;
}

// 方式二
class Example2 {
    private final int x;
    
    public Example2() {
        x = 10;
    }
}

答案:

两种方式都确保 final 变量 x 被初始化,但本质区别在于初始化时机:

  • 方式一: x 在声明时直接初始化(在字段定义处)。这发生在对象创建时,实例初始化块执行前。
  • 方式二: x 在构造器中初始化。这允许在构造器内赋值,更灵活,例如可以根据参数动态赋值。
    关键点:final 变量必须在构造器结束前被赋值一次,且不可修改。
4. 对象销毁与finalize()

问题: finalize() 方法是否适合用于资源释放(如关闭文件)?为什么?GC何时调用它?

答案:

  • 是否适合: 不适合。
  • 原因: finalize() 的执行时机不确定,由垃圾回收器(GC)在回收对象前调用,但 GC 运行时间不可预测。这可能导致资源(如文件句柄)未及时释放,引发内存泄漏或资源耗尽。资源释放应使用显式方法(如 close())或 try-with-resources 机制。
  • GC调用时机: GC 在对象被判定为"不可达"(即无引用)后、回收内存前调用 finalize()。但调用时间不保证,甚至可能不调用(如 JVM 关闭时)。
5. 实例初始化块 vs 构造器

问题: 如果一个类同时有多个实例初始化块和构造器,执行顺序是什么?

java 复制代码
class Demo {
    { System.out.print("A"); }
    
    Demo() { System.out.print("C"); }
    
    { System.out.print("B"); }
}

答案:

执行顺序:

  1. 实例初始化块按代码顺序执行(在构造器之前)。
  2. 构造器在实例初始化块后执行。
    对于 Demo 类:
  • 创建对象时,先执行所有实例初始化块:输出 A,然后 B
  • 再执行构造器:输出 C
    因此,输出为 A B C

第二阶段:继承与多态深度拷问

6. 方法绑定之谜

问题: 以下代码输出什么?解释静态绑定与动态绑定的区别。

java 复制代码
class Parent {
    void show() { System.out.print("Parent "); }
    static void print() { System.out.print("P-Static "); }
}

class Child extends Parent {
    void show() { System.out.print("Child "); }
    static void print() { System.out.print("C-Static "); }
}

public class Main {
    public static void main(String[] args) {
        Parent p = new Child();
        p.show();
        p.print();
        
        Child c = new Child();
        c.show();
        c.print();
    }
}

答案:

输出:

复制代码
Child P-Static Child C-Static

解释:

  • 静态绑定 vs 动态绑定:
    • 静态绑定: 在编译时确定方法调用,基于引用类型(如变量声明类型)。适用于 static 方法、final 方法和 private 方法。这里 print() 是静态方法,所以 p.print() 调用 Parent.print()(输出 P-Static),因为 pParent 类型。
    • 动态绑定: 在运行时确定方法调用,基于实际对象类型。适用于非静态方法(实例方法)。这里 show() 是非静态方法,所以 p.show() 调用 Child.show()(输出 Child),因为实际对象是 Child
  • 详细输出:
    • p.show():动态绑定,对象是 Child,输出 Child
    • p.print():静态绑定,引用是 Parent,输出 P-Static
    • c.show():动态绑定,对象是 Child,输出 Child
    • c.print():静态绑定,引用是 Child,输出 C-Static
7. 访问权限的继承规则

问题: 子类能否继承父类的 private 方法?能否重写父类的 private 方法?以下代码是否编译通过?

java 复制代码
class Base {
    private void method() {}
}

class Derived extends Base {
    public void method() {}  // 这是重写吗?
}

答案:

  • 能否继承: 子类不能继承父类的 private 方法,因为 private 方法只在父类内部可见。
  • 能否重写: 不能重写父类的 private 方法,因为子类无法访问或覆盖它。
  • 代码是否编译通过: 编译通过。Derived.method() 是一个新方法,不是重写(因为父类方法 private)。
  • 是否重写: 这不是重写,而是子类定义的新方法。重写要求方法签名相同,且父类方法可访问(非 private)。
8. super关键字的限制

问题: 在静态方法中能否使用 super 关键字?为什么?以下代码错在哪里?

java 复制代码
class Animal {
    String name = "Animal";
}

class Dog extends Animal {
    String name = "Dog";
    
    static void print() {
        System.out.println(super.name);  // 这里
    }
}

答案:

  • 能否使用 super 在静态方法中不能使用 super
  • 原因: super 指代父类对象实例,用于访问父类成员或方法,但静态方法不依赖于对象实例(属于类级别),因此无法使用 super(它需要当前对象上下文)。
  • 代码错误: print() 是静态方法,super.name 试图访问父类实例字段,但无对象实例。正确做法:改为实例方法,或直接使用父类名(如 Animal.name,但字段非静态)。
9. 构造器中的多态陷阱

问题: 以下代码输出什么?解释原因(涉及对象初始化状态)。

java 复制代码
class Animal {
    Animal() {
        print();
    }
    void print() {
        System.out.print("Animal ");
    }
}

class Dog extends Animal {
    private int age = 5;
    
    Dog() {
        super();
        print();
    }
    
    void print() {
        System.out.print(age + " ");
    }
}

public class Main {
    public static void main(String[] args) {
        new Dog();
    }
}

答案:

输出:

复制代码
0 5 

解释原因:

  • 对象初始化顺序:
    1. new Dog() 调用 Dog() 构造器。
    2. Dog()super() 调用父类 Animal() 构造器。
    3. Animal() 中,print() 被调用。此时,由于多态(动态绑定),实际调用 Dog.print()(因为对象是 Dog)。
    4. 但此时 Dogage 尚未初始化(在 super() 后初始化),默认值为 0,所以输出 0
    5. Animal() 完成后,执行 Dog 的字段初始化:age = 5
    6. 然后执行 Dog() 中的 print():输出 5
      关键:父类构造器调用被重写方法时,子类状态可能未完全初始化,导致意外行为。
10. 接口默认方法冲突

问题: 如果一个类实现两个接口,且两个接口有相同的默认方法,类该如何处理?以下代码如何修改才能编译?

java 复制代码
interface A {
    default void show() { System.out.println("A"); }
}

interface B {
    default void show() { System.out.println("B"); }
}

class C implements A, B { }  // 编译错误

答案:

  • 处理方式: 类必须覆盖冲突的默认方法,否则编译错误。
  • 修改代码:C 中实现 show() 方法,例如:
java 复制代码
class C implements A, B {
    @Override
    public void show() {
        System.out.println("C");  // 自定义实现
        // 或调用特定接口的默认方法:A.super.show();
    }
}

第三阶段:高级特性与设计

11. 内部类的内存泄漏风险

问题: 非静态内部类隐式持有外部类的引用,这在什么场景下可能导致内存泄漏?如何避免?

答案:

  • 风险场景: 当非静态内部类实例被长期持有(如作为监听器或缓存),而外部类实例不再需要时,内部类隐式引用会阻止外部类被垃圾回收,导致内存泄漏。常见于事件监听、线程或静态集合持有内部类实例。
  • 如何避免:
    • 使用静态内部类(static 修饰),它不持有外部类引用。
    • 如果必须用非静态内部类,确保在外部类不再需要时,断开内部类与外部类的关联(如置为 null)。
    • 避免将非静态内部类实例暴露给长生命周期对象。
12. 匿名内部类的限制

问题: 匿名内部类能否有构造器?能否实现多个接口?以下代码是否合法?

java 复制代码
Runnable r = new Runnable() {
    private int x;
    { x = 10; }  // 实例初始化块
    
    public void run() { System.out.println(x); }
};

答案:

  • 能否有构造器: 匿名内部类不能有显式构造器(不能定义构造方法),但可以用实例初始化块模拟初始化。
  • 能否实现多个接口: 不能。匿名内部类只能实现一个接口或继承一个类(单继承)。
  • 代码是否合法: 合法。这里使用实例初始化块初始化 x,实现了 Runnable 接口。
13. 单例模式的多线程安全实现

问题: 写出三种线程安全的单例实现方式,并分析优缺点。

答案:

  1. 饿汉式(Eager Initialization):

    java 复制代码
    public class Singleton {
        private static final Singleton INSTANCE = new Singleton();
        private Singleton() {}
        public static Singleton getInstance() { return INSTANCE; }
    }
    • 优点: 线程安全(静态变量类加载时初始化),简单高效。
    • 缺点: 可能浪费资源(如果实例未被使用)。
  2. 双重检查锁(Double-Checked Locking):

    java 复制代码
    public class Singleton {
        private volatile static Singleton instance;
        private Singleton() {}
        public static Singleton getInstance() {
            if (instance == null) {
                synchronized (Singleton.class) {
                    if (instance == null) {
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }
    • 优点: 懒加载,性能较好(只在第一次同步)。
    • 缺点: 实现稍复杂,需 volatile 确保可见性。
  3. 静态内部类(Static Inner Class):

    java 复制代码
    public class Singleton {
        private Singleton() {}
        private static class Holder {
            static final Singleton INSTANCE = new Singleton();
        }
        public static Singleton getInstance() { return Holder.INSTANCE; }
    }
    • 优点: 懒加载,线程安全(类加载机制保证),无需同步。
    • 缺点: 无法传参初始化。
14. 不可变类的设计

问题: 设计一个不可变类 ImmutablePoint(包含 x、y 坐标),需要满足哪些条件?

答案:

设计条件:

  1. 类声明为 final,防止子类修改。
  2. 所有字段为 private final,确保不可修改。
  3. 无 setter 方法。
  4. 通过构造器初始化所有字段。
  5. 如果字段是引用类型(如对象),需确保其不可变或深拷贝(避免外部修改)。
    示例代码:
java 复制代码
public final class ImmutablePoint {
    private final int x;
    private final int y;
    
    public ImmutablePoint(int x, int y) {
        this.x = x;
        this.y = y;
    }
    
    public int getX() { return x; }
    public int getY() { return y; }
}
15. 对象克隆的深拷贝与浅拷贝

问题: 实现 Cloneable 接口时,Object.clone() 默认是浅拷贝。如何实现深拷贝?以下代码有什么问题?

java 复制代码
class Data implements Cloneable {
    int[] arr = {1, 2, 3};
    
    @Override
    public Data clone() throws CloneNotSupportedException {
        return (Data) super.clone();  // 浅拷贝
    }
}

答案:

  • 实现深拷贝: 覆盖 clone() 方法,手动复制引用对象。例如:

    java 复制代码
    @Override
    public Data clone() throws CloneNotSupportedException {
        Data cloned = (Data) super.clone();  // 浅拷贝基础
        cloned.arr = arr.clone();  // 深拷贝:复制数组
        return cloned;
    }
  • 代码问题: 默认 super.clone() 是浅拷贝,arr 数组引用被共享(原对象和克隆对象共享同一数组)。修改数组会影响双方。需手动复制数组实现深拷贝。


附加题

16. 枚举类的本质

问题: enum 在 Java 中是什么?以下代码编译后相当于什么?

java 复制代码
enum Color { RED, GREEN, BLUE }

答案:

  • 本质: enum 是一种特殊类,继承自 java.lang.Enum,每个枚举常量是类的实例(public static final)。

  • 编译后等价: 相当于以下类:

    java 复制代码
    final class Color extends Enum<Color> {
        public static final Color RED = new Color("RED");
        public static final Color GREEN = new Color("GREEN");
        public static final Color BLUE = new Color("BLUE");
        private Color(String name) { super(name); }
        // 其他方法(如 values(), valueOf())由编译器生成
    }
17. 静态内部类的加载时机

问题: 静态内部类在何时加载?它与外部类的类加载器是否相同?

答案:

  • 加载时机: 静态内部类在第一次使用时加载(如首次访问其静态成员或创建实例),不同于外部类加载(外部类加载时不加载内部类)。
  • 类加载器: 相同。静态内部类和外部类使用同一个类加载器加载。
18. 方法重写的不可变性要求

问题: 如果父类方法返回 List<String>,子类重写时能否返回 ArrayList<String>?为什么?(协变返回类型)

答案:

  • 能否返回: 能。
  • 原因: Java 5+ 支持协变返回类型(covariant return type)。子类重写方法可以返回父类方法返回类型的子类型。ArrayList<String>List<String> 的子类,因此符合规则。这增强了灵活性而不破坏类型安全。
相关推荐
龙智DevSecOps解决方案2 小时前
汽车网络安全开发语言选型指南:C/C++/Rust/Java等主流语言对比+Perforce QAC/Klocwork工具支持
开发语言·autosar·嵌入式开发·perforce·代码安全·汽车网络安全
‿hhh3 小时前
微服务智慧交通管理平台 - 项目实现(结合Qoder搭建)
java·人工智能·机器学习·微服务·架构·需求分析·规格说明书
Eiceblue3 小时前
将 Python 列表导出为 Excel 文件:一维、二维、字典列表
开发语言·python·excel·visual studio code
五岳10 小时前
分库分表数据源ShardingSphereDataSource的Connection元数据误用问题分析
java·mysql·爬坑
Swizard10 小时前
别再让你的 Python 傻等了:三分钟带你通过 asyncio 实现性能起飞
python
带刺的坐椅10 小时前
迈向 MCP 集群化:Solon AI (支持 Java8+)在解决 MCP 服务可扩展性上的探索与实践
java·ai·llm·solon·mcp
鼠爷ねずみ10 小时前
SpringCloud前后端整体开发流程-以及技术总结文章实时更新中
java·数据库·后端·spring·spring cloud
代码or搬砖10 小时前
String字符串
android·java·开发语言