Java 基础面试一

发布于:2025-07-03 ⋅ 阅读:(14) ⋅ 点赞:(0)

Java 基础面试一

一、引用数据类型与基本数据类型的差异

引用数据类型与基本数据类型的核心区别在于存储内容和内存占用。引用数据类型不仅存储数据,还封装了操作数据的方法,如类和对象大多基于此类型构建。但相比基本数据类型,它因额外的对象头信息而占用更多内存空间。

在比较操作中,基本数据类型用 == 比较值,引用数据类型用 == 比较内存地址。例如:

int a = 10;
int b = 10;
System.out.println(a == b); // true,比较值相等

Integer c = new Integer(10);
Integer d = new Integer(10);
System.out.println(c == d); // false,比较地址不同

二、Integer 赋值 100 与 1000 的区别

当使用 Integer x = 100;Integer y = 100; 时,x == y 返回 true。这是因为 Java 为常用的小整数(-128 到 127)设立了缓存池,相同值直接引用池中对象。而超出此范围的值(如 1000),每次赋值都新建对象,故 Integer x = 1000; Integer y = 1000; 时,x == y 返回 false。但 intInteger 比较时会自动拆箱为基本类型,比较的是值。

Integer x = 100;				
Integer y = 100;
System.out.println(x == y);   	// true,引用池中同一对象

Integer x = 1000;
Integer y = 1000;
System.out.println(x == y);  // false,新建不同对象

int x = 1000;
Integer y = 1000;
System.out.println(x == y);	// true,自动拆箱后比较值

三、类对象的加载过程

类的加载始于类被加载至方法区。之后,JVM 在主线程栈中执行 main 方法,进行局部变量赋值等操作。遇到 new 关键字时,在堆内存创建对象,初始化对象字段,完成对象的构建。

public class Example {
    public static void main(String[] args) {
        MyClass obj = new MyClass(); // 加载类、执行 main 方法、创建对象
    }
}

class MyClass {
    int value;
}

四、static 与 abstract 的冲突

staticabstract 不能同时修饰同一个成员。static 表明成员属于类本身,而 abstract 要求子类实现抽象方法。二者目的相悖,故不能共存。

// 错误示例
public abstract class MyClass {
    public static abstract void myMethod(); // 编译错误
}

五、Java 三大特性详解

  • 继承 :子类继承父类方法和变量,实现代码复用。
  • 封装 :将数据和操作封装,提高代码安全性和可维护性,如类通过私有成员隐藏实现细节。
  • 多态 :父类引用可指向子类对象,调用方法时执行子类特定实现,增强代码灵活性。
// 多态示例
class Animal {
    void makeSound() {}
}

class Dog extends Animal {
    void makeSound() { System.out.println("汪汪"); }
}

class Cat extends Animal {
    void makeSound() { System.out.println("喵喵"); }
}

public class PolymorphismExample {
    public static void main(String[] args) {
        Animal dog = new Dog();
        Animal cat = new Cat();
        dog.makeSound(); // 输出 "汪汪"
        cat.makeSound(); // 输出 "喵喵"
    }
}

六、静态代码块与实例代码块的区别

静态代码块在类加载时执行,仅执行一次,常用于类的初始化操作。实例代码块在每次创建对象时执行,用于对象的初始化。

class MyClass {
    static { System.out.println("静态代码块执行"); } // 类加载时执行
    { System.out.println("实例代码块执行"); } // 对象创建时执行
}

public class CodeBlockExample {
    public static void main(String[] args) {
        new MyClass(); // 输出:静态代码块执行、实例代码块执行
        new MyClass(); // 输出:实例代码块执行
    }
}

七、==equals 的区别

== 对基本数据类型比较值,引用数据类型比较地址。equals 用于比较对象内容,可重写以实现自定义比较逻辑。

String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2); // false,比较地址不同
System.out.println(s1.equals(s2)); // true,内容相同

八、JDK 1.8 前后字符串常量池的变化

JDK 1.8 前,字符串常量池在永久代,空间有限,易溢出,且字符串引用和创建可能在堆和常量池间复制。JDK 1.8 后,字符串常量池移至堆中,优化了存储和引用效率。

