一、接口和抽象类的区别
接口(Interface)和抽象类(Abstract Class)是面向对象编程中两种重要的抽象机制,它们的核心区别体现在设计目的和使用场景上。以下是两者的主要区别:
1. 继承方式
- 接口:一个类可以实现多个接口(多继承),通过
implements
关键字。例如:class Dog implements Runnable, Swimmable { ... }
- 抽象类:
一个类只能继承一个抽象类(单继承),通过extends
关键字。
例如:class Dog extends Animal { ... }
2. 方法的实现
接口:
- Java 8 之前:接口中的方法只能是抽象方法(无方法体)。
- Java 8 之后:接口可以包含
default
方法(有默认实现)和static
方法。 - Java 9 之后:接口可以包含
private
方法。
public interface Flyable {
void fly(); // 抽象方法(默认 public abstract)
default void land() { // 默认方法
System.out.println("Landing...");
}
}
抽象类:
- 可以包含抽象方法(无实现)和具体方法(有实现)。
- 抽象类更倾向于提供部分通用实现。
public abstract class Animal {
public abstract void eat(); // 抽象方法
public void sleep() { // 具体方法
System.out.println("Sleeping...");
}
}
3. 成员变量
接口:
变量默认是 public static final
(常量),必须初始化。
public interface Constants {
int MAX_SPEED = 100; // 等同于 public static final int MAX_SPEED = 100;
}
抽象类:
可以定义普通成员变量、静态变量或常量,支持多种访问修饰符(如 private
, protected
)。
4. 构造器
- 接口:没有构造器,不能被实例化。
- 抽象类:
可以有构造器(供子类初始化时调用),但不能直接实例化。
5. 访问修饰符
- 接口:方法默认是
public
,不能是private
或protected
(Java 9 允许private
方法)。 - 抽象类:
方法可以自由定义访问修饰符(如public
,protected
,private
)。
6. 设计目的
- 接口:定义行为规范(“能做什么”),强调多态性,如
Runnable
(可运行)、Comparable
(可比较)。代表一种契约(实现接口的类必须履行接口定义的功能)。 - 抽象类:
定义共性逻辑(“是什么”),用于代码复用,如Animal
抽象类可以包含所有动物的共同属性和方法。
代表一种层级关系(子类与抽象类是“is-a”关系)。
使用场景示例
- 接口:需要让无关的类具备相同行为时使用接口。例如:
Flyable
接口可以被Bird
、Airplane
等不同类实现。 - 抽象类:
需要为相关类提供公共代码时使用抽象类。
例如:Vehicle
抽象类可以定义startEngine()
方法,供Car
、Motorcycle
等子类复用。
总结
特性 | 接口 | 抽象类 |
---|---|---|
继承方式 | 多继承 | 单继承 |
方法实现 | 默认抽象,支持默认方法 | 可包含抽象和具体方法 |
变量 | 只能是常量 | 可以是普通变量或常量 |
构造器 | 无 | 有(供子类调用) |
设计目的 | 定义行为规范(契约) | 代码复用和层次化设计 |
选择依据:
- 如果关注行为的多态性(不同类共享行为),优先用接口。
- 如果关注代码复用和层级关系,优先用抽象类。
二、 反射机制
反射(Reflection)是Java语言的一种特性,允许程序在运行时动态地获取类的信息并操作类或对象的属性、方法和构造器。它打破了传统代码的静态性,提供了高度的灵活性,但也带来了性能和安全性的考量。
1. 反射的核心类
java.lang.Class
所有反射操作的入口,表示一个类或接口的元数据。
获取方式:
// 方式1:通过类名.class
Class<String> stringClass = String.class;
// 方式2:通过对象.getClass()
String str = "Hello";
Class<?> strClass = str.getClass();
// 方式3:通过Class.forName("全限定类名")
Class<?> clazz = Class.forName("java.lang.String");
2. 反射的核心操作
2.1 获取构造器并创建对象
// 获取所有公共构造器
Constructor<?>[] publicConstructors = clazz.getConstructors();
// 获取所有构造器(包括私有)
Constructor<?>[] allConstructors = clazz.getDeclaredConstructors();
// 获取特定构造器(以String为例,参数为String)
Constructor<String> constructor = String.class.getConstructor(String.class);
// 创建实例(需处理异常)
String instance = constructor.newInstance("Hello");
2.2 获取并调用方法
// 获取所有公共方法(包括继承的)
Method[] publicMethods = clazz.getMethods();
// 获取所有方法(包括私有,但不包括继承的)
Method[] allMethods = clazz.getDeclaredMethods();
// 获取指定方法(方法名+参数类型)
Method method = clazz.getDeclaredMethod("methodName", int.class, String.class);
// 调用方法(需实例和参数)
method.setAccessible(true); // 若方法是私有的
Object result = method.invoke(instance, 123, "abc");
2.3 获取并操作字段
// 获取所有公共字段
Field[] publicFields = clazz.getFields();
// 获取所有字段(包括私有)
Field[] allFields = clazz.getDeclaredFields();
// 获取指定字段(以私有字段name为例)
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true); // 访问私有字段
// 读取/设置字段值
Object value = nameField.get(instance); // 获取值
nameField.set(instance, "New Name"); // 设置值
3. 反射的应用场景
- 动态代理如JDK动态代理通过
Proxy.newProxyInstance()
和InvocationHandler
实现,底层依赖反射调用方法。 - 框架开发如Spring通过反射实例化Bean、注入依赖,处理注解(如
@Autowired
)。 - 注解处理如JUnit通过反射查找
@Test
注解的方法并执行测试。 - 泛型擦除后的类型推断
通过反射获取方法参数的实际类型(如Method.getGenericParameterTypes()
)。
4. 反射的优缺点
优点 | 缺点 |
---|---|
动态加载类,提高灵活性 | 性能开销大(比直接调用慢) |
实现通用框架和工具(如Spring) | 破坏封装性(可访问私有成员) |
支持运行时类型检查和操作 | 代码复杂度高,易引发运行时异常(如 NoSuchMethodException ) |
5. 注意事项
- 性能优化反射调用比直接调用慢,可通过缓存
Class
、Method
等对象减少开销。 - 安全性反射可绕过访问修饰符,修改
final
字段或调用私有方法,需谨慎使用。 - 替代方案
优先考虑接口、抽象类或设计模式(如工厂模式),避免过度依赖反射。
6. 示例代码:反射调用私有方法
public class ReflectionExample {
public static void main(String[] args) throws Exception {
// 获取Class对象
Class<?> clazz = Class.forName("com.example.Person");
// 获取私有方法
Method privateMethod = clazz.getDeclaredMethod("privateMethod", String.class);
privateMethod.setAccessible(true); // 允许访问私有方法
// 创建实例并调用方法
Object instance = clazz.getDeclaredConstructor().newInstance();
privateMethod.invoke(instance, "Hello via Reflection!");
}
}
// Person类定义
class Person {
private void privateMethod(String message) {
System.out.println("Private method called: " + message);
}
}
三、动态代理
Proxy.newProxyInstance()
方法用于创建动态代理对象,其三个参数分别承担不同的角色,共同决定了代理对象的行为和特性。以下是每个参数的详细解释:
1. ClassLoader loader
:类加载器
作用:负责加载动态生成的代理类字节码到 JVM 中。
关键点:
- 通常使用 目标类(被代理类)的类加载器(如
target.getClass().getClassLoader()
),确保代理类与目标类在同一个类加载环境中。 - 如果传递了不兼容的类加载器(如系统类加载器),可能导致
ClassNotFoundException
。
示例:
UserServiceImpl target = new UserServiceImpl();
ClassLoader loader = target.getClass().getClassLoader(); // 使用目标类的类加载器
2. Class<?>[] interfaces
:接口数组
作用:指定代理对象需要实现的接口列表,决定了代理对象可以调用哪些方法。
关键点:
- 必须是被代理对象实现的所有接口(如
target.getClass().getInterfaces()
)。 - 动态代理只能基于接口生成代理类,无法直接代理具体类(需用 CGLIB)。
示例:
Class<?>[] interfaces = target.getClass().getInterfaces(); // 获取目标类实现的所有接口
3. InvocationHandler h
:调用处理器
作用:定义代理对象方法的调用逻辑(如日志、事务等),所有方法调用都会转发到它的 invoke()
方法。
关键点:
- 需要自定义一个
InvocationHandler
实现类(如LogHandler
)。 invoke()
方法中通过反射调用目标方法(method.invoke(target, args)
)。
示例:
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("方法调用前");
Object result = method.invoke(target, args); // 调用目标方法
System.out.println("方法调用后");
return result;
}
};
完整示例代码
public class DynamicProxyDemo {
public static void main(String[] args) {
// 目标对象
UserService target = new UserServiceImpl();
// 1. 类加载器:使用目标类的类加载器
ClassLoader loader = target.getClass().getClassLoader();
// 2. 接口数组:目标类实现的所有接口
Class<?>[] interfaces = target.getClass().getInterfaces();
// 3. 调用处理器:定义代理逻辑
InvocationHandler handler = (proxy, method, argsArray) -> {
System.out.println("【日志】调用方法: " + method.getName());
return method.invoke(target, argsArray); // 反射调用目标方法
};
// 生成代理对象
UserService proxy = (UserService) Proxy.newProxyInstance(
loader,
interfaces,
handler
);
// 通过代理对象调用方法
proxy.saveUser("Alice");
}
}