当前位置: 首页 > news >正文

Java 基础高频38问

Java基础高频38问

1. 接口和抽象类的区别

接口和抽象类均用于抽象共性,但设计定位和使用场景差异显著,核心对比如下:

对比维度抽象类(Abstract Class)接口(Interface)
关键字abstract修饰interface修饰
方法特性可包含抽象方法(无实现)和具体方法(有实现);Java 8后支持默认方法(default)和静态方法Java 8前仅含抽象方法(默认public abstract);Java 8后支持默认/静态方法,Java 9支持私有方法
字段特性可定义任意访问修饰符的字段(实例变量、静态变量),字段可修改字段默认public static final(必须是常量),不可修改
实例化能力不能直接实例化,但有构造方法(供子类调用初始化)不能实例化,且无构造方法
继承/实现规则一个类只能继承一个抽象类(单继承)一个类可实现多个接口(多实现);接口可多继承其他接口
设计意图表示“is-a”关系,用于同类事物的共性抽象(如Animal),提供默认行为和状态表示“has-a”能力(如Runnable),用于不同类的行为统一,不关注实现细节
典型场景同类事物的共性封装(如Animal包含eat()具体方法和makeSound()抽象方法)跨类行为约定(如Comparable定义比较能力、Serializable定义序列化能力)

2. == 和 equals() 的区别

==是运算符,equals()Object类方法,二者在比较逻辑和适用场景上完全不同:

