更多特训营笔记详见个人主页【面试鸭特训营】专栏
250119
1. 接口和抽象类有什么区别?
接口
- 接口的设计是自上而下的。
-
- 所谓的自上而下指的是:先约定接口,再实现。
- 我们知晓某一行为,于是基于这些行为约束定义了接口,一些类需要有这些行为,因此实现对应的接口。
- 接口中的方法默认是 public 和 abstract (但在 Java8 之后可以设置 default 方法或静态方法)。
- 接口不能包含构造函数,接口中的成员变量默认为 public static final,即常量。
- 接口可以有多个实现 ,如果多个接口内有相同的默认方法,子类必须重写这个方法,否则编译时会报错。
抽象类
- 抽象类的设计是自下而上的。
-
- 所谓的自下而上指的是:先有一些类,才抽象了共同父类。
- 我们写了很多类,发现它们之间有共性,有很多代码可以复用,因此将一些公共逻辑封装成一个抽象类,减少代码冗余。
- 抽象类可以包含 abstract 方法(没有实现)和具体方法(有实现)。它允许子类继承并重用抽象类中的方法实现。
- 抽象类可以包含构造函数,成员变量可以有不同的访问修饰符(如 private、protected、public),并且可以不是常量。
- 为避免菱形继承关系产生歧义,抽象类只能是单继承。
表格对比
| 接口 | 抽象类 |
---|
设计方案 | 自上而下先定义接口,再实现 | 自下而上先有一些类,抽取共同部分 |
包含的方法 | public 和 abstract不能有具体实现 | 可以包含 abstract 方法和具体方法具体方法可以有实现且允许子类重用 |
构造函数 | 不能包含 | 可以包含,在子类实例化时调用 |
包含的成员变量 | public static final必须是常量,必须初始化 | private、protected、public可以不是常量,可以是变量 |
多继承 | 支持 | 不支持 |
2. JDK 动态代理和 CGLIB 动态代理有什么区别?
JDK 动态代理
- 基于接口,要求代理对象必须实现至少一个接口。
- 通过反射机制生成实现了指定接口的代理类对象。
- 只能对实现了接口的类进行代理。
CGLIB 动态代理
- 基于 ASM 字节码生成工具,通过继承的方式生成目标类的子类来实现代理类,代理对象是原始类的子类,要注意 final 方法。
- 可以代理没有实现接口的类。
性能对比
- jdk6 下,在运行次数较少的情况下,jdk动态代理与cglib动态代理差距不明显,当调用次数增加之后,cglib动态代理稍微快一些。
- jdk7 下,在运行次数较少(1,000,000)的情况下,jdk动态代理比cglib动态代理快了差不多30%,当调用次数增加之后(50,000,000),jdk动态代理比cglib动态代理快了接近 1 倍。
- jdk8 下,表现和 jdk7 基本一致。
表格对比
特性 | JDK动态代理 | CGLIB动态代理 |
---|
代理方式 | 基于接口,使用反射生成代理对象 | 基于继承,使用字节码技术生成代理对象 |
是否需要接口 | 必须要求目标类实现至少一个接口 | 不需要接口,可以代理没有接口的类 |
代理Final类/Final方法 | 支持 | 不支持 |
适用场景 | 适用于目标类实现了接口的情况,接口驱动的设计 | 适用于目标类没有实现接口的情况,可以代理普通类 |
3. 你使用过 Java 的反射机制吗?如何应用反射?
定义
- Java 反射机制是指在运行时动态地获取类的结构信息(如方法、字段、构造函数)、创建对象以及调用对象的属性和方法的机制。
- Java 反射机制提供了运行时检查 Java 类型信息的能力,让Java 程序可以通过程序获取其本身的信息。
- Java 的反射机制提供了在运行时动态创建对象、调用方法、访问字段等功能,而无需在编译时知道这些类的具体信息。
优点
- 可以动态地获取类的信息,不需要在编译时就知道类的信息。
- 可以动态地创建对象,不需要在编译时就知道对象的类型。
- 可以动态地调用对象的属性和方法,可以在运行时动态地改变对象的行为。
缺点
- 由于反射是动态的,所以它的运行效率较低,不如直接调用方法或属性。
- 由于反射是动态的,所以它会破坏 Java 的封装性,可能会使代码变得复杂和不稳定。
应用场景
- 动态代理。动态代理可以使用反射机制在运行时动态地创建代理对象,而不需要在编译时就知道接口的实现类
- 单元测试。JUnit 等单元测试框架可以使用反射机制在运行时动态地获取类和方法的信息,实现自动化测试
- 配置文件加载。许多框架(如 Spring)使用反射机制来读取和解析配置文件,从而实现依赖注入和面向切面编程等功能
获取类对象的3种方案及代码实现
- 获取普通的Class对象:
-
实例.getClass()
: 此方式要求必须先有类的实例。类名.class
: 便捷但属于硬编码,对于JDK中内置的类,推荐使用此方式。Class.forName("类全名")
: 软编码,需要处理异常,对于自定义类,推荐使用此方式。
- 获取数组的Class对象:
-
- 判断数组的Class对象是否相同的时候,只比较它们的类型和维度。
public class qq {static class A {private int a1;private double a2;private String s3;}public static void main(String[] args) throws ClassNotFoundException {A my = new A();Class<?> aClass = my.getClass();System.out.println("aClass -> " + aClass); System.out.println("aClass.getName() -> " + aClass.getName()); System.out.println("aClass.hashCode() -> " + aClass.hashCode()); System.out.println("my.getClass() -> " + my.getClass());System.out.println("my.getClass() -> " + my.getClass().getName());System.out.println("my.getClass().hashCode() -> " + my.getClass().hashCode());System.out.println("A.class -> " + A.class);System.out.println("A.class -> " + A.class.getName());System.out.println("A.class.hashCode() -> " + A.class.hashCode());}
}