Java高级(泛型&反射)
文章目录
泛型
Java 泛型是什么?
Java 泛型(Generics) 是 Java 1.5 引入的一项特性,允许在定义类、接口和方法时使用类型参数,从而提高代码的类型安全性、可读性和复用性。泛型通过在编译时进行类型检查,避免了运行时类型转换错误(如 ClassCastException
),同时减少了显式的类型转换代码。
核心目标
- 类型安全: 确保集合或其他结构只存储特定类型的对象。
- 代码复用: 编写通用的类或方法,适用于多种数据类型。
- 简化代码: 消除不必要的类型转换,提高代码可读性。
泛型的基本概念
类型参数
- 通常使用大写字母表示,如
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
泛型的高级用法
-
类型擦除:
- Java 泛型在编译时会进行类型擦除,运行时不保留类型信息。
- 编译后,
List<String>
和List<Integer>
都变为List
。 - 因此,不能在运行时检查泛型类型(如
instanceof List<String>
)。
-
限制类型参数:
-
使用
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; } }
-
-
泛型与反射:
-
使用
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); } }
-
注意事项
-
不能实例化泛型类型:
- 不能直接
new T()
,因为类型擦除后T
不可知。 - 解决方法: 通过
Class<T>
或工厂方法传递类型信息。
- 不能直接
-
泛型不支持基本类型:
- 不能使用
List<int>
,需用包装类List<Integer>
。
- 不能使用
-
静态上下文不能使用类型参数:
- 泛型类型参数属于实例,不能在
static
方法或字段中直接使用。
- 泛型类型参数属于实例,不能在
-
PECS 原则(Producer Extends, Consumer Super):
- 使用
<? extends T>
用于生产者(只读)。 - 使用
<? super T>
用于消费者(只写)。
- 使用
反射
在Java中,反射(Reflection)是指在运行时动态获取类的信息(如类名、方法、字段、构造函数等)并操作这些信息的能力。Java的反射机制通过java.lang.reflect
包提供支持,允许程序在运行时检查和修改类的结构、调用方法、访问字段或创建对象,即使这些信息在编译时未知。
反射的核心作用
- 运行时动态性:可以在运行时根据需要加载类、调用方法或访问字段。
- 灵活性:常用于框架(如Spring、Hibernate)中,动态处理未知的类或配置。
- 调试和测试:可以用来检查私有成员或绕过访问限制。
反射的主要类和接口
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
反射的优缺点
优点
- 提高灵活性,适合开发通用框架。
- 支持动态加载类和方法,适合插件化开发。
缺点
- 性能开销:反射操作比直接调用慢,因为涉及运行时解析。
- 安全性问题:绕过访问限制可能破坏封装性。
- 代码复杂性:反射代码难以阅读和维护,容易出错。
注意事项
- 异常处理:反射操作可能抛出多种异常(如
ClassNotFoundException
、NoSuchMethodException
、IllegalAccessException
等),需要妥善处理。 - 访问权限:私有成员需要通过
setAccessible(true)
绕过访问限制,但可能受到安全管理器限制。 - 性能优化:尽量缓存
Class
、方法或字段对象,避免重复获取。 - 谨慎使用:反射不适合高性能场景或简单逻辑,优先考虑常规方式。
典型应用场景
- 框架开发:如Spring通过反射实例化Bean,Hibernate通过反射操作实体类字段。
- 序列化/反序列化:如JSON库(如Gson、Jackson)通过反射解析对象。
- 测试工具:如JUnit使用反射调用测试方法。
- 动态代理:Java的动态代理机制依赖反射实现。