Java学习第八部分——泛型
目录
一、概述
(一)定义
(二)作用
(三)引入原因
二、使用
(一)类
(二)接口
(三)方法
三、类型参数
(一)命名
(二)限制
四、类型擦除
(一)概念
(二)影响
五、通配符
(一)`?`(无界通配符)
(二)`? extends T`(上界通配符)
(三)`? super T`(下界通配符)
六、高级用法
(一)泛型数组
(二)泛型与反射
(三)泛型与匿名内部类
七、idea项目实战
(一)打开idea新建Java项目
(二)编写GenericCollectionUtil类
(三)创建测试类
(四)编译运行代码,注意切换到测试类文件
(五)运行结果如下
(六)代码解释如下
1. GenericCollectionUtil 类
2. GenericCollectionUtilTest`类
一、概述
(一)定义
Java泛型(Generics)是指在定义类、接口或方法时,可以指定一个或多个类型参数(Type Parameters),这些类型参数在使用时会被具体的类型替换。它允许程序员在编写代码时延迟确定数据类型,从而提高代码的复用性和安全性。
(二)作用
Java泛型是一种强大的特性,它可以让代码更加通用、安全和复用。通过泛型类、泛型接口和泛型方法,可以编写出能够处理多种类型数据的代码。同时,泛型的类型参数、类型擦除和通配符等概念也需要深入理解,以便正确使用泛型。
(三)引入原因
1. 类型安全
- 在没有泛型的情况下,集合类(如ArrayList)只能存储Object类型的对象。当从集合中取出元素时,需要进行强制类型转换。例如:
List list = new ArrayList();
list.add("Hello");
String str = (String) list.get(0);
这种方式存在类型安全问题,因为list可以存储任何类型的对象,如果添加了其他类型的对象,如`list.add(123);`,在取出时进行强制类型转换就会抛出`ClassCastException`异常。
- 使用泛型后,可以在编译阶段就检查类型是否正确。例如:
List<String> list = new ArrayList<String>();
list.add("Hello");
String str = list.get(0); // 不需要强制类型转换
如果尝试添加非String类型的对象,如`list.add(123);`,编译器会报错。
2. 代码复用
- 泛型可以让我们编写更加通用的代码。例如,一个泛型类可以处理多种类型的数据,而不需要为每种数据类型都编写一个类。
二、使用
(一)类
1. 定义泛型类
- 泛型类的定义格式为:
class 类名<类型参数> {
// 类的成员可以使用类型参数
}
- 例如,定义一个简单的泛型类Box:
public class Box<T> {
private T t;
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
}
2. 使用泛型类
- 使用时需要指定具体的类型参数。例如:
Box<String> stringBox = new Box<String>();
stringBox.set("Hello");
String str = stringBox.get();
Box<Integer> integerBox = new Box<Integer>();
integerBox.set(123);
Integer num = integerBox.get();
(二)接口
1. 定义泛型接口
- 泛型接口的定义格式与泛型类类似:
interface 接口名<类型参数> {
// 接口的成员可以使用类型参数
}
- 例如,定义一个泛型接口Comparable:
public interface Comparable<T> {
int compareTo(T o);
}
2. 实现泛型接口
- 实现泛型接口时,可以指定具体的类型参数,也可以不指定。例如:
public class Person implements Comparable<Person> {
private String name;
public Person(String name) {
this.name = name;
}
@Override
public int compareTo(Person o) {
return this.name.compareTo(o.name);
}
}
(三)方法
1. 定义泛型方法
- 泛型方法的定义格式为:
<类型参数> 返回值类型 方法名(参数列表) {
// 方法体
}
- 例如,定义一个泛型方法swap:
public <T> void swap(T[] array, int i, int j) {
T temp = array[i];
array[i] = array[j];
array[j] = temp;
}
2. 使用泛型方法
- 调用泛型方法时,可以省略类型参数,编译器会自动推断。例如:
String[] strArray = {"a", "b"};
swap(strArray, 0, 1);
三、类型参数
(一)命名
- 通常使用单个大写字母作为类型参数的名称,常见的有:
- `T`:Type
- `E`:Element
- `K`:Key
- `V`:Value
- `N`:Number
(二)限制
1. 有界类型参数
- 可以通过`extends`关键字对类型参数进行限制,使其只能是某个类或接口的子类型。例如:
public <T extends Number> void print(T t) {
System.out.println(t);
}
这样,`T`只能是`Number`或其子类的实例。
2. 无界类型参数
- 如果没有对类型参数进行限制,那么它就是一个无界类型参数,可以是任何类型。例如:
public <T> void print(T t) {
System.out.println(t);
}
四、类型擦除
(一)概念
- 在Java中,泛型是通过类型擦除实现的。也就是说,在运行时,泛型类型会被擦除,所有的类型参数都会被替换为它们的边界(如果有边界的话),如果没有边界,则替换为`Object`。
- 例如,`List<String>`和`List<Integer>`在运行时都会被擦除为`List`。
(二)影响
1.无法获取泛型类型参数
- 由于类型擦除,无法在运行时获取泛型类型参数的具体类型。例如:
List<String> list = new ArrayList<String>();
System.out.println(list.getClass().getTypeParameters()); // 输出的是一个空的TypeVariable数组
2.泛型方法的重载
- 由于类型擦除,泛型方法的签名在运行时是相同的,因此不能仅根据类型参数的不同来重载泛型方法。例如:
public <T> void print(T t) {
System.out.println(t);
}
public <E> void print(E e) {
System.out.println(e);
}
这是不允许的,因为编译器无法区分这两个方法。
五、通配符
(一)`?`(无界通配符)
- 表示任意类型。例如:
public void printList(List<?> list) {
for (Object obj : list) {
System.out.println(obj);
}
}
这个方法可以接受任何类型的`List`作为参数。
(二)`? extends T`(上界通配符)
- 表示类型参数是`T`或`T`的子类型。例如:
public void printNumberList(List<? extends Number> list) {
for (Number num : list) {
System.out.println(num);
}
}
这个方法可以接受`List<Number>`、`List<Integer>`、`List<Double>`等作为参数。
(三)`? super T`(下界通配符)
- 表示类型参数是`T`或`T`的父类型。例如:
public void addNumberToList(List<? super Integer> list) {
list.add(123);
}
这个方法可以接受`List<Integer>`、`List<Number>`、`List<Object>`等作为参数。
六、高级用法
(一)泛型数组
- 不能创建泛型数组,例如`new T[10]`是不允许的。但是可以通过其他方式来实现类似的功能。例如:
public <T> T[] createArray(Class<T> clazz, int size) {
return (T[]) Array.newInstance(clazz, size);
}
(二)泛型与反射
- 可以通过反射来获取泛型类型参数。例如:
Type type = new TypeReference<List<String>>() {}.getType();
if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
System.out.println(actualTypeArguments[0]); // 输出:class java.lang.String
}
(三)泛型与匿名内部类
- 可以在匿名内部类中使用泛型。例如:
public static void main(String[] args) {
List<String> list = new ArrayList<String>() {
{
add("Hello");
}
};
System.out.println(list.get(0));
}
七、idea项目实战
(一)打开idea新建Java项目
ps:如果需要,可以在pom.xml
文件中添加其他依赖。对于这个简单的项目不需要额外的依赖。
(二)编写GenericCollectionUtil
类
package com.example.util;import java.util.ArrayList;
import java.util.List;public class GenericCollectionUtil<T> {private List<T> list = new ArrayList<>();/*** 添加元素到集合** @param element 要添加的元素*/public void add(T element) {list.add(element);}/*** 获取集合中的元素** @param index 元素的索引* @return 索引对应的元素*/public T get(int index) {return list.get(index);}/*** 清空集合*/public void clear() {list.clear();}/*** 获取集合大小** @return 集合的大小*/public int size() {return list.size();}@Overridepublic String toString() {return list.toString();}
}
(三)创建测试类
package com.example.util;public class GenericCollectionUtilTest {public static void main(String[] args) {// 测试字符串集合GenericCollectionUtil<String> stringUtil = new GenericCollectionUtil<>();stringUtil.add("Hello");stringUtil.add("World");System.out.println("String Collection: " + stringUtil);System.out.println("Element at index 0: " + stringUtil.get(0));stringUtil.clear();System.out.println("After clear: " + stringUtil);// 测试整数集合GenericCollectionUtil<Integer> intUtil = new GenericCollectionUtil<>();intUtil.add(1);intUtil.add(2);intUtil.add(3);System.out.println("Integer Collection: " + intUtil);System.out.println("Element at index 1: " + intUtil.get(1));intUtil.clear();System.out.println("After clear: " + intUtil);}
}
(四)编译运行代码,注意切换到测试类文件
ps:红色箭头所示两种点击都可以实现
(五)运行结果如下
(六)代码解释如下
1. GenericCollectionUtil 类
这个类是一个泛型工具类,用于处理不同类型的数据集合操作。它使用了 Java 的泛型特性,使得同一个类可以处理多种类型的数据。
成员变量
- `private List<T> list = new ArrayList<>();`
- 这是一个泛型列表,用于存储集合中的元素。`T` 是一个类型参数,表示集合中元素的类型。
方法
- `public void add(T element)`
- 这是一个泛型方法,用于向集合中添加元素。`element` 是要添加的元素,其类型由 `T` 决定。
- `public T get(int index)`
- 这是一个泛型方法,用于从集合中获取指定索引位置的元素。返回值的类型也是 `T`。
- `public void clear()`
- 这是一个非泛型方法,用于清空集合中的所有元素。
- `public int size()`
- 这是一个非泛型方法,用于获取集合中元素的数量。
- `@Override public String toString()`
- 这是一个重写的方法,用于返回集合的字符串表示形式。它直接返回 `list.toString()`,即集合中所有元素的字符串表示。
2. GenericCollectionUtilTest`类
这个类是一个测试类,用于验证 `GenericCollectionUtil` 类的功能。
主要逻辑
- 创建 `GenericCollectionUtil` 类的实例,分别用于字符串和整数类型的集合。
- 向集合中添加元素,并打印集合的字符串表示形式。
- 获取并打印指定索引位置的元素。
- 清空集合,并打印清空后的集合表示形式。
输出示例
- 打印字符串集合和整数集合的初始状态。
- 打印指定索引位置的元素。
- 打印清空集合后的状态。
优点
- **泛型使用**:通过使用泛型,代码更加通用和灵活,可以处理多种类型的数据集合。
- **简洁性**:代码简洁明了,易于理解和维护。
- **功能明确**:每个方法的功能都很明确,易于使用。
缺点
- **功能有限**:目前只实现了添加元素、获取元素、清空集合和获取集合大小的功能,还可以扩展更多功能,如删除元素、查找元素等。
- **异常处理**:在获取元素时,如果索引超出范围,会抛出 `IndexOutOfBoundsException` 异常。可以考虑添加异常处理逻辑,提高代码的健壮性。
扩展功能建议
- **删除元素**:添加一个方法,用于从集合中删除指定索引位置的元素。
- **查找元素**:添加一个方法,用于查找集合中是否存在指定的元素。
- **排序**:添加一个方法,用于对集合中的元素进行排序。
- **过滤**:添加一个方法,用于对集合中的元素进行过滤,返回满足条件的元素集合。