Java高级(泛型&反射)

Java高级(泛型&反射)

泛型

Java 泛型是什么?

Java 泛型(Generics) 是 Java 1.5 引入的一项特性,允许在定义类、接口和方法时使用类型参数,从而提高代码的类型安全性可读性复用性。泛型通过在编译时进行类型检查,避免了运行时类型转换错误(如 ClassCastException),同时减少了显式的类型转换代码。

核心目标

  1. 类型安全: 确保集合或其他结构只存储特定类型的对象。
  2. 代码复用: 编写通用的类或方法,适用于多种数据类型。
  3. 简化代码: 消除不必要的类型转换,提高代码可读性。

泛型的基本概念

类型参数

  • 通常使用大写字母表示,如 T(Type)、E(Element)、K(Key)、V(Value)。
  • 可以有多个类型参数,如 <K, V>

T(Type)

  • 含义: 表示通用类型,通常用于表示任意类型或抽象类型。

  • 使用场景:

    • 定义通用的类、接口或方法,适用于不特定于某种具体语义的场景。
    • 常用于泛型类、泛型方法或工具类,强调类型的通用性。
    • 当类型参数没有明确的语义(如集合元素、键值对)时,T 是默认选择。
  • 典型场景:

    • 泛型容器类。

    • 通用工具方法(如数组操作、类型转换)。

    • 单个类型参数的场景。

E(Element)

  • 含义: 表示集合中的元素类型,强调类型是集合(如 List、Set、Queue 等)中的个体元素。

  • 使用场景:

    • 常用于集合框架相关的泛型类或方法。

    • 适用于表示列表、数组、集合等数据结构的元素。

    • 在 Java 集合框架中(如 List、Set),E 是标准命名。

  • 典型场景:

    • 定义集合类(如链表、队列)。

    • 处理集合的遍历、添加、删除等操作。

    • 集合相关的算法(如排序、过滤)。

K(Key)和 V(Value)

  • 含义:

    • K 表示键(Key),V 表示值(Value)。

    • 通常成对使用,表示键值对结构(如 Map)。

  • 使用场景:

    • 常用于键值对相关的泛型类或方法。

    • 适用于映射结构(如 Map、Dictionary)或键值对操作。

    • 在 Java 集合框架中,Map<K, V> 是典型代表。

  • 典型场景:

    • 定义映射类(如哈希表、树映射)。

    • 处理键值对的增删改查。

    • 键值对相关的算法(如分组、统计)。


如何使用泛型?

1. 使用泛型类

泛型类在实例化时指定具体类型。

public class Box<T> {
    private T value;
    public void setValue(T value) {
        this.value = value;
    }
    public T getValue() {
        return value;
    }
}


Box<Integer> intBox = new Box<>();
intBox.setValue(10);
Integer value = intBox.getValue(); // 无需强制转换
System.out.println(value); // 输出: 10

Box<String> strBox = new Box<>();
strBox.setValue("Hello");
String str = strBox.getValue();
System.out.println(str); // 输出: Hello
  • 错误示例(类型安全):

    Box<Integer> intBox = new Box<>();
    intBox.setValue("Hello"); // 编译错误,类型不匹配
    

2. 使用泛型接口

实现泛型接口时,指定具体类型或保持泛型。

public interface Container<T> {
    void add(T item);
    T get();
}


public class StringContainer implements Container<String> {
    private String item;
    @Override
    public void add(String item) {
        this.item = item;
    }
    @Override
    public String get() {
        return item;
    }
}

Container<String> container = new StringContainer();
container.add("Test");
System.out.println(container.get()); // 输出: Test

3. 使用泛型方法

泛型方法可以独立于泛型类,在调用时自动推断类型。

public <T> void printArray(T[] array) {
    for (T item : array) {
        System.out.println(item);
    }
}


public class Util {
    public static <T> void printArray(T[] array) {
        for (T item : array) {
            System.out.println(item);
        }
    }
}

String[] strArray = {"A", "B", "C"};
Integer[] intArray = {1, 2, 3};
Util.printArray(strArray); // 输出: A B C
Util.printArray(intArray); // 输出: 1 2 3