String s1 = "hello";
String s2 = "hello";
System.out.println(s1 == s2); // true,JDK 1.8 及之后指向堆中同一地址

九、String、StringBuffer、StringBuilder 的差异

  • String :不可变,每次修改都创建新对象,线程安全。
  • StringBuffer :可变,线程安全但效率低,因采用 synchronized
  • StringBuilder :可变,效率高但线程不安全,适合单线程环境。
String s = "hello";
s += " world"; // 实际创建新字符串

StringBuilder sb = new StringBuilder("hello");
sb.append(" world"); // 直接修改对象

十、ArrayList 的扩容机制

ArrayList 初始容量为 0,第一次扩容至 10,之后每次扩容为原容量的 1.5 倍。扩容时会创建新数组并复制数据,影响性能,故建议提前预估容量。

ArrayList<Integer> list = new ArrayList<>();
for (int i = 0; i < 15; i++) {
    list.add(i);
    if (i == 10) {
        System.out.println("扩容后容量:" + ((ArrayList<?>) list).capacity()); // 输出扩容后的容量
    }
}

十一、Vector 与 ArrayList 的区别

  • 线程安全 :Vector 线程安全,ArrayList 不安全。
  • 效率 :Vector 因线程安全机制效率低。
  • 扩容方式 :Vector 扩容为原容量的 2 倍,ArrayList 为 1.5 倍。
Vector<Integer> vector = new Vector<>();
vector.add(1); // 同步操作

ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(1); // 非同步操作

十二、数组转列表与列表转数组的区别

Arrays.asList 将数组转为固定大小的列表,元素修改影响原数组。toArray 将集合转为数组,修改新数组不影响原集合。

Integer[] array = {1, 2, 3};
List<Integer> list = Arrays.asList(array); // 数组转列表
list.set(0, 10); // array[0] 变为 10

ArrayList<Integer> arrayList = new ArrayList<>(Arrays.asList(1, 2, 3));
Integer[] newArray = arrayList.toArray(new Integer[0]); // 列表转数组
newArray[0] = 10; // arrayList 中元素仍为 1

十三、ArrayList 与 LinkedList 的区别

  • 底层结构 :ArrayList 基于动态数组,LinkedList 基于双向链表。
  • 查询效率 :ArrayList 随机访问快,LinkedList 慢。
  • 增删效率 :ArrayList 增删慢(需移动元素),LinkedList 快(只需修改指针)。
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(0, 1); // 可能需移动大量元素

LinkedList<Integer> linkedList = new LinkedList<>();
linkedList.addFirst(1); // 直接修改指针,无需移动元素

十四、Set 与 List 的区别

  • 去重 :Set 自动去重,List 允许重复。
  • 顺序 :Set 无序,List 有序。
  • 操作效率 :Set 的增删查(基于哈希表实现)效率高,List 的查询快(基于索引),增删慢(需移动元素)。
Set<Integer> set = new HashSet<>();
set.add(1);
set.add(1); // 重复添加无效

List<Integer> list = new ArrayList<>();
list.add(1);
list.add(1); // 允许重复添加

十五、HashSet 的去重原理

HashSet 底层基于 HashMap,元素作为键,固定值作为值。通过 hashCode 获取哈希值,结合 equals 比较元素内容,相同则视为重复并去重。

HashSet<String> set = new HashSet<>();
set.add("hello");
set.add("hello"); // 第二次添加返回 false,去重成功

十六、TreeSet 的排序与自定义对象排序

TreeSet 默认按元素自然顺序(通过 compareTo 方法)排序。自定义对象排序需实现 Comparable 接口定义自然顺序,或传入 Comparator 指定排序方式。

// 自定义对象实现 Comparable
class Person implements Comparable<Person> {
    int age;

    public Person(int age) {
        this.age = age;
    }

    @Override
    public int compareTo(Person other) {
        return Integer.compare(this.age, other.age); // 按年龄升序排序
    }
}

TreeSet<Person> set = new TreeSet<>();
set.add(new Person(20));
set.add(new Person(18)); // 按 age 排序插入

网站公告

今日签到

点亮在社区的每一天
去签到