2022最新Java泛型详解(360度无死角介绍)
什么是Java泛型?
Java泛型是Java SE 5.0版本中的新特性,提供了一种对类型进行参数化的机制,让代码的重用性和类型安全性都得到了极大的提高。
泛型主要有以下特点:
- 提高代码的可读性和可维护性
- 在编译期进行类型检查,提高代码的安全性
- 可以适用于各种类型,提高代码的重用性
如何使用Java泛型?
Java泛型主要包括以下几种用法:
- 泛型类:使用泛型来定义类
- 泛型接口:使用泛型来定义接口
- 泛型方法:使用泛型来定义方法
- 泛型边界:限定泛型类型的范围
泛型类
public class MyGenericClass<T> {
private T data;
public MyGenericClass(T data) {
this.data = data;
}
public T getData() {
return data;
}
}
上面的代码中,MyGenericClass
是一个泛型类,用 T
来表示类型参数。在类的实例化时,可以指定具体类型,例如:
MyGenericClass<String> stringGeneric = new MyGenericClass<>("hello world");
String data = stringGeneric.getData(); // 返回值类型为String
泛型接口
public interface MyGenericInterface<T> {
T processData(T input);
}
上面的代码中,MyGenericInterface
是一个泛型接口,用 T
来表示类型参数。实现该接口时,需要指定具体类型,例如:
public class MyGenericImpl implements MyGenericInterface<String> {
@Override
public String processData(String input) {
return input.toUpperCase();
}
}
MyGenericImpl impl = new MyGenericImpl();
String result = impl.processData("hello world"); // 返回值为"HELLO WORLD"
泛型方法
public class MyGenericMethod {
public static <T> T processData(T input) {
// 处理逻辑
return input;
}
}
上面的代码中,MyGenericMethod
是一个包含泛型方法的类,其中使用 T
来表示类型参数。调用该方法时,可以指定具体类型,例如:
String result = MyGenericMethod.<String>processData("hello world"); // 返回值为"hello world"
不指定具体类型时,编译器会自动根据传入的参数类型推断出类型参数。
泛型边界
public class MyGenericBound<T extends Number> {
private T data;
public MyGenericBound(T data) {
this.data = data;
}
public T getData() {
return data;
}
public void plusData(T input) {
data = (T) new Double(data.doubleValue() + input.doubleValue()); // 这里的强制类型转换有点奇怪,仅为了示例
}
}
上面的代码中,MyGenericBound
是一个泛型类,使用 T extends Number
来表示类型参数必须是 Number
的子类型。在类的实例化时,只能指定 Number
的子类类型,例如:
MyGenericBound<Integer> integerGeneric = new MyGenericBound<>(10);
integerGeneric.plusData(5); // 实际上执行的是10 + 5
Integer data = integerGeneric.getData(); // 返回值为15
如何避免Java泛型的坑点?
Java泛型使用时,有些坑点需要避免,例如类型擦除、泛型数组等问题。下面简单介绍一下如何避免这些问题。
类型擦除
Java泛型在编译期间会进行类型擦除,从而避免类型安全问题。但是,在某些情况下,泛型类型擦除会给代码带来一些坑。比如:
public class MyGenericClass<T> {
private T data;
public MyGenericClass(T data) {
this.data = data;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
MyGenericClass<Integer> integerInstance = new MyGenericClass<>(10);
使用泛型类时,在实例化时指定了类型参数 Integer
,则在编译期间,MyGenericClass
中的 T
类型会被擦除成 Object
类型。而 setData
方法中的参数类型是泛型类型 T
,也会被擦除成 Object
类型。因此,使用该方法设置一个 String
类型的参数是合法的,但是在调用 getData
方法时就会抛出 ClassCastException
异常。
为了避免这个问题,可以采用以下三种方式:
- 在方法中增加类型参数
public <U extends T> void setData(U data) {
this.data = data;
}
- 通过构造函数传入类型信息
public MyGenericClass(Class<T> clazz, T data) {
this.data = clazz.cast(data);
}
- 使用通配符
public void setData(? extends T data) {
this.data = data;
}
上述方式中,第一种方式在方法定义时增加了类型参数,并指定类型参数必须是 T
的子类型;第二种方式通过额外传入一个 Class
类型的参数来保存类型信息;第三种方式通过通配符避免了在方法中使用泛型类型参数的问题。
泛型数组
Java中的泛型数组是被禁止的,因为泛型数组在编译期间会被擦除掉,这样就很容易导致类型安全问题。如果想要使用泛型数组,可以换用列表等容器类型来替代。例如,下面的代码就可以通过使用列表来替代数组来实现:
List<Integer> integerList = new ArrayList<>();
integerList.add(1);
integerList.add(2);
示例
在实际的开发中,Java泛型的使用场景是非常多的。下面给出一个示例,演示了如何使用泛型来实现一个通用的线程池。
public class MyThreadPool<T extends Runnable> {
private int threadsNum;
private ExecutorService executorService;
public MyThreadPool(int threadsNum) {
this.threadsNum = threadsNum;
this.executorService = Executors.newFixedThreadPool(threadsNum);
}
public void addTask(T task) {
executorService.submit(task);
}
public int getThreadsNum() {
return threadsNum;
}
public void shutdown() {
executorService.shutdown();
}
public static void main(String[] args) {
MyThreadPool<Runnable> threadPool = new MyThreadPool<>(10);
threadPool.addTask(() -> {
System.out.println("Hello world");
});
}
}
上面的代码中,MyThreadPool
是一个泛型类,使用 T extends Runnable
来表示类型参数必须是 Runnable
的子类型。在 main 方法中,可以向线程池中添加任务,即将一个实现了 Runnable
接口的对象传入 addTask
方法中。
这样,就可以通过泛型来实现一个通用的线程池,可以接受任意实现了 Runnable
接口的对象。
总结
本文对Java泛型进行了详细的介绍,包括泛型类、泛型接口、泛型方法和泛型边界等方面。在避免Java泛型坑点时,应当注意类型擦除和泛型数组相关的问题。通过以上示例,我们可以看到,Java泛型的使用场景非常丰富,使用泛型可以大大提高代码的重用性和安全性。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:2022最新Java泛型详解(360度无死角介绍) - Python技术站