一、接口和抽象类的区别

接口(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,不能是 privateprotected(Java 9 允许 private 方法)。
  • 抽象类
    方法可以自由定义访问修饰符(如 public, protected, private)。

6. 设计目的

  • 接口:定义行为规范(“能做什么”),强调多态性,如 Runnable(可运行)、Comparable(可比较)。代表一种契约(实现接口的类必须履行接口定义的功能)。
  • 抽象类
    定义共性逻辑(“是什么”),用于代码复用,如 Animal 抽象类可以包含所有动物的共同属性和方法。
    代表一种层级关系(子类与抽象类是“is-a”关系)。

使用场景示例

  • 接口:需要让无关的类具备相同行为时使用接口。例如:Flyable 接口可以被 BirdAirplane 等不同类实现。
  • 抽象类
    需要为相关类提供公共代码时使用抽象类。
    例如:Vehicle 抽象类可以定义 startEngine() 方法,供 CarMotorcycle 等子类复用。

总结

特性 接口 抽象类
继承方式 多继承 单继承
方法实现 默认抽象,支持默认方法 可包含抽象和具体方法
变量 只能是常量 可以是普通变量或常量
构造器 有(供子类调用)
设计目的 定义行为规范(契约) 代码复用和层次化设计

选择依据

  • 如果关注行为的多态性(不同类共享行为),优先用接口。
  • 如果关注代码复用和层级关系,优先用抽象类。

二、 反射机制

反射(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. 注意事项

  • 性能优化反射调用比直接调用慢,可通过缓存 ClassMethod等对象减少开销。
  • 安全性反射可绕过访问修饰符,修改 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");
    }
}