java面试题(二)
11. Java 反射获取类信息的方式有哪几种?分别是什么?
反射获取类信息(Class对象)的方式有 3 种:
- Class.forName(String className):
通过类的全限定名加载类(触发类初始化),如Class.forName("java.lang.String")。
- 对象.getClass():
通过实例对象获取Class,如"hello".getClass()。
- 类名.class:
直接通过类名获取(不触发类初始化),如String.class。
12. Java 代理的主要方法有哪几种?列举代理的使用场景 2 个。
主要代理方法
- 静态代理:
手动编写代理类(实现与目标类相同的接口),在代理类中调用目标方法并添加增强逻辑(如日志)。缺点:代理类与目标类耦合,不易扩展。
- 动态代理:
- JDK 动态代理:基于接口生成代理类(通过Proxy.newProxyInstance()),要求目标类实现接口;
- CGLIB 动态代理:通过继承目标类生成代理子类(无需接口),基于 ASM 字节码技术。
使用场景
- AOP(面向切面编程):如 Spring AOP 通过代理实现日志、事务、权限等横切逻辑;
- 远程调用:如 RPC 框架(Dubbo)通过代理封装网络通信细节,让调用远程服务像调用本地方法。
13. equals () 方法的作用是什么?重写 equals 需要注意哪些事项?为什么?
- 作用:判断两个对象的内容是否相等(==判断地址是否相等)。
重写equals的注意事项(需满足 5 大特性)
- 自反性:x.equals(x)必须返回true;
- 对称性:x.equals(y)与y.equals(x)结果一致;
- 传递性:若x.equals(y)且y.equals(z),则x.equals(z);
- 一致性:多次调用结果相同(对象未被修改时);
- 与hashCode()一致:若x.equals(y)为true,则x.hashCode()必须等于y.hashCode()。
原因
- 违反前 4 点会导致逻辑错误(如集合中判断元素相等时出错);
- 违反第 5 点会导致HashMap、HashSet等基于哈希的集合无法正常工作(如两个相等的对象可能被存入不同桶中)。
14. Java 是按值传递还是按引用传递?什么是引用传递,什么是值传递,哪些语言支持引用传递?
- Java 是按值传递:
- 基本类型:传递值的副本(修改副本不影响原变量);
- 引用类型:传递引用地址的副本(修改副本指向的对象内容会影响原对象,但修改副本的指向不影响原引用)。
概念区分
- 值传递:传递数据的副本,函数内修改副本不影响原数据;
- 引用传递:传递数据的地址(而非副本),函数内修改会直接影响原数据。
支持引用传递的语言
如 C++(通过&声明引用参数)、Python(对象传递本质是引用传递)。
15. 描述 java 的类初始化顺序?如果存在继承,初始化顺序会如何
单个类的初始化顺序(从先到后)
- 静态变量(类变量);
- 静态代码块(static {});
- 实例变量(成员变量);
- 构造代码块({},每次创建实例时执行);
- 构造方法。
存在继承时的初始化顺序
- 父类静态成员 / 静态代码块(只执行一次);
- 子类静态成员 / 静态代码块(只执行一次);
- 父类实例成员 / 构造代码块;
- 父类构造方法;
- 子类实例成员 / 构造代码块;
- 子类构造方法。
规律:静态优先于实例,父类优先于子类。
16. 本地方法栈有什么作用?
本地方法栈是 JVM 内存区域之一,作用是:
- 支持本地方法(native修饰的方法) 的执行,存储本地方法的参数、返回值、局部变量等;
- 本地方法通常用 C/C++ 实现,本地方法栈与操作系统的栈结构相关(不同系统实现不同)。
17. 描述 Java 的双亲委派机制,为什么要用到双亲委派机制
- 双亲委派机制:类加载时,子类加载器先委托父类加载器加载类,若父类加载器无法加载(找不到类),子类加载器才尝试自己加载。
核心加载器(从父到子)
- 启动类加载器(Bootstrap):加载JAVA_HOME/lib下的核心类(如java.lang.String);
- 扩展类加载器(Extension):加载JAVA_HOME/lib/ext下的类;
- 应用类加载器(App):加载应用 classpath 下的类。
作用
- 避免类重复加载:同一类只会被一个加载器加载;
- 保证核心类安全:防止自定义类(如java.lang.String)篡改核心类,避免安全风险。
18. 重写和重载的区别是什么?
维度 | 重写(Override) | 重载(Overload) |
定义 | 子类覆盖父类的方法 | 同一类中方法名相同,参数不同 |
方法名 | 必须相同 | 必须相同 |
参数列表 | 必须相同(个数、类型、顺序) | 必须不同(个数、类型、顺序) |
返回值类型 | 子类返回值需与父类兼容(<= 父类) | 可不同 |
访问修饰符 | 子类修饰符权限 >= 父类 | 无限制 |
异常 | 子类抛出异常 <= 父类 | 无限制 |
作用 | 实现多态(运行时绑定) | 提高方法灵活性(编译时绑定) |
19. 子类构造方法调用父类构造方法的注意事项有哪些?
- 默认调用父类无参构造:子类构造方法第一行隐含super(),若父类无无参构造,编译报错;
- 显式调用父类有参构造:若父类只有有参构造,子类必须在构造方法第一行用super(参数)显式调用;
- 不可调用多次:super()或this()(调用本类其他构造方法)只能出现一次,且必须在第一行。
20. 子类实例初始化是否触发父类实例初始化?
是。
子类实例化时,会先执行父类的实例初始化(实例变量、构造代码块、构造方法),再执行子类的实例初始化。原因是:子类构造方法第一行会调用父类构造方法(super()),触发父类实例初始化。