本文纲要
- 泛型概述
不写泛型的弊端
泛型的好处 - 泛型类的使用
以ArrayList为例
源码解析 - 自定义泛型类
定义格式
案例:Box类 - 泛型方法的使用
ArrayList中的toArray方法
空参 vs 带参 - 自定义泛型方法
定义格式
案例:批量添加元素 - 泛型接口
定义格式
两种使用方式 - 通配符
无界通配符<?>
上界限定<? extends 类型>
下界限定<? super 类型>
示例项目结构
本文所有代码均位于 mygenericity 项目中,包结构如下:
mygenericity/src/com/wb/
├── genericitysummarize/
│ ├── GenericitySummarize.java
│ └── GenericityClass.java
├── genericityclass/
│ ├── Box.java
│ └── MyGenericityClass.java
├── genericitymethod/
│ ├── GenericityMethod1.java
│ └── GenericityMethod2.java
├── genericityinterface/
│ └── GenericityInterface.java
└── genericityglobbing/
└── genericityglobbing1.java
泛型概述
在创建集合时,如果不指定泛型,集合中元素的类型会被当作 Object,虽然数据本身可以正常存储,但在取出时会丢失具体类型信息。
不写泛型的弊端演示:
public class GenericitySummarize {
public static void main(String[] args) {
ArrayList list = new ArrayList(); // 没有泛型
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add(123); // 添加Integer也没问题
Iterator it = list.iterator();
while (it.hasNext()) {
// 取出时是Object,必须强转
String next = (String) it.next(); // 当取到123时会抛出ClassCastException
int len = next.length();
System.out.println(len);
}
}
}
问题总结:
集合中可以随意添加不同类型数据(都是 Object)。
取出时必须强转,如果类型不一致,会在运行时抛出 ClassCastException。
代码冗余,所有需要强转的地方都得自己处理。
泛型的好处:
- 将运行时的错误提前到编译期(添加元素时就会类型检查)。
- 避免了强制类型转换,代码更简洁安全。
泛型类的使用
Java中已定义好的泛型类最典型的代表就是 ·ArrayList·。我们可以查看源码发现 ·ArrayList· 的声明:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable { ... }
这里的 <E> 代表一个未知的类型,会在创建对象时被确定。
public class GenericityClass {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>(); // 此时 E 被明确为 String
list.add("hello");
String s = list.get(0); // 直接得到String,无需强转
}
}
ArrayList 中的 add(E e) 和 get(int index): E 等方法里的 E 都会随着创建对象时的具体类型参数而变化。这样设计让集合类可以适应任意数据类型,同时保证类型安全。
自定义泛型类
定义格式
修饰符 class 类名<泛型标识> { ... }
泛型标识可以是任意符合变量命名规则的字母,常见的有 E(Element)、T(Type)、K(Key)、V(Value)等。
案例:Box类
定义一个万能盒子,可以存放任意类型的元素:
// Box.java
public class Box<E> {
private E element;
public E getElement() {
return element;
}
public void setElement(E element) {
this.element = element;
}
}
使用方式:
// MyGenericityClass.java
public class MyGenericityClass {
public static void main(String[] args) {
// 存放字符串
Box<String> box1 = new Box<>();
box1.setElement("给小丽的土味情话");
String element1 = box1.getElement();
System.out.println(element1);
// 存放整数
Box<Integer> box2 = new Box<>();
box2.setElement(19);
Integer element2 = box2.getElement();
System.out.println(element2);
}
}
在创建 Box 对象时,指定的类型会传递给类中的 E,从而使 element 和相应的 get/set 方法拥有具体的类型。
泛型方法的使用
Java内置的 ArrayList 就提供了泛型方法的典型应用:toArray。
public class GenericityMethod1 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("给小花同学的土味情话");
list.add("给小丽同学的土味情话");
list.add("给小路同学的土味情话");
// 空参:返回Object[]
Object[] objects = list.toArray();
System.out.println(Arrays.toString(objects));
// 带参数的泛型方法:返回指定类型的数组
String[] strings = list.toArray(new String[list.size()]);
System.out.println(Arrays.toString(strings));
}
}
空参的 toArray() 返回的是 Object[],想使用字符串方法仍需强转。
带参的 toArray(T[] a) 会根据传入的数组类型返回相同类型的数组,避免了强转。
这里的 T 就是泛型方法中定义的类型参数。
自定义泛型方法
定义格式
修饰符 <泛型标识> 返回值类型 方法名(参数列表) { ... }
泛型标识必须声明在返回值之前,否则编译器会认为它是一个具体的类名。
案例:批量添加元素
定义一个可以批量添加四个元素到集合中并返回集合的方法:
public class GenericityMethod2 {
public static void main(String[] args) {
ArrayList<String> list1 = addElement(new ArrayList<String>(), "a", "b", "c", "d");
System.out.println(list1);
ArrayList<Integer> list2 = addElement(new ArrayList<Integer>(), 1, 2, 3, 4);
System.out.println(list2);
}
// 泛型方法:类型T在调用时确定
public static <T> ArrayList<T> addElement(ArrayList<T> list, T t1, T t2, T t3, T t4) {
list.add(t1);
list.add(t2);
list.add(t3);
list.add(t4);
return list;
}
}
调用 addElement 时,根据传入的集合和元素类型自动推导出 T,无需手动指定。
泛型接口
定义格式
修饰符 interface 接口名<泛型标识> { ... }
两种使用方式
假设有如下泛型接口:
interface Genericity<E> {
void method(E e);
}
方式一:实现类仍然不确定类型(延续泛型)
class GenericityImpl1<E> implements Genericity<E> {
@Override
public void method(E e) {
System.out.println(e);
}
}
// 使用
GenericityImpl1<String> genericity = new GenericityImpl1<>();
genericity.method("小丽给我的土味情话");
GenericityImpl1 本身也是一个泛型类,创建对象时才确定类型。
方式二:实现类直接指定具体类型
class GenericityImpl2 implements Genericity<Integer> {
@Override
public void method(Integer integer) {
System.out.println(integer);
}
}
// 使用
GenericityImpl2 genericityImpl2 = new GenericityImpl2();
genericityImpl2.method(19);
此时 GenericityImpl2 是一个普通类,类型被固定为 Integer。
通配符
通配符 ? 表示未知类型,常用于接收各种泛型集合,但不能添加元素(null除外)
1 ) 无界通配符 <?>
public static void printList(ArrayList<?> list) {
for (Object obj : list) {
System.out.println(obj);
}
}
2 ) 上界限定 <? extends 类型>
表示可以接收该类型及其所有子类。例如 <? extends Number> 可以接收 Number 或 Integer、Double 等
3 ) 下界限定 <? super 类型>
表示可以接收该类型及其所有父类。例如 <? super Number> 可以接收 Number 或 Object。
完整案例
考虑继承关系:
代码演示:
public class genericityglobbing1 {
public static void main(String[] args) {
ArrayList<Integer> list1 = new ArrayList<>();
ArrayList<Number> list2 = new ArrayList<>();
ArrayList<Object> list3 = new ArrayList<>();
// 上界通配符:接收 Number 及其子类
method1(list1); // OK,Integer是Number的子类
method1(list2); // OK
// method1(list3); // 编译错误,Object不是Number的子类
// 下界通配符:接收 Number 及其父类
method2(list2); // OK
method2(list3); // OK,Object是Number的父类
// method2(list1); // 编译错误,Integer不是Number的父类
}
// 上界:只能是 Number 或 Number 的子类
public static void method1(ArrayList<? extends Number> list) {
for (Number num : list) {
System.out.println(num);
}
}
// 下界:只能是 Number 或 Number 的父类
public static void method2(ArrayList<? super Number> list) {
// 这里只能以Object类型访问,因为父类最低是Number,最高是Object
for (Object obj : list) {
System.out.println(obj);
}
}
}
记忆要点:
? extends Number:规定了上边界,最多到 Number,所以只能取(当作 Number),不能随便添加。
? super Number:规定了下边界,至少是 Number,可以添加 Number 及其子类,但取出时只能当作 Object。
总结
泛型是Java提供的一种编译时类型安全检测机制,合理使用泛型可以写出更健壮、更优雅的代码。掌握泛型类、泛型方法、泛型接口以及通配符的用法,是Java基础的重要内容。
651

被折叠的 条评论
为什么被折叠?