对比维度==equals()
适用类型基本类型(intchar等)、引用类型(对象、数组)仅引用类型(对象)
比较逻辑基本类型:比较“值”是否相等;
引用类型:比较“内存地址”是否相同(是否为同一对象)
默认(未重写):同==,比较内存地址;
重写后(如StringInteger):比较“内容”是否相等
重写能力不可重写(运算符无重写概念)可重写(需遵循自反性、对称性、传递性、一致性、非空性)
空指针风险无(如null == null返回true有(若调用者为null,直接抛出NullPointerException,建议用Objects.equals()规避)

示例代码

// 1. 基本类型比较(==)
int a = 10;
int b = 10;
System.out.println(a == b); // true(值相等)// 2. 引用类型比较(==)
String s1 = new String("abc");
String s2 = new String("abc");
System.out.println(s1 == s2); // false(内存地址不同)// 3. equals()比较(重写后)
System.out.println(s1.equals(s2)); // true(内容相等,String重写了equals())// 4. 规避空指针
String s3 = null;
System.out.println(Objects.equals(s3, "abc")); // false(安全不报错)

3. 重载和重写的区别

重载(Overloading)是“同类同名不同参”,重写(Overriding)是“子类重写父类方法”,核心差异如下:

对比维度重载(Overloading)重写(Overriding)
发生范围同一类或父子类之间(水平关系)子类与父类(或实现类与接口)之间(垂直关系)
方法签名方法名相同,参数列表(个数、类型、顺序)不同方法名、参数列表、返回值类型(子类可窄化)完全一致
返回值可任意修改需与父类方法返回值“兼容”(Java 5+支持协变返回,如子类返回值是父类返回值的子类)
访问修饰符可任意修改子类方法权限不能低于父类(如父类public,子类不能是protected
异常抛出可任意修改(新增或删除异常)子类抛出的异常不能比父类更宽泛(只能是父类异常的子类或同级)
静态/非静态静态方法和非静态方法均可重载静态方法不能重写(仅能“隐藏”);非静态方法才能重写
多态类型编译时多态(编译器按参数列表匹配方法)运行时多态(运行时按对象实际类型调用方法)

重载示例

public class MathUtil {// 参数个数不同public int add(int a, int b) { return a + b; }public int add(int a, int b, int c) { return a + b + c; }// 参数类型不同public double add(double a, double b) { return a + b; }
}

重写示例

class Animal {public void makeSound() { System.out.println("动物叫"); }
}class Dog extends Animal {@Override // 重写父类方法,实现不同逻辑public void makeSound() { System.out.println("汪汪叫"); }
}

4. 请简述Java异常体系

Java异常体系基于Throwable类,分为Error(错误)Exception(异常) 两大分支,核心作用是“捕获运行时错误,保证程序健壮性”:

Throwable(顶层类)
├─ Error(错误):JVM层面的严重问题,无法捕获和恢复
│  ├─ OutOfMemoryError(内存溢出错误)
│  ├─ StackOverflowError(栈溢出错误)
│  └─ NoClassDefFoundError(类未找到错误)
└─ Exception(异常):程序可捕获和处理的问题├─ 编译时异常(Checked Exception):编译期强制要求处理(try-catch或throws)│  ├─ IOException(IO流异常)│  ├─ SQLException(数据库操作异常)│  └─ ClassNotFoundException(类未找到异常)└─ 运行时异常(Unchecked Exception):编译期不强制处理,运行时触发├─ NullPointerException(空指针异常)├─ ArrayIndexOutOfBoundsException(数组下标越界异常)├─ ClassCastException(类型转换异常)└─ ArithmeticException(算术异常,如除以0)

关键说明

  • Error:由JVM生成(如内存溢出),程序无法处理,通常直接崩溃;
  • 编译时异常:必须显式处理(否则编译报错),如读取文件时的IOException
  • 运行时异常:多由代码逻辑错误导致(如空对象调用方法),可选择处理或通过代码优化规避。

5. 运行时异常和非运行时异常(编译时异常)

运行时异常(Unchecked)和编译时异常(Checked)是Exception的两大子类,核心区别在“编译要求”和“触发原因”:

对比维度运行时异常(Unchecked Exception)非运行时异常(Checked Exception,编译时异常)
父类继承自RuntimeException直接继承自Exception(非RuntimeException子类)
编译要求编译期不强制处理(无需try-catch或throws)编译期强制处理(不处理则编译报错)
触发原因通常由代码逻辑错误导致(如空指针、数组越界、除以0)由外部环境或资源问题导致(如IO流失败、数据库连接超时、类未找到)
常见示例NullPointerExceptionArrayIndexOutOfBoundsExceptionArithmeticExceptionIOExceptionSQLExceptionClassNotFoundExceptionParseException
处理建议优先通过优化代码逻辑避免(如判空、校验下标、避免除以0)必须显式处理(捕获后修复或向上抛出,让上层调用者处理)

6. 访问修饰符public、private、protected、默认(default)的区别

Java访问修饰符控制类、字段、方法的访问权限,从宽到严依次为public > protected > 默认 > private

修饰符同一类中同一包中(非子类)不同包的子类不同包的非子类
public可访问可访问可访问可访问
protected可访问可访问可访问不可访问
默认(无)可访问可访问不可访问不可访问
private可访问不可访问不可访问不可访问

关键场景

  • private:封装字段,仅本类可见(如Person类的name字段,通过getter/setter访问);
  • 默认修饰符:包内可见,适用于包内组件协作(如工具类内部的辅助方法);
  • protected:同包可见 + 不同包子类可见,适用于父类给子类暴露方法(如Animalmove()方法);
  • public:全局可见,适用于对外提供的接口(如Math类的abs()方法)。

7. 请简述Java 128陷阱

Java中Integer类为优化性能,在**-128~127**范围内创建“缓存对象”,当使用Integer.valueOf(int)或自动装箱时复用缓存,超出范围则创建新对象,这就是“128陷阱”。

核心原理

  • Integer内部维护了一个IntegerCache静态内部类,缓存-128~127Integer对象;
  • 自动装箱(如Integer a = 100)本质是调用Integer.valueOf(100),优先从缓存获取对象;
  • 直接new Integer(100)会跳过缓存,直接创建新对象。

示例代码

// 1. 范围内(-128~127):复用缓存,==返回true
Integer a = 100;
Integer b = 100;
System.out.println(a == b); // true(同一缓存对象)// 2. 范围外(>127):创建新对象,==返回false
Integer c = 200;
Integer d = 200;
System.out.println(c == d); // false(不同对象)// 3. 直接new:不复用缓存
Integer e = new Integer(100);
Integer f = new Integer(100);
System.out.println(e == f); // false

注意:缓存范围可通过JVM参数-XX:AutoBoxCacheMax=<size>修改(如扩大到256),类似缓存机制的类还有Byte(全缓存)、ShortLongCharacter(0~127)。

8. 获取Class对象的三种方式

Class对象是反射的核心,代表类的“元信息”(字段、方法、构造器等),获取方式有三种:

方式1:Class.forName("全类名")

  • 特点:通过“包名+类名”动态加载类,触发类的初始化(执行静态代码块、静态变量赋值);
  • 场景:仅知道类名(如配置文件中配置的类名),如JDBC加载驱动(Class.forName("com.mysql.cj.jdbc.Driver"));
  • 示例
    try {Class<?> clazz = Class.forName("com.test.Person"); // 全类名:包名+类名
    } catch (ClassNotFoundException e) {e.printStackTrace();
    }
    

方式2:类名.class

  • 特点:通过类的class属性静态获取,不触发类的初始化(仅加载类),效率高;
  • 场景:已知类的编译时类型,如String.classint.class(基本类型也有Class对象);
  • 示例
    Class<?> clazz1 = Person.class;
    Class<?> clazz2 = String.class;
    Class<?> clazz3 = int.class; // 基本类型的Class对象
    

方式3:对象.getClass()

  • 特点:通过对象实例调用getClass()获取,不触发类的初始化(对象已创建,类已加载);
  • 场景:已知对象实例,动态获取其所属类,如new Person().getClass()
  • 示例
    Person person = new Person();
    Class<?> clazz = person.getClass();
    

关键结论:同一类在JVM中仅存在一个Class对象,三种方式获取的Class对象是同一实例(clazz1 == clazz2 == clazz3)。

9. 请简述static关键字

static修饰类的成员(字段、方法、代码块)或内部类,表示“属于类,不属于实例”,可直接通过类名访问,无需创建对象。

修饰字段(静态变量)

  • 特点:所有对象共享同一静态变量,存储在JVM的“方法区”,类加载时初始化(仅一次);
  • 场景:存储全局共享数据(如计数器、常量);
  • 示例
    public class Counter {public static int count = 0; // 所有对象共享public Counter() {count++; // 每次创建对象,计数器+1}
    }// 测试:两个对象共享count
    System.out.println(Counter.count); // 0(直接通过类名访问)
    new Counter();
    new Counter();
    System.out.println(Counter.count); // 2
    

修饰方法(静态方法)

  • 特点:属于类,无this指针(不能访问非静态成员),可直接通过类名调用;
  • 场景:工具方法(如Math.abs()Arrays.sort());
  • 注意:静态方法不能重写(仅能“隐藏”,子类静态方法与父类同名时,父类方法被隐藏)。

修饰代码块(静态代码块)

  • 特点:类加载时执行(仅一次),优先级高于构造方法,用于初始化静态变量;
  • 示例
    public class StaticBlock {static {System.out.println("静态代码块执行"); // 类加载时执行}public StaticBlock() {System.out.println("构造方法执行"); // 创建对象时执行}
    }// 测试:先执行静态代码块,再执行构造方法
    new StaticBlock(); 
    // 输出:静态代码块执行 → 构造方法执行
    

10. 请简述final关键字

final表示“不可变”或“不可修改”,可修饰类、方法、字段,核心作用是“限制继承、防止重写、保证变量不可变”:

修饰类

  • 特点:被final修饰的类不能被继承(无子类);
  • 场景:确保类的完整性,不允许被扩展(如StringInteger类,避免破坏原有逻辑);
  • 示例
    final class FinalClass {}
    // class SubClass extends FinalClass {} // 错误:FinalClass不能被继承
    

修饰方法

  • 特点:被final修饰的方法不能被子类重写;
  • 场景:确保方法逻辑不被修改(如父类的核心业务方法);
  • 示例
    class Parent {final public void coreMethod() { System.out.println("父类核心方法"); }
    }class Child extends Parent {// @Override // 错误:coreMethod()不能被重写// public void coreMethod() {}
    }
    

修饰字段

  • 特点:必须“显式初始化”(声明时、构造方法中、静态代码块中),初始化后不可修改;
    • 基本类型:值不可变;
    • 引用类型:引用地址不可变(但对象内容可修改);
  • 场景:存储常量(如public static final int MAX_AGE = 120);
  • 示例
    public class FinalField {final int num1 = 10; // 声明时初始化final int num2; // 构造方法中初始化public FinalField(int num2) {this.num2 = num2; // 必须初始化,否则编译报错}// 引用类型:地址不可变,内容可修改final List<String> list = new ArrayList<>();public void addElement() {list.add("a"); // 允许(内容可修改)// list = new LinkedList<>(); // 错误(地址不可变)}
    }
    

11. 请简述this关键字

this代表“当前对象”,即调用当前方法或字段的对象,仅在非静态方法、构造方法中使用,核心作用:

1. 区分局部变量与成员变量(重名时)

当方法参数或局部变量与成员变量同名时,用this.成员变量表示成员变量:

public class Person {private String name;// 构造方法:参数name与成员变量重名public Person(String name) {this.name = name; // this.name:成员变量;name:参数}public void setName(String name) {this.name = name; // 同上}
}

2. 调用本类的其他构造方法(this()

在构造方法中,用this(参数)调用本类的其他构造方法,必须放在构造方法第一行

public class Student {private String name;private int age;// 无参构造:调用有参构造初始化默认值public Student() {this("未知", 0); // 调用Student(String, int)}// 有参构造public Student(String name, int age) {this.name = name;this.age = age;}
}

3. 传递当前对象

将当前对象作为参数传递给其他方法,或作为返回值实现链式调用:

public class User {// 传递当前对象给其他方法public void sendToService() {UserService service = new UserService();service.process(this); // this:当前User对象}// 返回当前对象(链式调用)public User setName(String name) {this.name = name;return this; // 支持链式调用:new User().setName("张三").setAge(20)}
}

注意this不能在静态方法中使用(静态方法属于类,无“当前对象”)。

12. 请简述构造器的特点

构造器(Constructor)是类的特殊方法,用于创建对象并初始化成员变量,与类名同名,无返回值,在new对象时自动调用:

  1. 名称与类名完全一致:如Person类的构造器名为Person(),不能自定义其他名称(若加void则变为普通方法);

    public class Person {public Person() {} // 正确:构造器// public void Person() {} // 错误:普通方法,不是构造器
    }
    
  2. 无返回值:不能声明返回值类型(包括void),若加返回值则变为普通方法;

  3. 自动调用:仅在new对象时自动调用,不能手动调用(如person.Person()报错);

    Person person = new Person(); // new时自动调用Person()构造器
    
  4. 默认构造器:若类中未显式定义构造器,编译器会自动生成“无参默认构造器”;若显式定义了构造器,默认构造器消失(需手动定义才能使用);

    public class Car {// 未显式定义构造器:编译器生成无参默认构造器 public Car() {}
    }public class Car {public Car(String brand) {} // 显式定义有参构造器,默认无参构造器消失// public Car() {} // 若需无参构造器,需手动定义
    }
    
  5. 支持重载:一个类可有多个构造器(参数列表不同),用于不同初始化场景;

    public class Phone {private String brand;private int price;public Phone() { this.brand = "未知"; } // 无参构造public Phone(String brand) { this.brand = brand; } // 单参构造public Phone(String brand, int price) { this.brand = brand; this.price = price; } // 双参构造
    }
    
  6. 子类构造器默认调用父类无参构造器:子类构造器第一行默认隐含super(),调用父类无参构造器;若父类无无参构造器,子类必须显式调用父类有参构造器(super(参数)),否则编译报错;

    class Parent {public Parent(String name) {} // 父类显式定义有参构造器,无参构造器消失
    }class Child extends Parent {public Child() {super("父类名称"); // 必须显式调用父类有参构造器,否则编译报错}
    }
    

13. 常见的集合底层实现

Java集合框架分为Collection(单元素)和Map(键值对),底层实现决定其性能特性:

Collection接口下的集合

集合类底层实现核心特点适用场景
ArrayList动态数组(Object[])查询快(O(1))、插入/删除慢(O(n))、线程不安全、初始容量10、1.5倍扩容查询频繁,插入/删除少(如列表展示)
LinkedList双向链表查询慢(O(n))、插入/删除快(O(1))、线程不安全、实现Deque(支持队列/栈)插入/删除频繁(如队列、栈)
HashSet基于HashMap(键存储)无序、不可重复、线程不安全、查询/插入/删除O(1)(无哈希冲突时)去重、无序存储(如唯一ID)
TreeSet基于TreeMap(红黑树)有序(自然排序/自定义排序)、不可重复、线程不安全、查询/插入O(log n)有序、去重存储(如按价格排序)
LinkedHashSet基于LinkedHashMap有序(插入顺序)、不可重复、线程不安全、效率略低于HashSet去重、保留插入顺序(如历史记录)

Map接口下的集合

集合类底层实现核心特点适用场景
HashMap(Java8+)数组(哈希桶)+ 链表/红黑树无序、键不可重复、值可重复、线程不安全、桶内元素≤8为链表,≥8为红黑树键值对存储,查询频繁(如缓存)
TreeMap红黑树有序(键排序)、键不可重复、值可重复、线程不安全、查询/插入O(log n)有序键的键值对(如按日期排序)
LinkedHashMap哈希表+双向链表有序(插入/访问顺序)、键不可重复、值可重复、线程不安全保留顺序的键值对(如LRU缓存)
Hashtable数组(哈希桶)+ 链表无序、键不可重复、值可重复、线程安全(方法加synchronized)、效率低低并发键值对(已被ConcurrentHashMap替代)
ConcurrentHashMap(Java8+)数组+链表/红黑树+CAS+synchronized无序、键不可重复、值可重复、线程安全(锁粒度为哈希桶)、效率高高并发键值对(如多线程缓存)

14. List、Set、Map之间的区别

List、Set属于Collection(单元素),Map属于独立的Map(键值对),核心区别如下:

对比维度ListSetMap
核心定义有序、可重复的单元素集合无序(除LinkedHashSet)、不可重复的单元素集合无序(除TreeMap/LinkedHashMap)、键不可重复、值可重复的键值对集合
存储结构线性结构(数组/链表)哈希表或红黑树哈希表或红黑树(键),值关联到键
核心方法按索引操作(get(int)add(int, E));支持重复添加无索引操作;添加重复元素返回false(去重)按键操作(put(K,V)get(K));同一键重复put覆盖值
是否允许null允许(ArrayList/LinkedList可存多个nullHashSet允许一个nullTreeSet不允许nullHashMap允许一个null键/多个null值;Hashtable不允许null
遍历方式迭代器、for-each、普通for循环(按索引)迭代器、for-eachkeySet()(遍历键)、values()(遍历值)、entrySet()(遍历键值对)
常见实现类ArrayListLinkedListHashSetTreeSetLinkedHashSetHashMapTreeMapLinkedHashMapConcurrentHashMap
适用场景按顺序存储、允许重复(如购物车、列表展示)去重、无序存储(如唯一ID、标签)键值对映射(如缓存、用户信息)

15. 重写equals()方法为什么要重写hashCode()

核心原则(Java规范)

如果两个对象通过equals()比较返回true,则它们的hashCode()必须相等;反之,hashCode()相等的两个对象,equals()不一定返回true(哈希冲突)。

若只重写equals()不重写hashCode(),会违反该原则,导致HashMapHashSet等哈希表集合出现逻辑错误。

问题根源

Object类的默认hashCode()返回“对象内存地址相关值”,若只重写equals(),两个equals()true的对象(内容相同),默认hashCode()可能不同,导致哈希表集合无法识别它们是“同一对象”。

错误示例(HashSet去重失效)

class Person {private String id;public Person(String id) { this.id = id; }// 只重写equals():id相同则认为相等@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Person person = (Person) o;return Objects.equals(id, person.id);}// 未重写hashCode():默认返回内存地址值
}// 测试:两个id相同的Person对象
Person p1 = new Person("1001");
Person p2 = new Person("1001");System.out.println(p1.equals(p2)); // true(内容相同)
System.out.println(p1.hashCode() == p2.hashCode()); // false(默认hashCode()不同)// 存入HashSet:本应去重,却存入两个对象
HashSet<Person> set = new HashSet<>();
set.add(p1);
set.add(p2);
System.out.println(set.size()); // 2(错误:应为1)

解决方法:同时重写equals()和hashCode()

重写hashCode()时,需基于equals()比较的字段(如id)计算哈希值,确保“相等对象哈希值相等”:

class Person {private String id;public Person(String id) { this.id = id; }@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Person person = (Person) o;return Objects.equals(id, person.id);}// 基于id计算hashCode(),确保equals()为true的对象hashCode()相等@Overridepublic int hashCode() {return Objects.hash(id); // 用Objects.hash()避免空指针}
}// 重新测试:去重成功
HashSet<Person> set = new HashSet<>();
set.add(p1);
set.add(p2);
System.out.println(set.size()); // 1(正确)

总结

  • 若类的对象需存入HashMapHashSet等哈希表集合,必须同时重写equals()hashCode()
  • 重写hashCode()的核心:基于equals()比较的字段计算哈希值,遵循“相等对象哈希值相等”原则。

16. & 和 &&的区别

&是“按位与”运算符,也可用于“逻辑与”;&&是“短路逻辑与”运算符,仅用于逻辑判断,核心区别:

对比维度&&&
运算符类型按位运算符 + 逻辑运算符仅逻辑运算符(短路与)
适用场景1. 按位运算:对整数二进制位进行“与”操作;
2. 逻辑运算:判断多个布尔表达式的“与”结果
仅逻辑运算:判断多个布尔表达式的“与”结果(利用短路特性优化性能)
短路特性无短路:无论第一个表达式是否为false,都会执行所有后续表达式有短路:若第一个表达式为false,直接返回false,不执行后续表达式
返回值按位运算:返回整数(二进制位“与”结果);
逻辑运算:返回布尔值
仅返回布尔值(true/false

示例1:逻辑运算(短路特性对比)

int a = 5;
// &:无短路,执行所有表达式
if (a > 10 & ++a > 5) { // a>10为false,但仍执行++a// 不进入
}
System.out.println(a); // 6(a被自增)// &&:有短路,不执行后续表达式
int b = 5;
if (b > 10 && ++b > 5) { // b>10为false,直接返回false,不执行++b// 不进入
}
System.out.println(b); // 5(b未被自增)

示例2:按位运算(仅&可用)

// 按位与:二进制位都为1时,结果为1
int x = 3; // 二进制:0011
int y = 5; // 二进制:0101
int result = x & y; // 二进制:0001 → 十进制1
System.out.println(result); // 1

17. String s = “123”;这个语句有几个对象产生?

需分两种场景,核心取决于“字符串常量池是否已有"123"”:

场景1:字符串常量池中没有"123"

  • 过程
    1. JVM检查字符串常量池(方法区的一部分),若没有"123",则创建一个"123"对象存入常量池;
    2. 变量s引用常量池中的"123"对象,不创建新对象;
  • 结论:产生1个对象(常量池中的"123")。

场景2:字符串常量池中已有"123"

  • 过程
    1. JVM检查字符串常量池,发现已有"123"对象;
    2. 变量s直接引用常量池中的已有对象,不创建任何新对象;
  • 结论:产生0个对象

关键背景:字符串常量池

Java为优化字符串性能,设计了“字符串常量池”:

  • ""定义字符串(如"123")时,JVM优先从常量池查找,有则复用,无则创建后存入;
  • new String("123")时,会创建两个对象(若常量池无"123"):常量池中的"123" + 堆内存中的String对象。

示例验证

// 1. 第一次定义:常量池无"123",创建1个对象
String s1 = "123"; // 2. 第二次定义:常量池有"123",复用,创建0个对象
String s2 = "123"; 
System.out.println(s1 == s2); // true(引用同一对象)// 3. new String("123"):创建2个对象(常量池1个 + 堆1个)
String s3 = new String("123"); 
System.out.println(s1 == s3); // false(s1指向常量池,s3指向堆)

结论String s = "123";产生的对象数为0个或1个,取决于字符串常量池中是否已有"123"

18. 面向对象的三大特性是什么?请简单介绍一下!

面向对象(OOP)的三大特性是封装(Encapsulation)继承(Inheritance)多态(Polymorphism),是OOP编程的核心思想:

1. 封装(Encapsulation)

定义

将类的“字段(属性)”和“方法(行为)”封装在一起,隐藏内部实现细节,仅对外暴露可控的访问接口(如getter/setter),限制外部对内部数据的直接操作。

核心目的
  • 保护数据安全:防止外部随意修改内部字段,确保数据合法性(如年龄不能为负数);
  • 隐藏实现细节:外部只需关注“如何使用”,无需关注“如何实现”(如调用ArrayList.add(),无需知道底层数组扩容逻辑)。
示例
public class Person {// 私有字段:隐藏内部数据private String name;private int age;// getter:允许外部获取字段值public String getName() { return name; }// setter:控制外部修改逻辑(校验年龄合法性)public void setAge(int age) {if (age < 0 || age > 150) {throw new IllegalArgumentException("年龄必须在0~150之间");}this.age = age;}
}// 使用:通过setter控制修改,通过getter获取
Person person = new Person();
person.setAge(20); // 合法
// person.setAge(200); // 抛出异常,阻止非法修改

2. 继承(Inheritance)

定义

子类(Subclass)继承父类(Superclass)的字段和方法,同时可添加新字段/方法或重写父类方法,实现“代码复用”和“层次化设计”。

核心目的
  • 代码复用:父类的通用逻辑(如Animaleat()方法)无需在子类中重复编写;
  • 层次化设计:建立类之间的“is-a”关系(如Dog is a Animal),使代码结构更清晰。
关键规则
  • Java支持单继承(一个子类只能继承一个父类),但支持多实现(一个类可实现多个接口);
  • 子类继承父类的非private成员,private成员需通过父类的public方法访问;
  • 子类构造器默认调用父类的无参构造器(super()),若父类无无参构造器,需显式调用父类有参构造器。
示例
// 父类:通用逻辑
class Animal {public void eat() { System.out.println("动物吃东西"); }
}// 子类:继承父类,复用eat(),添加新方法
class Dog extends Animal {public void bark() { System.out.println("狗汪汪叫"); } // 新增方法@Override // 重写父类方法,修改逻辑public void eat() { System.out.println("狗吃骨头"); }
}// 使用:子类可调用继承和新增的方法
Dog dog = new Dog();
dog.eat(); // 重写后的方法:狗吃骨头
dog.bark(); // 新增方法:狗汪汪叫

3. 多态(Polymorphism)

定义

同一方法(或接口)在不同对象上有不同的实现,即“一个接口,多种实现”,分为编译时多态(重载)运行时多态(重写)

核心目的
  • 提高代码扩展性:新增子类时,无需修改原有代码(如新增Cat类,无需修改Animal的调用逻辑);
  • 降低耦合:调用者只需面向父类或接口编程,无需关注具体子类。
实现条件
  • 继承:子类继承父类(或实现接口);
  • 重写:子类重写父类方法(或实现接口方法);
  • 向上转型:父类引用指向子类对象(如Animal animal = new Dog())。
示例
// 父类
class Animal {public void makeSound() { System.out.println("动物叫"); }
}// 子类1
class Dog extends Animal {@Overridepublic void makeSound() { System.out.println("汪汪叫"); }
}// 子类2
class Cat extends Animal {@Overridepublic void makeSound() { System.out.println("喵喵叫"); }
}// 测试:运行时多态
public class Test {public static void main(String[] args) {Animal animal1 = new Dog(); // 向上转型Animal animal2 = new Cat(); // 向上转型animal1.makeSound(); // 运行时调用Dog的方法:汪汪叫animal2.makeSound(); // 运行时调用Cat的方法:喵喵叫}
}

19. Java语言有几种基本类型,分别是什么?

Java的基本类型(Primitive Type)共8种,分为“数值类型”和“布尔类型”,直接存储值(存储在栈内存,效率高),对应的包装类属于引用类型:

类型分类基本类型关键字占用字节数取值范围默认值包装类
整数类型字节型byte1字节(8位)−27-2^727 ~ 27−12^7-1271(-128 ~ 127)0Byte
短整型short2字节(16位)−215-2^{15}215 ~ 215−12^{15}-12151(-32768 ~ 32767)0Short
整型int4字节(32位)−231-2^{31}231 ~ 231−12^{31}-12311(-2147483648 ~ 2147483647)0Integer
长整型long8字节(64位)−263-2^{63}263 ~ 263−12^{63}-12631(-9223372036854775808 ~ 9223372036854775807)0LLong
浮点类型单精度float4字节(32位)约±3.4e38(精度6~7位小数)0.0fFloat
双精度double8字节(64位)约±1.8e308(精度15~17位小数)0.0dDouble
字符类型字符型char2字节(16位)\u0000(0) ~ \uffff(65535)\u0000Character
布尔类型布尔型boolean未明确规定(JVM中通常1字节或1位)true/falsefalseBoolean

关键说明

  • 整数类型中,long需加L标识(如100L),否则视为int
  • 浮点类型中,float需加f标识(如3.14f),doubled可省略(如3.14);
  • char存储Unicode字符,如'A'(ASCII码65)、'中'(Unicode码4E2D);
  • boolean仅表示真/假,不能用0/1代替(与C/C++不同)。

20. int[]类型是不是基本类型?

不是int[]是“数组类型”,属于引用类型,而非基本类型。

核心原因

  1. 存储方式不同

    • 基本类型(如int):直接存储值,存储在栈内存;
    • 数组类型(如int[]):存储“数组对象在堆内存中的地址”(引用),数组元素(int值)存储在堆内存。
  2. 继承关系

    • 所有数组类型都间接继承自Object类(引用类型的顶层类),可调用Object的方法(如toString()hashCode());
    • 基本类型不继承任何类,也没有方法(需通过包装类调用)。
  3. 初始化方式

    • 基本类型:直接赋值(如int a = 10),无需new
    • 数组类型:需通过new创建(如int[] arr = new int[5]),或字面量初始化(如int[] arr = {1,2,3}),本质是创建数组对象。

示例验证

// 1. 数组是引用类型:存储引用地址
int[] arr1 = new int[3];
int[] arr2 = arr1; // arr2引用arr1指向的同一数组对象arr1[0] = 10;
System.out.println(arr2[0]); // 10(修改arr1的元素,arr2也受影响)// 2. 数组继承自Object:可调用Object方法
int[] arr = {1,2,3};
System.out.println(arr.toString()); // [I@1b6d3586(默认toString()格式)
System.out.println(arr instanceof Object); // true(数组是Object的子类)// 3. 存储位置不同
int a = 5; // 栈内存存储值5
int[] arr3 = {5}; // 栈存储引用,堆存储元素5

结论int[]是引用类型,其元素int是基本类型,但数组本身属于引用类型。

21. 什么是自动拆箱、什么是自动装箱

自动装箱(Autoboxing)和自动拆箱(Unboxing)是Java 5引入的特性,用于基本类型与包装类之间的自动转换,简化代码:

自动装箱(Autoboxing):基本类型 → 包装类

定义

基本类型的值自动转换为对应的包装类对象(如intIntegerdoubleDouble)。

触发场景
  • 基本类型值赋值给包装类变量;
  • 基本类型值作为参数传入需要包装类的方法(如List.add(Integer))。
底层实现

通过包装类的valueOf()方法实现(如intInteger调用Integer.valueOf(int)),且会复用缓存对象(见“128陷阱”)。

示例
// 1. 基本类型int → 包装类Integer
Integer num1 = 10; // 等价于:Integer num1 = Integer.valueOf(10);// 2. 基本类型作为参数传入List(需包装类)
List<Integer> list = new ArrayList<>();
list.add(20); // 自动装箱:20(int)→ Integer(20)

自动拆箱(Unboxing):包装类 → 基本类型

定义

包装类对象自动转换为对应的基本类型值(如IntegerintDoubledouble)。

触发场景
  • 包装类对象赋值给基本类型变量;
  • 包装类对象作为参数传入需要基本类型的方法(如Math.abs(int));
  • 包装类对象参与基本类型的运算(如+-)。
底层实现

通过包装类的xxxValue()方法实现(如Integerint调用intValue()Doubledouble调用doubleValue())。

示例
// 1. 包装类Integer → 基本类型int
Integer num2 = Integer.valueOf(30);
int a = num2; // 等价于:int a = num2.intValue();// 2. 包装类参与基本类型运算
Integer num3 = 40;
int sum = num3 + 10; // 自动拆箱:num3→40(int),再与10相加

注意事项

  1. 空指针风险:若包装类对象为null,自动拆箱会抛出NullPointerException

    Integer num = null;
    // int a = num; // 错误:NullPointerException
    
  2. 缓存机制:自动装箱时,包装类会复用缓存对象(如Integer缓存-128~127),自动拆箱无缓存影响;

    Integer a = 100;
    Integer b = 100;
    System.out.println(a == b); // true(复用缓存)
    
  3. 性能影响:频繁自动装箱/拆箱会产生额外对象开销(如循环中大量Integer运算),建议优先使用基本类型。

22. 位运算有哪几种?分别是什么意思

位运算对整数的二进制位直接操作,效率远高于算术运算,Java支持7种位运算:

位运算符名称运算规则(a、b为二进制位,1真0假)示例(a=3(0011)、b=5(0101))
&按位与两位都为1时,结果为1;否则为0a & b = 0011 & 0101 = 0001(1)
``按位或两位有一个为1时,结果为1;否则为0
^按位异或两位不同时,结果为1;相同为0a ^ b = 0011 ^ 0101 = 0110(6)
~按位非对单个位取反(1变0,0变1)~a = ~0011 = 1100(十进制-4)
<<左移整体左移n位,右侧补0,等价于×2n2^n2na << 2 = 0011 << 2 = 1100(12)
>>右移(算术)整体右移n位,左侧补符号位,等价于÷2n2^n2n(向下取整)a >> 1 = 0011 >> 1 = 0001(1)
>>>无符号右移整体右移n位,左侧补0,结果非负-3 >>> 1 = 2147483646

关键说明

  1. 按位非(~):Java中整数为32位(int)或64位(long),按位非会对所有位(包括符号位)取反,公式:~x = -(x + 1)(如~3 = -4);

  2. 左移(<<):左移n位时,高位超出范围会被舍弃,常用于正数快速乘2n2^n2n(如1 << 30 = 2302^{30}230);

  3. 右移(>>)vs 无符号右移(>>>)

    • 正数:两者结果相同(左侧补0);
    • 负数:>>左侧补1(保持负数),>>>左侧补0(转为正数),如-2 >> 1 = -1-2 >>> 1 = 2147483647

常见应用

  • 判断奇偶x & 1 == 1(奇数),x & 1 == 0(偶数);
  • 交换两数a = a ^ b; b = a ^ b; a = a ^ b;(无需临时变量);
  • 快速乘除x << n(×2n2^n2n),x >> n(÷2n2^n2n);
  • 清零x & 0(任何数与0按位与,结果为0)。

23. String类的常用函数有哪些?列举十种

String是Java中最常用的类,底层基于char[](Java 8)或byte[](Java 9+)实现,提供丰富的字符串操作方法,以下是十种常用函数:

方法签名功能描述示例
int length()返回字符串长度(字符个数)"abc".length() → 3
char charAt(int index)返回指定索引处的字符(索引从0开始)"abc".charAt(1) → ‘b’
boolean equals(Object anObject)比较字符串内容是否相等(区分大小写)"abc".equals("ABC") → false
boolean equalsIgnoreCase(String s)比较字符串内容是否相等(不区分大小写)"abc".equalsIgnoreCase("ABC") → true
String concat(String str)连接指定字符串到末尾,返回新字符串"a".concat("b") → “ab”
boolean contains(CharSequence s)判断是否包含指定字符序列"abcde".contains("bc") → true
int indexOf(String str)返回指定字符串第一次出现的索引,未找到返回-1"abcabc".indexOf("ab") → 0
int lastIndexOf(String str)返回指定字符串最后一次出现的索引,未找到返回-1"abcabc".lastIndexOf("ab") → 3
String substring(int begin)从begin索引(含)截取到末尾,返回新字符串"abcde".substring(2) → “cde”
String substring(int begin, int end)从begin(含)截取到end(不含),返回新字符串"abcde".substring(1,3) → “bc”
String trim()去除两端空白字符(空格、制表符等),返回新字符串" abc ".trim() → “abc”
String toUpperCase()转为大写,返回新字符串"abc".toUpperCase() → “ABC”
String toLowerCase()转为小写,返回新字符串"ABC".toLowerCase() → “abc”
boolean startsWith(String prefix)判断是否以指定前缀开头"abcde".startsWith("ab") → true
boolean endsWith(String suffix)判断是否以指定后缀结尾"abcde".endsWith("de") → true

关键说明String是不可变的,所有修改方法(如concat()substring())都会返回新的String对象,原对象不变。

24. String、StringBuffer、StringBuilder的区别

三者均用于处理字符串,核心区别在可变性、线程安全性、性能

对比维度StringStringBufferStringBuilder
可变性不可变(底层final char[]/byte[],修改时创建新对象)可变(底层char[],修改时直接操作数组)可变(同StringBuffer,底层char[]
线程安全性线程安全(不可变对象天然安全)线程安全(方法加synchronized锁)线程不安全(方法无锁)
性能最低(频繁修改创建大量新对象,GC开销大)中等(锁机制导致性能损耗)最高(无锁,比StringBuffer高10%~50%)
底层实现Java 8:final char[];Java 9+:final byte[](按编码优化)char[](无final,可扩容)同StringBuffer,char[]
扩容机制无扩容(修改时创建新对象)默认初始容量16,扩容时按2倍+2扩容(如16→34→70),需复制数组同StringBuffer
适用场景字符串不频繁修改(如常量定义、少量拼接)多线程环境下频繁修改(如日志输出、多线程缓存)单线程环境下频繁修改(如字符串拼接、JSON构建)

示例对比

// 1. String:频繁修改效率低
String s = "a";
for (int i = 0; i < 1000; i++) {s += "b"; // 每次创建新对象
}// 2. StringBuffer:多线程安全
StringBuffer sb = new StringBuffer("a");
for (int i = 0; i < 1000; i++) {sb.append("b"); // 直接操作数组
}// 3. StringBuilder:单线程效率最高
StringBuilder sb2 = new StringBuilder("a");
for (int i = 0; i < 1000; i++) {sb2.append("b"); // 无锁,效率最优
}

结论:单线程优先用StringBuilder,多线程用StringBuffer,不修改字符串用String

25. String字符串的不可变是指哪里不可变?

String的不可变是指字符串对象的内容(字符序列)不可变,一旦创建String对象,其包含的字符序列无法被修改(增删、修改字符),任何“修改”操作都会创建新的String对象。

底层实现原因(Java 8及之前)

String类的底层存储依赖private final char value[]数组,两个关键修饰符确保不可变:

  1. privatevalue数组是私有成员,外部类无法直接访问或修改;
  2. finalvalue数组的引用(地址)不可变,即无法指向新的数组对象。
public final class String {private final char value[]; // 私有+final,确保数组引用不可变// 构造方法:复制参数数组,避免外部修改原数组public String(char value[]) {this.value = Arrays.copyOf(value, value.length);}// 示例:"修改"方法实际返回新对象public String concat(String str) {char buf[] = new char[value.length + str.value.length];System.arraycopy(value, 0, buf, 0, value.length);System.arraycopy(str.value, 0, buf, value.length, str.value.length);return new String(buf); // 返回新对象,原value数组未修改}
}

不可变的具体表现

  1. 无法修改字符:没有方法能直接修改value数组中的字符(如setCharAt()),所有“修改”方法(concat()replace())都会创建新对象;

    String s = "abc";
    s.concat("d"); // 返回新对象"abcd",原s仍为"abc"
    System.out.println(s); // "abc"(原对象未修改)
    
  2. 无法改变长度:字符串长度由value数组长度决定,value引用不可变且数组长度固定,因此字符串长度也不可变;

    String s = "a";
    String s2 = s + "b"; // s2是新对象(长度2),s仍为"a"(长度1)
    
  3. 哈希值缓存:由于String不可变,其哈希值(hashCode())在第一次计算后会被缓存到private int hash字段,后续调用直接返回缓存值,提高效率(若可变,哈希值会变化,导致哈希表集合出错)。

26. 子类实例初始化是否会触发父类实例初始化?

,子类实例初始化时,会先触发父类的实例初始化(执行父类的构造器),再执行子类的实例初始化,这是Java继承机制的核心规则。

核心流程(子类实例化步骤)

  1. 加载子类时,先加载父类(JVM类加载机制:父类优先加载);
  2. 执行子类构造器前,先执行父类的构造器(默认调用父类无参构造器,通过super()实现);
  3. 父类实例初始化完成后,再执行子类的实例初始化(子类构造器中的逻辑)。

详细步骤(以Child extends Parent为例)

class Parent {// 父类实例变量private String parentName = "父类名称";// 父类构造器(实例初始化核心逻辑)public Parent() {System.out.println("父类构造器执行");}
}class Child extends Parent {// 子类实例变量private String childName = "子类名称";// 子类构造器public Child() {// 隐含super(),调用父类无参构造器(若未显式调用)System.out.println("子类构造器执行");}
}// 测试:创建子类实例
public class Test {public static void main(String[] args) {new Child();// 输出顺序:// 父类构造器执行 → 子类构造器执行}
}

关键说明

  1. super()的作用:子类构造器的第一行默认隐含super(),用于调用父类的无参构造器;若父类无无参构造器,子类必须显式调用父类的有参构造器(super(参数)),否则编译报错;

    class Parent {public Parent(String name) { System.out.println("父类有参构造器"); } // 无无参构造器
    }class Child extends Parent {public Child() {super("父类名称"); // 必须显式调用父类有参构造器,否则编译报错System.out.println("子类构造器");}
    }
    
  2. 实例变量初始化时机:父类的实例变量在父类构造器执行前初始化,子类的实例变量在子类构造器执行前初始化;

    class Parent {private String parentVar = "父类变量初始化"; // 先执行public Parent() {System.out.println(parentVar); // 后执行:输出“父类变量初始化”}
    }
    

结论:子类实例初始化必然触发父类实例初始化,遵循“父类优先”原则,确保父类的实例变量和构造器逻辑先执行,为子类提供正确的继承基础。

27. boolean类型占多少位?为什么?

Java语言规范未明确规定boolean类型的占用位数,但在JVM中,boolean类型通常有两种存储方式:

1. 单独使用时(如boolean b = true

  • 占用1字节(8位)
  • 原因:JVM的内存分配最小单位是“字节”(1字节),无法直接分配1位内存,为了简化内存管理,将boolean按1字节存储。

2. 数组中使用时(如boolean[] arr = new boolean[10]

  • 占用1位
  • 原因:JVM对boolean数组做了优化,通过“位压缩”存储(1个字节存储8个boolean值),减少内存占用(若按1字节/个存储,1000个元素需1000字节,优化后仅需125字节)。

关键说明

  • Java语言层面不允许直接操作boolean的二进制位(无位运算支持),仅支持true/false取值;
  • 不同JVM实现可能存在差异,但主流JVM(如HotSpot)均遵循上述存储规则;
  • boolean类型的包装类Boolean,由于是对象,存储在堆内存中,占用空间远大于1字节(包括对象头、实例变量等)。

结论boolean类型的占用位数取决于使用场景,单独使用时1字节,数组中1位,核心是JVM为平衡“内存效率”和“管理复杂度”的优化结果。

28. instanceof关键字的作用是什么?

instanceof是Java的二元运算符,用于判断一个对象是否为某个类(或接口)的实例,返回值为boolean类型(true/false),核心作用是“类型校验”,避免类型转换异常。

核心语法

对象 instanceof/接口

关键规则

  1. 若对象为nullinstanceof返回falsenull不是任何类的实例);
  2. 若对象是“类的实例”或“子类的实例”,返回true
  3. 若对象是“接口的实现类的实例”,返回true
  4. 基本类型不能使用instanceof(如1 instanceof int报错),需通过包装类(如Integer)使用。

示例代码

// 1. 对象是类的实例
class Animal {}
class Dog extends Animal {}
Animal animal = new Dog();System.out.println(animal instanceof Animal); // true(animal是Animal的实例)
System.out.println(animal instanceof Dog);    // true(animal是Dog的实例,Dog是Animal的子类)// 2. 对象是接口的实现类实例
interface Runnable {}
class MyRunnable implements Runnable {}
Runnable runnable = new MyRunnable();
System.out.println(runnable instanceof Runnable); // true(runnable是Runnable的实现类实例)// 3. 对象为null
Animal nullAnimal = null;
System.out.println(nullAnimal instanceof Animal); // false(null不是任何类的实例)// 4. 基本类型不能使用instanceof
// int a = 1;
// System.out.println(a instanceof int); // 错误:基本类型不能用instanceof
Integer b = 1;
System.out.println(b instanceof Integer); // true(包装类可以使用)

典型应用场景

  1. 避免类型转换异常:在强制类型转换前,用instanceof校验类型,确保转换安全;

    Object obj = new String("abc");
    if (obj instanceof String) {String str = (String) obj; // 安全转换,无异常System.out.println(str.length());
    }
    
  2. 多态场景下判断对象实际类型:在父类引用指向子类对象时,用instanceof判断对象的实际类型,执行不同逻辑;

    public void makeSound(Animal animal) {if (animal instanceof Dog) {System.out.println("汪汪叫");} else if (animal instanceof Cat) {System.out.println("喵喵叫");}
    }
    

注意instanceof不支持“泛型擦除后的类型校验”(如List<String> list = new ArrayList<>(); list instanceof List<String>报错),需通过其他方式(如反射)校验泛型类型。

29. 基本类型的强制类型转换是否会丢失精度?引用类型的强制类型转换需要注意什么?

一、基本类型的强制类型转换:可能丢失精度

基本类型强制转换((目标类型)变量)是否丢失精度,核心取决于源类型与目标类型的取值范围,仅当“目标类型范围 < 源类型范围”时,才可能丢失精度或产生错误值。

1. 不会丢失精度的场景(目标类型范围 ≥ 源类型范围)

当目标类型能完全覆盖源类型的取值范围时,转换后值不变,无精度丢失:

  • 整数类型:小范围→大范围(byteshortintlong),如byte b = 10; int i = (int)b;i=10,无丢失);
  • 浮点类型:floatdoublefloat精度67位,`double`精度1517位,可完整保留float值);
  • 字符类型:charintchar取值范围065535,完全包含在`int`的02147483647范围内),如char c = 'A'; int i = (int)c;i=65,无丢失)。
2. 可能丢失精度的场景(目标类型范围 < 源类型范围)

当源类型值超出目标类型范围时,会发生“截断”或“溢出”,导致精度丢失或值错误:

  • 整数→整数:大范围→小范围,高位被截断,如int i = 300; byte b = (byte)i;byte范围-128~127,300超出范围,b=-56,值错误);
  • 浮点→整数:小数部分被舍弃(仅保留整数部分),如double d = 3.9; int i = (int)d;i=3,小数部分丢失);若浮点值超出整数范围,结果为整数类型的极值,如double d = 2147483648.0; int i = (int)d;int最大值2147483647,i=-2147483648,溢出错误);
  • 双精度→单精度doublefloat,若double值超出float范围(±3.4e38),结果为Infinity(无穷大),如double d = 1e40; float f = (float)d;f=Infinity,精度丢失)。
示例代码
// 1. 整数大范围→小范围:值错误
int intNum = 200;
byte byteNum = (byte)intNum;
System.out.println(byteNum); // -56(200超出byte范围,高位截断)// 2. 浮点→整数:小数部分丢失
double doubleNum = 5.8;
int intNum2 = (int)doubleNum;
System.out.println(intNum2); // 5(小数部分0.8丢失)// 3. double→float:超出范围,结果为无穷大
double bigDouble = 1e40;
float floatNum = (float)bigDouble;
System.out.println(floatNum); // Infinity(无穷大)

二、引用类型的强制类型转换:需注意类型兼容性

引用类型强制转换是“将父类引用转为子类引用”或“接口引用转为实现类引用”,核心要求是类型兼容(即对象的实际类型是目标类型或其子类型),否则会抛出ClassCastException(类型转换异常)。

1. 核心规则
  • 向上转型(自动转换,无需强制):父类引用指向子类对象(如Animal animal = new Dog();),天然兼容,无需强制转换,可直接调用父类的方法;
  • 向下转型(需强制转换):子类引用指向父类引用(如Dog dog = (Dog)animal;),仅当父类引用的“实际对象类型”是子类时才合法,否则报错;
  • 不兼容类型转换:无继承/实现关系的类之间转换(如String str = (String)new Animal();),编译直接报错。
2. 注意事项
(1)先通过instanceof校验类型,避免ClassCastException

向下转型前,必须用instanceof判断父类引用的实际对象类型是否为目标子类,确保转换安全:

class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}public class Test {public static void main(String[] args) {Animal animal = new Dog(); // 向上转型:Animal引用指向Dog对象// 错误示例:直接向下转为Cat,实际对象是Dog,抛出ClassCastException// Cat cat = (Cat)animal; // 正确示例:先通过instanceof校验if (animal instanceof Dog) {Dog dog = (Dog)animal; // 安全转换,无异常System.out.println("转换成功:Animal→Dog");}if (animal instanceof Cat) {Cat cat = (Cat)animal; } else {System.out.println("实际对象不是Cat,无法转换");}}
}
(2)接口引用转实现类引用,需确保实际对象是实现类

接口引用指向实现类对象时,向下转为实现类需校验实际类型:

interface Runnable {}
class MyRunnable implements Runnable {}
class OtherRunnable implements Runnable {}public class Test {public static void main(String[] args) {Runnable runnable = new MyRunnable(); // 接口引用指向实现类对象if (runnable instanceof MyRunnable) {MyRunnable myRunnable = (MyRunnable)runnable; // 安全转换}if (runnable instanceof OtherRunnable) {OtherRunnable otherRunnable = (OtherRunnable)runnable;} else {System.out.println("实际对象不是OtherRunnable,无法转换");}}
}
(3)null引用转型不报错,但后续使用会抛NullPointerException

null可以强制转为任何引用类型(编译不报错),但后续调用该引用的方法时,会抛出NullPointerException

Animal animal = null;
Dog dog = (Dog)animal; // 编译通过,运行不报错(null转型合法)
// dog.bark(); // 错误:NullPointerException(dog是null)

30. 一个汉字占多少位?一个字母占多少位?

在Java中,字符的占用位数取决于编码方式存储场景(如char类型、文件存储、网络传输),核心区别在于“字符编码规则”:

一、Java中char类型的占用位数(内存中)

Java的char类型固定占用2字节(16位),采用Unicode编码(UTF-16小端序),无论字符是汉字、字母还是符号,单个char都占2字节:

  • 字母(如'A''a'):char类型存储,占2字节(16位);
  • 汉字(如'中''国'):char类型存储,占2字节(16位);
  • 特殊符号(如'!''@'):char类型存储,占2字节(16位)。

示例代码

// char类型固定占2字节(16位)
char letter = 'A';
char chinese = '中';
System.out.println(Character.BYTES); // 2(char类型的字节数,固定为2)

二、文件/网络传输中的占用位数(取决于编码)

当字符通过文件存储或网络传输时,会按指定编码(如UTF-8GBK)转换为字节流,此时汉字和字母的占用位数不同:

编码方式单个字母(如’A’、‘a’)单个汉字(如’中’、‘国’)核心特点
UTF-81字节(8位)3字节(24位)(部分生僻字4字节)可变长度编码,兼容ASCII,节省英文存储
GBK1字节(8位)2字节(16位)中文编码,仅支持中文字符和ASCII,不兼容Unicode
UTF-162字节(16位)2字节(16位)(部分生僻字4字节)固定长度为主,与Javachar类型编码一致
ASCII1字节(8位)不支持(无法存储汉字)仅支持英文和符号,不支持中文

示例验证(Java中字符串按编码转字节数组)

import java.io.UnsupportedEncodingException;public class Test {public static void main(String[] args) throws UnsupportedEncodingException {String letter = "A";String chinese = "中";// UTF-8编码:字母1字节,汉字3字节System.out.println(letter.getBytes("UTF-8").length); // 1System.out.println(chinese.getBytes("UTF-8").length); // 3// GBK编码:字母1字节,汉字2字节System.out.println(letter.getBytes("GBK").length); // 1System.out.println(chinese.getBytes("GBK").length); // 2}
}

总结

场景单个字母(如’A’)单个汉字(如’中’)
Java内存中(char2字节(16位)2字节(16位)
文件/传输(UTF-8)1字节(8位)3字节(24位)
文件/传输(GBK)1字节(8位)2字节(16位)

31. ""与null的区别是什么?

""(空字符串)和null都表示“无内容”,但本质是完全不同的概念,核心区别在“是否是对象”“是否占用内存”“使用场景”:

对比维度“”(空字符串)null
本质String类的实例对象,内部维护一个长度为0的char[](Java 8)或byte[](Java 9+)是“空引用”,表示没有指向任何对象,不关联任何内存空间
内存占用占用内存(存储String对象的元信息和空数组)不占用内存(仅表示“无指向”,无实际对象)
方法调用可调用String类的方法(如length()equals()),不会抛空指针异常不能调用任何方法(调用会直接抛出NullPointerException
字符串比较需用equals()比较(如""equals(str)),或str.isEmpty()判断需用str == null判断(不能用equals(),会抛空指针)
默认值可作为String类型变量的默认值(如String str = "";是所有引用类型的默认值(如未初始化的String str;,默认值为null
示例代码java String str = ""; System.out.println(str.length()); // 0(正常调用) System.out.println(str.equals("abc")); // false java String str = null; // System.out.println(str.length()); // 错误:NullPointerException System.out.println(str == null); // true

关键注意事项

  1. 判断空字符串的正确方式

    • 先判断是否为null,再判断是否为空字符串(避免空指针):
      String str = ...;
      if (str != null && str.isEmpty()) {System.out.println("是空字符串");
      }
      
    • 或使用Objects.equals(str, "")(JDK 7+),自动处理null
      if (Objects.equals(str, "")) {System.out.println("是空字符串");
      }
      
  2. 避免“将null当作空字符串”

    • 错误示例:if (str.equals(""))(若strnull,抛空指针);
    • 正确示例:if ("".equals(str))""是对象,调用equals()不会抛空指针,若strnull,返回false)。

32. 什么是switch击穿?

switch击穿(Fall-Through)是switch语句的默认行为:当case分支执行完后,若没有breakreturnthrow语句终止,程序会继续执行下一个case分支的代码,无论下一个case的条件是否匹配,直到遇到终止语句或switch结束。

一、switch击穿的示例

public class Test {public static void main(String[] args) {int num = 2;switch (num) {case 1:System.out.println("执行case 1");// 无break,击穿到case 2case 2:System.out.println("执行case 2");// 无break,击穿到case 3case 3:System.out.println("执行case 3");break; // 有break,终止switchcase 4:System.out.println("执行case 4");}}
}

输出结果

执行case 2
执行case 3

原因num=2匹配case 2,执行完case 2的代码后,因无break,击穿到case 3,执行case 3的代码,直到遇到break终止。

二、switch击穿的“合理使用”场景

虽然击穿通常是“需要避免的问题”,但在某些场景下,可利用击穿实现“多个case执行同一逻辑”:

// 需求:输入1~5,输出“工作日”;输入6~7,输出“休息日”
public class Test {public static void main(String[] args) {int day = 3;switch (day) {case 1:case 2:case 3:case 4:case 5:System.out.println("工作日");break; // 多个case共用同一逻辑,最后加breakcase 6:case 7:System.out.println("休息日");break;}}
}

输出结果工作日
原理day=3匹配case 3,因case 1~4均无break,击穿到case 5,执行“工作日”逻辑后,break终止,实现“1~5共用同一代码”。

三、避免switch击穿的方法

  1. 每个case分支末尾加break:确保执行完当前case后,终止switch(最常用方法);
  2. return替代break:若case分支需返回值,可在执行完逻辑后用return终止(同时终止方法);
  3. throw抛出异常:若case分支是错误场景,可抛出异常终止程序(如throw new IllegalArgumentException("无效参数"));
  4. 使用Java 14+的switch表达式(箭头语法):箭头语法默认不击穿,无需加break,简洁安全:
    // Java 14+ switch表达式(箭头语法,无击穿)
    int day = 3;
    String result = switch (day) {case 1, 2, 3, 4, 5 -> "工作日"; // 多个case用逗号分隔,箭头语法不击穿case 6, 7 -> "休息日";default -> "无效日期";
    };
    System.out.println(result); // 工作日
    

33. finally的作用,一般用于什么场景?

finally是Java异常处理(try-catch-finally)的核心关键字,用于定义“无论try块是否抛出异常、catch块是否执行,都必须执行的代码”,核心作用是“释放资源、保证清理逻辑执行”。

一、finally的核心特性

  1. 执行时机
    • try块无异常:执行try块代码 → 执行finally块代码;
    • try块有异常且被catch捕获:执行try块异常前代码 → 执行catch块代码 → 执行finally块代码;
    • try块有异常且未被catch捕获:执行try块异常前代码 → 执行finally块代码 → 抛出异常给上层;
  2. 唯一不执行的情况:若在try/catch块中调用System.exit(0)(强制终止JVM),finally块不会执行(JVM已退出,无法执行后续代码)。

二、finally的典型应用场景

1. 释放资源(最核心场景)

try块中使用了“需要手动关闭的资源”(如文件流、数据库连接、网络连接)时,finally块用于确保资源被释放,避免资源泄漏:

  • 文件流关闭
    import java.io.FileReader;
    import java.io.IOException;public class Test {public static void main(String[] args) {FileReader reader = null;try {reader = new FileReader("test.txt"); // 打开文件流// 读取文件逻辑} catch (IOException e) {e.printStackTrace();} finally {// 无论是否有异常,都关闭文件流if (reader != null) {try {reader.close(); // 关闭资源} catch (IOException e) {e.printStackTrace();}}}}
    }
    
  • 数据库连接关闭
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.SQLException;public class Test {public static void main(String[] args) {Connection conn = null;try {conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "123456"); // 打开数据库连接// 数据库操作逻辑} catch (SQLException e) {e.printStackTrace();} finally {// 无论是否有异常,都关闭数据库连接if (conn != null) {try {conn.close(); // 关闭资源} catch (SQLException e) {e.printStackTrace();}}}}
    }
    
2. 清理临时数据/状态

try块中修改了某些全局状态或临时数据时,finally块用于恢复状态或清理数据:

public class Test {private static boolean isRunning = false;public static void main(String[] args) {try {isRunning = true; // 修改状态为“运行中”// 执行耗时操作int result = 10 / 0; // 抛出异常} catch (ArithmeticException e) {e.printStackTrace();} finally {isRunning = false; // 无论是否有异常,恢复状态为“未运行”System.out.println("状态已恢复:isRunning=" + isRunning);}}
}

输出结果

java.lang.ArithmeticException: / by zeroat Test.main(Test.java:9)
状态已恢复:isRunning=false
3. 确保返回值之外的逻辑执行

即使try/catch块中有return语句,finally块仍会执行(执行完finally后,再返回try/catch中的值):

public class Test {public static int getNum() {try {return 1; // 执行try的return前,先执行finally} catch (Exception e) {return 2;} finally {System.out.println("finally执行"); // 会执行}}public static void main(String[] args) {System.out.println(getNum());}
}

输出结果

finally执行
1

三、注意事项

  1. 避免在finally中使用return:若finally块有return,会覆盖try/catch块的return值,且可能掩盖异常(不推荐):

    public static int getNum() {try {return 1;} finally {return 2; // 覆盖try的return值}
    }
    public static void main(String[] args) {System.out.println(getNum()); // 输出2(而非1)
    }
    
  2. Java 7+的try-with-resources:对于实现AutoCloseable接口的资源(如FileReaderConnection),可使用try-with-resources语法替代finally,自动关闭资源,代码更简洁:

    // try-with-resources:自动关闭reader,无需finally
    try (FileReader reader = new FileReader("test.txt")) {// 读取文件逻辑
    } catch (IOException e) {e.printStackTrace();
    }
    

34. 反射能否获取私有信息,如果能,需要注意什么?

,Java反射可以获取类的私有信息(私有字段、私有方法、私有构造器),核心是通过Class对象的getDeclaredXXX()系列方法(如getDeclaredField()getDeclaredMethod()),并调用setAccessible(true)打破访问权限限制。

一、反射获取私有信息的示例

1. 获取并修改私有字段
import java.lang.reflect.Field;class Person {// 私有字段private String name;private int age;public Person(String name, int age) {this.name = name;this.age = age;}@Overridepublic String toString() {return "Person{name='" + name + "', age=" + age + "}";}
}public class Test {public static void main(String[] args) throws Exception {// 1. 获取Class对象Class<?> personClass = Person.class;// 2. 创建对象Person person = (Person) personClass.getConstructor(String.class, int.class).newInstance("张三", 20);System.out.println("修改前:" + person); // Person{name='张三', age=20}// 3. 获取私有字段nameField nameField = personClass.getDeclaredField("name");// 4. 打破访问权限限制(关键步骤)nameField.setAccessible(true);// 5. 修改私有字段值nameField.set(person, "李四");// 6. 获取私有字段ageField ageField = personClass.getDeclaredField("age");ageField.setAccessible(true);// 7. 获取私有字段值int age = (int) ageField.get(person);System.out.println("当前age:" + age); // 20// 8. 修改私有字段值ageField.set(person, 25);System.out.println("修改后:" + person); // Person{name='李四', age=25}}
}
2. 获取并调用私有方法
import java.lang.reflect.Method;class Person {// 私有方法private void sayHello(String msg) {System.out.println("Hello, " + msg);}
}public class Test {public static void main(String[] args) throws Exception {Class<?> personClass = Person.class;Person person = (Person) personClass.getConstructor().newInstance();// 1. 获取私有方法sayHello(参数:方法名 + 参数类型)Method sayHelloMethod = personClass.getDeclaredMethod("sayHello", String.class);// 2. 打破访问权限限制sayHelloMethod.setAccessible(true);// 3. 调用私有方法(参数:对象实例 + 方法参数)sayHelloMethod.invoke(person, "反射!"); // 输出:Hello, 反射!}
}
3. 获取并调用私有构造器
import java.lang.reflect.Constructor;class Person {private String name;// 私有构造器private Person(String name) {this.name = name;}@Overridepublic String toString() {return "Person{name='" + name + "'}";}
}public class Test {public static void main(String[] args) throws Exception {Class<?> personClass = Person.class;// 1. 获取私有构造器(参数:构造器参数类型)Constructor<?> privateConstructor = personClass.getDeclaredConstructor(String.class);// 2. 打破访问权限限制privateConstructor.setAccessible(true);// 3. 调用私有构造器创建对象Person person = (Person) privateConstructor.newInstance("王五");System.out.println(person); // Person{name='王五'}}
}

二、需要注意的事项

  1. 访问权限风险setAccessible(true)会跳过Java的访问权限检查(privateprotected等修饰符失效),可能破坏类的封装性,导致代码逻辑混乱(如随意修改私有字段,违背类的设计意图);
  2. 性能损耗:反射操作需要动态解析类的元信息(如字段、方法签名),比直接调用(非反射)慢10~100倍,频繁使用会影响程序性能,不建议在高性能场景(如循环、高频接口)中使用;
  3. 安全性限制:若程序运行在有安全管理器(SecurityManager)的环境中(如Applet),setAccessible(true)可能被安全策略禁止,抛出SecurityException
  4. 代码可读性差:反射代码通过字符串指定字段/方法名(如getDeclaredField("name")),编译器无法进行语法检查,若字段名/方法名写错,仅在运行时抛出NoSuchFieldExceptionNoSuchMethodException,排查难度大;
  5. 版本兼容性问题:若类的私有成员(如字段名、方法参数)在后续版本中修改,反射代码会因找不到对应成员而抛出异常,且编译器无法提前预警,兼容性差。

三、使用建议

  • 仅在“必须动态访问私有成员”的场景中使用(如框架开发:Spring的依赖注入、MyBatis的ORM映射);
  • 尽量避免在业务代码中使用反射访问私有成员,优先通过类提供的public方法(如getter/setter)操作;
  • 若使用反射,建议对字段名/方法名进行常量定义(如private static final String FIELD_NAME = "name"),减少拼写错误;
  • 敏感场景下,可通过自定义安全管理器限制反射的访问权限。

35. 什么是泛型,请写一个泛型队列或者泛型栈。

一、什么是泛型

泛型(Generic)是Java 5引入的特性,用于在编译时指定集合或类的元素类型,核心作用是“类型安全”和“代码复用”:

  • 类型安全:编译时检查元素类型,避免运行时抛出ClassCastException(如避免将Integer存入List<String>);
  • 代码复用:无需为不同类型(如StringInteger)编写重复代码,一个泛型类/方法可适配多种类型。
泛型的核心概念
  • 泛型类:类定义时声明类型参数(如class List<E>E为类型参数,代表“元素类型”);
  • 泛型方法:方法定义时声明类型参数(如public <T> T getValue(T obj)T为类型参数);
  • 类型擦除:Java泛型是“编译时泛型”,运行时会擦除类型参数(如List<String>在运行时变为List),仅在编译时提供类型检查。

二、泛型队列实现(基于链表)

队列(Queue)遵循“先进先出(FIFO)”原则,以下是基于链表的泛型队列实现,支持enqueue(入队)、dequeue(出队)、peek(查看队首)、isEmpty(判断空)等核心操作:

// 泛型队列:基于链表实现,支持任意类型元素
public class GenericQueue<T> {// 链表节点类(泛型)private static class Node<T> {T data; // 节点存储的数据Node<T> next; // 下一个节点的引用Node(T data) {this.data = data;this.next = null;}}private Node<T> front; // 队首(出队端)private Node<T> rear;  // 队尾(入队端)private int size;      // 队列元素个数// 构造器:初始化空队列public GenericQueue() {front = null;rear = null;size = 0;}// 1. 入队:将元素添加到队尾public void enqueue(T data) {Node<T> newNode = new Node<>(data);if (isEmpty()) {// 空队列时,队首和队尾都指向新节点front = newNode;rear = newNode;} else {// 非空队列时,队尾指向新节点,更新队尾rear.next = newNode;rear = newNode;}size++;}// 2. 出队:移除并返回队首元素(空队列抛出异常)public T dequeue() {if (isEmpty()) {throw new IllegalStateException("队列为空,无法出队");}// 获取队首元素T data = front.data;// 更新队首为下一个节点front = front.next;size--;// 若出队后为空,队尾也置为nullif (isEmpty()) {rear = null;}return data;}// 3. 查看队首元素(不移除,空队列抛出异常)public T peek() {if (isEmpty()) {throw new IllegalStateException("队列为空,无队首元素");}return front.data;}// 4. 判断队列是否为空public boolean isEmpty() {return size == 0;}// 5. 获取队列元素个数public int size() {return size;}// 测试泛型队列public static void main(String[] args) {// 1. 存储String类型的队列GenericQueue<String> stringQueue = new GenericQueue<>();stringQueue.enqueue("Java");stringQueue.enqueue("泛型");stringQueue.enqueue("队列");System.out.println("String队列大小:" + stringQueue.size()); // 3System.out.println("队首元素:" + stringQueue.peek()); // JavaSystem.out.println("出队元素:" + stringQueue.dequeue()); // JavaSystem.out.println("出队后大小:" + stringQueue.size()); // 2// 2. 存储Integer类型的队列GenericQueue<Integer> intQueue = new GenericQueue<>();intQueue.enqueue(10);intQueue.enqueue(20);intQueue.enqueue(30);System.out.println("\nInteger队列大小:" + intQueue.size()); // 3while (!intQueue.isEmpty()) {System.out.println("出队元素:" + intQueue.dequeue()); // 10 → 20 → 30}// 3. 空队列出队(抛出异常)// intQueue.dequeue(); // 抛出IllegalStateException: 队列为空,无法出队}
}

输出结果

String队列大小:3
队首元素:Java
出队元素:Java
出队后大小:2Integer队列大小:3
出队元素:10
出队元素:20
出队元素:30

三、泛型栈实现(基于数组)

栈(Stack)遵循“后进先出(LIFO)”原则,以下是基于数组的泛型栈实现,支持push(入栈)、pop(出栈)、peek(查看栈顶)等操作:

// 泛型栈:基于数组实现,支持任意类型元素
public class GenericStack<T> {private T[] stack; // 存储栈元素的数组private int top;   // 栈顶指针(指向栈顶元素的下一个位置)private int capacity; // 栈的初始容量// 构造器:默认初始容量为10@SuppressWarnings("unchecked") // 抑制“未检查的转换”警告public GenericStack() {capacity = 10;stack = (T[]) new Object[capacity]; // 泛型数组不能直接创建,需通过Object数组转换top = 0;}// 构造器:自定义初始容量@SuppressWarnings("unchecked")public GenericStack(int capacity) {this.capacity = capacity;stack = (T[]) new Object[capacity];top = 0;}// 1. 入栈:将元素添加到栈顶(栈满时扩容)public void push(T data) {// 栈满,扩容为原容量的2倍if (isFull()) {resize(capacity * 2);}stack[top] = data;top++;}// 2. 出栈:移除并返回栈顶元素(栈空时抛出异常)public T pop() {if (isEmpty()) {throw new IllegalStateException("栈为空,无法出栈");}top--;T data = stack[top];stack[top] = null; // 释放引用,避免内存泄漏return data;}// 3. 查看栈顶元素(不移除,栈空时抛出异常)public T peek() {if (isEmpty()) {throw new IllegalStateException("栈为空,无栈顶元素");}return stack[top - 1];}// 4. 判断栈是否为空public boolean isEmpty() {return top == 0;}// 5. 判断栈是否已满public boolean isFull() {return top == capacity;}// 6. 获取栈元素个数public int size() {return top;}// 扩容:创建新数组,复制原数组元素@SuppressWarnings("unchecked")private void resize(int newCapacity) {T[] newStack = (T[]) new Object[newCapacity];for (int i = 0; i < top; i++) {newStack[i] = stack[i];}stack = newStack;capacity = newCapacity;}// 测试泛型栈public static void main(String[] args) {// 存储Double类型的栈GenericStack<Double> doubleStack = new GenericStack<>();doubleStack.push(1.1);doubleStack.push(2.2);doubleStack.push(3.3);System.out.println("Double栈大小:" + doubleStack.size()); // 3System.out.println("栈顶元素:" + doubleStack.peek()); // 3.3System.out.println("出栈元素:" + doubleStack.pop()); // 3.3System.out.println("出栈后大小:" + doubleStack.size()); // 2// 存储Person类型的栈(自定义类)class Person {private String name;Person(String name) { this.name = name; }@Overridepublic String toString() { return "Person{" + "name='" + name + "'}"; }}GenericStack<Person> personStack = new GenericStack<>();personStack.push(new Person("张三"));personStack.push(new Person("李四"));System.out.println("\nPerson栈大小:" + personStack.size()); // 2while (!personStack.isEmpty()) {System.out.println("出栈元素:" + personStack.pop()); // Person{name='李四'} → Person{name='张三'}}}
}

输出结果

Double栈大小:3
栈顶元素:3.3
出栈元素:3.3
出栈后大小:2Person栈大小:2
出栈元素:Person{name='李四'}
出栈元素:Person{name='张三'}

36. 基本类型的数据存在JVM的哪个区域?

基本类型的数据存储位置取决于是否为局部变量,核心分为“栈内存”和“堆内存”两类,具体如下:

一、局部变量:存储在“虚拟机栈”的栈帧中

局部变量是定义在方法、代码块(如iffor)中的基本类型变量,存储在虚拟机栈(VM Stack) 的“栈帧(Stack Frame)”中:

  • 栈帧:每个方法执行时,JVM会创建一个栈帧,栈帧包含“局部变量表”“操作数栈”等,局部变量存储在“局部变量表”中;
  • 生命周期:方法执行时,栈帧入栈;方法执行完毕,栈帧出栈,局部变量随栈帧销毁,内存自动释放;
  • 示例
    public void test() {int a = 10; // 局部变量a,存储在test()方法的栈帧局部变量表中double b = 3.14; // 局部变量b,同样存储在局部变量表中
    }
    

二、非局部变量(成员变量、静态变量):存储在“堆”或“方法区”

非局部变量是定义在类中的基本类型变量(成员变量、静态变量),存储位置与变量类型(实例变量/静态变量)相关:

1. 实例变量(成员变量):存储在“堆内存”

实例变量是类中无static修饰的基本类型变量,属于对象的一部分,存储在堆内存(Heap) 中:

  • 存储逻辑:当创建对象(new)时,对象的实例变量随对象一起存储在堆中;
  • 生命周期:对象被GC(垃圾回收)回收时,实例变量随对象销毁;
  • 示例
    class Person {int age; // 实例变量age,随Person对象存储在堆中boolean isStudent; // 实例变量isStudent,同样存储在堆中
    }public void test() {Person person = new Person(); // person对象存储在堆中,age和isStudent也在堆中person.age = 20; // 修改堆中age的值
    }
    
2. 静态变量(类变量):存储在“方法区”

静态变量是类中用static修饰的基本类型变量,属于类的一部分,存储在方法区(Method Area) 中:

  • 存储逻辑:类加载时,静态变量被初始化,存储在方法区的“静态变量区”(JDK 8后,方法区的静态变量移至堆中的“元空间(Metaspace)”,但概念上仍属于方法区范畴);
  • 生命周期:类卸载时,静态变量随类销毁,生命周期与类一致;
  • 示例
    class MathUtil {public static final int MAX_VALUE = 100; // 静态变量MAX_VALUE,存储在方法区public static int count = 0; // 静态变量count,同样存储在方法区
    }
    

三、总结:基本类型存储位置

变量类型定义位置存储区域生命周期
局部变量方法、代码块中虚拟机栈-栈帧局部变量表方法执行期间,随栈帧销毁
实例变量(成员变量)类中,无static修饰堆内存随对象,对象被GC回收时销毁
静态变量(类变量)类中,有static修饰方法区(JDK 8后为元空间)随类,类卸载时销毁

37. Throw和Throws的区别

throwthrows均用于Java异常处理,但作用和使用方式完全不同:throw用于“主动抛出异常对象”,throws用于“声明方法可能抛出的异常类型”,核心区别如下:

对比维度throwthrows
关键字类型语句关键字(用于执行抛出异常的动作)方法修饰符(用于声明异常)
作用主动创建并抛出异常对象(触发异常)声明方法可能抛出的异常类型,告知调用者“需处理这些异常”
使用位置方法体内部(如if判断后、逻辑错误处)方法签名末尾(如public void test() throws IOException
语法格式throw new 异常类(异常信息);(只能抛出一个异常对象)方法返回值 方法名(参数列表) throws 异常类1, 异常类2,...;(可声明多个异常类型)
异常类型可抛出编译时异常(需配合throwstry-catch)或运行时异常声明的异常类型可为编译时异常或运行时异常(运行时异常声明可省略)
示例代码java public void test() { int a = -1; if (a < 0) { // 主动抛出运行时异常 throw new IllegalArgumentException("a不能为负数"); } } java // 声明方法可能抛出IOException(编译时异常) public void readFile() throws IOException { FileReader reader = new FileReader("test.txt"); reader.close(); }

关键使用场景

1. throw的使用场景
  • 主动触发异常:当代码逻辑不符合预期时(如参数非法、状态错误),主动抛出异常,终止当前逻辑;
  • 抛出运行时异常:无需在方法上声明throws(运行时异常是RuntimeException子类,编译器不强制声明);
  • 抛出编译时异常:需在方法上用throws声明该异常,或在try-catch中捕获;
    // 抛出编译时异常,需配合throws
    public void test() throws Exception {throw new Exception("主动抛出编译时异常");
    }
    
2. throws的使用场景
  • 声明编译时异常:方法内部可能抛出编译时异常(如IOException),但不希望在方法内部捕获,而是交给调用者处理,需用throws声明;
  • 声明多个异常:方法可能抛出多种异常,用逗号分隔声明;
    // 声明方法可能抛出IOException和SQLException
    public void test() throws IOException, SQLException {// 可能抛出IOException的代码// 可能抛出SQLException的代码
    }
    
  • 避免冗余try-catch:在多层方法调用中,上层方法可通过throws将异常向上传递,由最外层统一处理,减少中间层的try-catch冗余。

注意事项

  • throw不能单独使用throw必须配合异常对象(如throw new NullPointerException()),且抛出后,后续代码不会执行(异常会中断当前代码流);
  • throws不处理异常throws仅声明异常,不处理异常,异常最终需由调用者通过try-catch处理,或继续向上传递;
  • 运行时异常无需throws声明RuntimeException及其子类(如NullPointerExceptionIllegalArgumentException)可不用throws声明,编译器不强制,但建议在方法注释中说明可能抛出的运行时异常。

38. Object类的方法有哪些?分别有什么作用?

Object是Java中所有类的顶层父类(包括数组、自定义类,默认隐式继承Object),它定义了11个核心方法,这些方法被所有子类继承,是Java面向对象体系的基础。以下是所有方法的详细解析,按常用程度排序:

一、常用核心方法(7个)

1. public String toString()
  • 核心作用:返回对象的“字符串表示形式”,用于快速查看对象的关键信息。
  • 默认实现:返回格式为 全类名@哈希码的十六进制(如 com.test.Person@1b6d3586),其中哈希码由 hashCode() 方法生成,无实际业务意义。
  • 重写建议:几乎所有自定义类都需重写 toString(),返回对象的字段信息(如 Person{name='张三', age=20}),方便日志打印、调试和业务展示。
  • 示例
    class Person {private String name;private int age;@Overridepublic String toString() {return "Person{name='" + name + "', age=" + age + "}"; // 重写后返回字段信息}
    }// 使用:System.out.println(obj) 本质是调用 obj.toString()
    Person person = new Person("张三", 20);
    System.out.println(person); // 输出:Person{name='张三', age=20}
    
2. public boolean equals(Object obj)
  • 核心作用:判断两个对象是否“相等”,默认比较“内存地址”,可重写为比较“对象内容”。
  • 默认实现return (this == obj);,即同 == 运算符,仅当两个引用指向同一对象时返回 true
  • 重写场景:当需要按“内容相等”判断对象(如 String 比较字符序列、Person 比较身份证号)时,必须重写 equals()
  • 重写规则(Java规范)
    1. 自反性x.equals(x) 必须返回 true
    2. 对称性:若 x.equals(y)true,则 y.equals(x) 也必须为 true
    3. 传递性:若 x.equals(y)y.equals(z)true,则 x.equals(z) 也必须为 true
    4. 一致性:多次调用 x.equals(y),结果需一致(对象未修改时);
    5. 非空性x.equals(null) 必须返回 false
  • 示例
    class Person {private String id; // 按身份证号判断相等@Overridepublic boolean equals(Object o) {if (this == o) return true; // 先判断内存地址,优化性能if (o == null || getClass() != o.getClass()) return false; // 判空+判类型Person person = (Person) o;return Objects.equals(id, person.id); // 用Objects.equals避免空指针}
    }
    
3. public int hashCode()
  • 核心作用:返回对象的“哈希码”(一个int值),用于哈希表集合(如 HashMapHashSet)的“哈希桶定位”,提高查询效率。
  • 默认实现:返回对象的“内存地址相关值”(不同对象的哈希码大概率不同)。
  • 关键规则(与equals()联动)
    • x.equals(y) == true,则 x.hashCode() == y.hashCode() 必须成立(否则哈希表集合无法识别相等对象,导致去重、查询错误);
    • x.hashCode() == y.hashCode(),则 x.equals(y) 不一定为 true(哈希冲突,不同对象可能有相同哈希码)。
  • 重写要求重写 equals() 时必须重写 hashCode(),且哈希码的计算逻辑需与 equals() 一致(如 PersonhashCode() 基于 id 计算)。
  • 示例
    class Person {private String id;@Overridepublic boolean equals(Object o) { /* 实现见上文 */ }@Overridepublic int hashCode() {return Objects.hash(id); // 基于id计算哈希码,与equals()逻辑一致}
    }
    
4. protected Object clone() throws CloneNotSupportedException
  • 核心作用:创建并返回当前对象的“拷贝”,实现对象的复制(默认是 浅拷贝)。
  • 使用前提
    1. 类必须实现 Cloneable 接口(标记接口,无任何方法,仅用于告知JVM“该类支持克隆”);
    2. 若未实现 Cloneable,调用 clone() 会抛出 CloneNotSupportedException
  • 拷贝类型
    • 浅拷贝:仅复制对象本身和基本类型字段,引用类型字段仅复制“引用地址”(不复制引用指向的对象,即原对象和拷贝对象共享引用类型字段的对象);
    • 深拷贝:需手动实现,复制对象本身、基本类型字段,以及引用类型字段指向的对象(原对象和拷贝对象完全独立)。
  • 示例(浅拷贝)
    // 实现Cloneable接口,支持克隆
    class Person implements Cloneable {private String name; // 基本类型包装类(String不可变,浅拷贝无问题)private List<String> hobbies; // 引用类型(浅拷贝仅复制引用)@Overrideprotected Person clone() throws CloneNotSupportedException {return (Person) super.clone(); // 调用Object的clone(),默认浅拷贝}
    }// 使用
    Person p1 = new Person("张三", Arrays.asList("篮球", "游戏"));
    Person p2 = p1.clone();
    System.out.println(p1 == p2); // false(p2是新对象)
    System.out.println(p1.getHobbies() == p2.getHobbies()); // true(共享hobbies对象,浅拷贝特性)
    
5. public final Class<?> getClass()
  • 核心作用:返回当前对象所属类的 Class 对象(反射的核心入口),通过 Class 对象可获取类的元信息(字段、方法、构造器等)。
  • 关键特性
    • 方法用 final 修饰,不能被重写(确保返回的 Class 对象真实反映对象的类型);
    • 同一类在JVM中仅存在一个 Class 对象,无论通过哪种方式(getClass()类名.classClass.forName())获取,都是同一实例。
  • 示例(反射场景)
    Person person = new Person();
    Class<?> clazz = person.getClass(); // 获取Person类的Class对象// 通过Class对象获取类信息
    System.out.println(clazz.getName()); // 输出全类名:com.test.Person
    Field[] fields = clazz.getDeclaredFields(); // 获取所有字段(包括私有)
    
6. public final void notify()
  • 核心作用:唤醒当前对象“等待池”中的任意一个线程,使其从“等待态”进入“就绪态”,参与对象锁的竞争(获取锁后继续执行)。
  • 使用场景:配合 wait() 实现线程间通信(如生产者-消费者模式,生产者唤醒消费者)。
  • 使用限制
    • 必须在 synchronized 同步块或同步方法中调用(否则抛出 IllegalMonitorStateException),因为调用者需持有当前对象的“对象锁”;
    • 唤醒的线程是随机的(无法指定唤醒某个线程)。
  • 示例(线程通信)
    class Monitor {private boolean flag = false;// 生产者:修改flag并唤醒消费者public synchronized void produce() {flag = true;notify(); // 唤醒等待该对象锁的线程}// 消费者:等待flag为true,被唤醒后执行public synchronized void consume() throws InterruptedException {while (!flag) {wait(); // 释放对象锁,进入等待池}flag = false;System.out.println("消费完成");}
    }
    
7. public final void wait() throws InterruptedException
  • 核心作用:使当前线程从“运行态”进入“等待态”,并释放当前对象的锁,直到被 notify()/notifyAll() 唤醒,或被中断。
  • 使用限制
    • 必须在 synchronized 同步块或同步方法中调用(需持有对象锁,否则抛出 IllegalMonitorStateException);
    • 线程被唤醒后,需重新竞争对象锁(获取锁后才会继续执行 wait() 之后的代码)。
  • 重载方法
    • wait(long timeout):等待 timeout 毫秒(超时后自动唤醒,无需 notify());
    • wait(long timeout, int nanos):更精确的等待(timeout 毫秒 + nanos 纳秒,nanos 范围 0~999999)。
  • 注意事项wait() 需放在 while 循环中(而非 if),避免“虚假唤醒”(线程被唤醒后,需重新校验条件是否满足,防止条件不满足时继续执行)。
  • 示例(避免虚假唤醒)
    public synchronized void consume() throws InterruptedException {// 用while循环,唤醒后重新校验条件while (!flag) {wait(); }// 业务逻辑
    }
    

二、其他方法(4个)

8. public final void notifyAll()
  • 核心作用:唤醒当前对象“等待池”中的所有线程,使其进入“就绪态”,参与对象锁的竞争。
  • notify() 的区别notify() 唤醒任意一个线程,notifyAll() 唤醒所有线程(适用于多个线程等待同一资源的场景,如多个消费者等待生产者的产品)。
  • 使用限制:同 notify(),需在 synchronized 中调用。
9. public final void wait(long timeout) throws InterruptedException
  • 核心作用:带超时的等待,线程进入等待态后,若在 timeout 毫秒内未被 notify()/notifyAll() 唤醒,则自动唤醒,避免线程永久等待。
  • 示例
    public synchronized void consume() throws InterruptedException {// 等待1秒,超时后自动唤醒wait(1000); // 业务逻辑
    }
    
10. public final void wait(long timeout, int nanos) throws InterruptedException
  • 核心作用:比 wait(long timeout) 更精确的等待,支持纳秒级超时(timeout 毫秒 + nanos 纳秒)。
  • 注意事项nanos 取值范围为 0~999999,若超过则向上取整到毫秒(如 nanos=1000000 等价于 timeout+1 毫秒),实际开发中较少使用(精度需求高时优先用 java.util.concurrent 包的工具类)。
11. protected void finalize() throws Throwable
  • 核心作用:对象被垃圾回收器(GC)回收前,JVM会调用该方法,用于释放对象持有的资源(如关闭文件流、数据库连接)。
  • 现状与问题
    • JDK 9 后被标记为 @Deprecated(过时),不推荐使用;
    • 执行时机不确定(GC触发时间不确定,finalize() 可能迟迟不执行,导致资源泄漏);
    • 可能导致对象“复活”(在 finalize() 中使对象重新被引用,导致GC无法回收)。
  • 替代方案:资源释放优先使用 try-with-resources(自动关闭实现 AutoCloseable 接口的资源)或 finally 块(确保资源被释放)。

三、Object类方法总结

方法签名核心作用关键注意点
toString()返回对象字符串表示必须重写,方便调试和展示
equals(Object)判断对象是否相等重写时需遵循5大规则,且必须重写 hashCode()
hashCode()返回对象哈希码计算逻辑需与 equals() 一致,支持哈希表集合
clone()复制对象需实现 Cloneable,默认浅拷贝,深拷贝需手动实现
getClass()获取 Class 对象反射入口,final 方法不可重写
notify()唤醒任意等待线程需在 synchronized 中调用,随机唤醒
notifyAll()唤醒所有等待线程需在 synchronized 中调用,适用于多线程等待场景
wait()线程等待并释放锁需在 synchronized 中调用,放 while 循环防虚假唤醒
wait(long)带超时的线程等待超时后自动唤醒,避免永久等待
wait(long, int)纳秒级超时等待精度高,实际使用少
finalize()垃圾回收前释放资源已过时,推荐 try-with-resourcesfinally

Object类的方法是Java面向对象的基础,尤其是 equals()hashCode()toString() 和反射相关的 getClass(),在日常开发和框架底层(如集合、ORM)中被广泛使用,必须熟练掌握其原理和正确用法。

http://www.dtcms.com/a/609926.html

相关文章:

  • 煤矿煤质分类数据集3406张5类别
  • MR30分布式I/O模块服务换热站项目,守护万家温暖
  • 唐山市城市建设规划局网站网站首页index.html
  • 大模型架构和原理二
  • 11.14 脚本网页 青蛙过河
  • 【算法专题训练】30、二叉树的应用
  • 深入解析IP, ICMP, OSPF, BGP四大核心网络协议
  • RAG系统中的文本分块技术:从基础策略到智能分块的深度解析
  • Bootstrap 4 Flex布局详解
  • APP网站建设开发企业发展网站建设增城
  • 我做网站了 圆通北京网站建设首选优达
  • Lidar调试记录Ⅲ之Ubuntu22.04+ROS2环境中安装colcon
  • 闵行网站制作设计公司wordpress wp polls
  • IntelliJ IDEA初始化指南
  • Unity ScriptedImporter 教程:自定义资源导入器
  • C语言编译器苹果 | 适用于macOS的高效C语言编译环境介绍
  • python(57) : 离线环境升级依赖
  • C++网络开发---客户端网络传输 libcurl
  • 电商项目练习实操(二)
  • 不使用后端接口导出excel的三种方式
  • leetcode 394 字符串解码
  • 如何做充值网站seo模拟点击软件源码
  • 好看的旅游网站模板下载镇江百度推广公司
  • 智慧物业|物业管理|基于SprinBoot+vue的智慧物业管理系统(源码+数据库+文档)
  • Android thermal (7)_thermal core
  • 网站的维护费用售后服务网站建设
  • Databend SQL nom Parser 性能优化
  • wordpress的标签页网站seo竞争分析工具
  • Clip模型与Vit模型的区别?
  • 前端 CSS selector