4. 使用通配符

  • ? 表示未知类型,常用于限制类型范围。

  • 上界通配符: <? extends Type>(类型是 Type 或其子类)。

  • 下界通配符: <? super Type>(类型是 Type 或其父类)。

  • 无界通配符: <?>(任意类型)。

  • 上界通配符<? extends Type>):

    • 用于只读场景,限制类型为 Type 或其子类。

    • 示例:

      public void printList(List<? extends Number> list) {
          for (Number n : list) {
              System.out.println(n);
          }
      }
      
      List<Integer> intList = Arrays.asList(1, 2, 3);
      List<Double> doubleList = Arrays.asList(1.1, 2.2);
      printList(intList); // 有效
      printList(doubleList); // 有效
      
  • 下界通配符<? super Type>):

    • 用于写入场景,限制类型为 Type 或其父类。

    • 示例:

      public void addNumbers(List<? super Integer> list) {
          list.add(1);
          list.add(2);
      }
      
      List<Number> numList = new ArrayList<>();
      addNumbers(numList); // 有效
      System.out.println(numList); // 输出: [1, 2]
      
  • 无界通配符<?>):

    • 用于任意类型,通常用于不关心具体类型的操作。

    • 示例:

      public void printAnyList(List<?> list) {
          System.out.println(list);
      }
      
      List<String> strList = Arrays.asList("A", "B");
      printAnyList(strList); // 输出: [A, B]
      

5. 泛型与集合

泛型在集合框架中应用广泛,确保集合元素类型一致。

List<String> list = new ArrayList<>();
list.add("Hello");
list.add("World");
// list.add(123); // 编译错误,类型不匹配
String item = list.get(0); // 无需强制转换
System.out.println(item); // 输出: Hello

泛型的高级用法

  1. 类型擦除:

    • Java 泛型在编译时会进行类型擦除,运行时不保留类型信息。
    • 编译后,List<String>List<Integer> 都变为 List
    • 因此,不能在运行时检查泛型类型(如 instanceof List<String>)。
  2. 限制类型参数:

    • 使用 extends 限制类型参数为特定类或接口。

    • 示例:

      public class Pair<K extends Comparable<K>, V> {
          private K key;
          private V value;
          public Pair(K key, V value) {
              this.key = key;
              this.value = value;
          }
          public K getKey() {
              return key;
          }
      }
      
  3. 泛型与反射:

    • 使用 Class<T> 或反射处理泛型类型。

    • 示例(结合问题中的方法签名):

      public void run(Integer taskId, Class<? extends TaskWorkFlowService> serviceClass) {
          try {
              TaskWorkFlowService service = serviceClass.getDeclaredConstructor().newInstance();
              service.execute(taskId);
          } catch (Exception e) {
              throw new RuntimeException("执行失败", e);
          }
      }
      

注意事项

  1. 不能实例化泛型类型:

    • 不能直接 new T(),因为类型擦除后 T 不可知。
    • 解决方法: 通过 Class<T> 或工厂方法传递类型信息。
  2. 泛型不支持基本类型:

    • 不能使用 List<int>,需用包装类 List<Integer>
  3. 静态上下文不能使用类型参数:

    • 泛型类型参数属于实例,不能在 static 方法或字段中直接使用。
  4. PECS 原则(Producer Extends, Consumer Super):

    • 使用 <? extends T> 用于生产者(只读)。
    • 使用 <? super T> 用于消费者(只写)。

反射

在Java中,反射(Reflection)是指在运行时动态获取类的信息(如类名、方法、字段、构造函数等)并操作这些信息的能力。Java的反射机制通过java.lang.reflect包提供支持,允许程序在运行时检查和修改类的结构、调用方法、访问字段或创建对象,即使这些信息在编译时未知。

反射的核心作用

  1. 运行时动态性:可以在运行时根据需要加载类、调用方法或访问字段。
  2. 灵活性:常用于框架(如Spring、Hibernate)中,动态处理未知的类或配置。
  3. 调试和测试:可以用来检查私有成员或绕过访问限制。

反射的主要类和接口

  • Class:表示类的元信息入口,通过它可以获取类的方法、字段、构造函数等。
  • Field:表示类的字段,可以获取或设置字段的值。
  • Method:表示类的方法,可以动态调用方法。
  • Constructor:表示类的构造函数,可以创建对象。
  • Modifier:用于解析访问修饰符(如public、private)。

如何使用反射

以下是反射的常见操作步骤和示例代码:

1. 获取Class对象

有三种方式获取Class对象:

// 方式1:通过类名
Class<?> clazz1 = String.class;

// 方式2:通过对象实例
String str = "Hello";
Class<?> clazz2 = str.getClass();

// 方式3:通过全限定类名(可能抛出ClassNotFoundException)
Class<?> clazz3 = Class.forName("java.lang.String");

2. 获取类的信息

通过Class对象可以获取类的构造函数、方法、字段等:

// 示例类
class Person {
    private String name;
    public int age;

    public Person() {}
    public Person(String name) { this.name = name; }

    public void sayHello() { System.out.println("Hello, " + name); }
    private void privateMethod() { System.out.println("Private method"); }
}

/**
   * 获取Class对象
   * 获取所有公共构造函数
   * 获取所有构造函数
   * 获取所有公共方法(包括继承的)
   * 获取本类的所有方法
   * 获取所有字段(仅公共字段)
   * 获取所有字段(包括私有)
   * 获取包名
   * 获取父类
   * 获取接口
   */
public class ReflectionDemo {
    public static void main(String[] args) throws Exception {
        // 获取Class对象
        Class<?> clazz = Person.class;

        // 获取所有公共构造函数
        Constructor<?>[] constructors = clazz.getConstructors();
        System.out.println("Constructors:");
        for (Constructor<?> c : constructors) {
            System.out.println(c);
        }
      
        //获取所有构造函数
        Constructor<?>[] constructors = clazz.getDeclaredConstructors();  
        System.out.println("Person构造函数:");
        for(Constructor<?> con : constructors) {  
          System.out.println("\t" + con);  //输出所有构造函数
        }

        // 获取所有公共方法(包括继承的)
        Method[] methods = clazz.getMethods();
        System.out.println("\nMethods:");
        for (Method m : methods) {
            System.out.println(m);
        }
      
        //获取本类的所有方法
        Method[] declaredMethods = clazz.getDeclaredMethods();  
        System.out.println("Person本类所有方法:");
        for (Method method : declaredMethods) {
          System.out.println("\t" + method);
        }

        // 获取所有字段(仅公共字段)
        Field[] fields = clazz.getFields();
        System.out.println("\nFields:");
        for (Field f : fields) {
            System.out.println(f);
        }

        // 获取所有字段(包括私有)
        Field[] allFields = clazz.getDeclaredFields();
        System.out.println("\nAll Fields:");
        for (Field f : allFields) {
            System.out.println(f);
        }
      
        //获取包名
        Package package = clazz.getPackage();  
        System.out.println("Person包名称:" + package.getName());
      
        //获取父类
        Class<?> parent = clazz.getSuperclass();  
        System.out.println("Person父类名称:" + parent.getName());
      
        //获取接口
        Class<?> [] interfaces = clazz.getInterfaces();  
        System.out.println("Person父接口:");
        for(Class<?> temp : interfaces) { 
          System.out.println("\t" + temp.getName());
        }
    }
}

3. 创建对象

使用反射通过构造函数创建对象:

// 获取构造函数
Constructor<?> constructor = clazz.getConstructor(String.class);
// 创建对象
Object person = constructor.newInstance("Alice");
System.out.println(person); // Person@...

4. 调用方法

动态调用类的方法:

// 获取方法
Method sayHello = clazz.getMethod("sayHello");
// 调用方法
sayHello.invoke(person); // 输出: Hello, Alice

// 调用私有方法
Method privateMethod = clazz.getDeclaredMethod("privateMethod");
privateMethod.setAccessible(true); // 绕过访问限制
privateMethod.invoke(person); // 输出: Private method

5. 访问和修改字段

动态获取或修改字段值:

// 获取字段
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true); // 绕过私有限制

// 设置字段值
nameField.set(person, "Bob");

// 获取字段值
String nameValue = (String) nameField.get(person);
System.out.println("Name: " + nameValue); // 输出: Name: Bob

反射的优缺点

优点

  • 提高灵活性,适合开发通用框架。
  • 支持动态加载类和方法,适合插件化开发。

缺点

  • 性能开销:反射操作比直接调用慢,因为涉及运行时解析。
  • 安全性问题:绕过访问限制可能破坏封装性。
  • 代码复杂性:反射代码难以阅读和维护,容易出错。

注意事项

  1. 异常处理:反射操作可能抛出多种异常(如ClassNotFoundExceptionNoSuchMethodExceptionIllegalAccessException等),需要妥善处理。
  2. 访问权限:私有成员需要通过setAccessible(true)绕过访问限制,但可能受到安全管理器限制。
  3. 性能优化:尽量缓存Class、方法或字段对象,避免重复获取。
  4. 谨慎使用:反射不适合高性能场景或简单逻辑,优先考虑常规方式。

典型应用场景

  • 框架开发:如Spring通过反射实例化Bean,Hibernate通过反射操作实体类字段。
  • 序列化/反序列化:如JSON库(如Gson、Jackson)通过反射解析对象。
  • 测试工具:如JUnit使用反射调用测试方法。
  • 动态代理:Java的动态代理机制依赖反射实现。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值