Java-泛型类的定义与使用
Java-泛型类的定义与使用
- 一、为什么需要泛型类?
- 1.1 传统方式的问题(以容器类为例)
- 1.2 泛型类的优势
- 二、泛型类的基本定义
- 2.1 基本语法
- 2.2 单类型参数泛型类
- 2.3 多类型参数泛型类
- 三、泛型类的使用方式
- 3.1 基本使用(指定具体类型)
- 3.2 类型参数的限制(有界泛型)
- 3.2.1 单边界限制
- 3.2.2 多边界限制(接口+类)
- 3.3 泛型类的继承
- 3.3.1 泛型类继承普通类
- 3.3.2 泛型类继承泛型类
- 四、泛型类的实际应用场景
- 4.1 容器类(集合、缓存)
- 4.2 工具类(通用功能)
- 五、泛型类的常见问题与注意事项
- 5.1 不能使用基本类型作为类型参数
- 5.2 泛型类型的类型擦除(Type Erasure)
- 5.3 不能通过类型参数创建对象
- 5.4 泛型类的静态成员不能使用类型参数
- 总结
泛型是Java5引入的核心特性之一,它允许类、接口和方法在定义时不指定具体类型,而是在使用时动态指定,泛型类作为泛型的基础应用,能够显著提升代码的复用性和类型安全性。
一、为什么需要泛型类?
在泛型出现之前,开发者通常使用Object
类实现“通用”功能,但这种方式存在明显缺陷。
1.1 传统方式的问题(以容器类为例)
假设我们需要一个“可以存储任何类型的容器类”,传统方式可能这样实现:
// 传统容器类(使用Object存储)
class ObjectContainer {private Object value;// 存储数据public void setValue(Object value) {this.value = value;}// 获取数据(需要强制类型转换)public Object getValue() {return value;}
}
使用该容器时:
public class Test {public static void main(String[] args) {ObjectContainer container = new ObjectContainer();// 存储字符串container.setValue("Hello");// 取出时需要强制转换为StringString str = (String) container.getValue();// 存储整数container.setValue(123);// 错误:将Integer强制转换为String,运行时抛出ClassCastExceptionString num = (String) container.getValue(); }
}
传统方式的缺陷:
- 类型不安全:编译时无法检查类型,错误只能在运行时发现(如
Integer
转String
的异常); - 代码冗余:每次获取数据都需要手动强制类型转换;
- 可读性差:无法通过代码直观判断容器中存储的是什么类型。
1.2 泛型类的优势
泛型类通过“在定义时声明类型参数,使用时指定具体类型”解决上述问题:
// 泛型容器类(T为类型参数)
class GenericContainer<T> {private T value; // 使用类型参数T定义变量// 存储数据(参数类型为T)public void setValue(T value) {this.value = value;}// 获取数据(返回类型为T,无需强制转换)public T getValue() {return value;}
}
使用泛型容器:
public class Test {public static void main(String[] args) {// 1. 指定存储String类型GenericContainer<String> strContainer = new GenericContainer<>();strContainer.setValue("Hello");String str = strContainer.getValue(); // 无需强制转换// 2. 指定存储Integer类型GenericContainer<Integer> intContainer = new GenericContainer<>();intContainer.setValue(123);Integer num = intContainer.getValue();// 3. 编译时检查类型(错误在编译期暴露)strContainer.setValue(123); // 编译报错:无法将int转换为String}
}
泛型类的核心优势:
- 类型安全:编译时检查类型匹配,避免运行时类型转换异常;
- 消除强制转换:获取数据时无需手动转换,代码更简洁;
- 代码复用:一个泛型类可适配多种数据类型(如
GenericContainer
可存储String
、Integer
等); - 可读性强:通过
GenericContainer<String>
可直观判断存储类型。
二、泛型类的基本定义
泛型类的定义核心是“声明类型参数”,语法格式和关键概念如下。
2.1 基本语法
// 泛型类定义
修饰符 class 类名<类型参数列表> {// 类体中可使用类型参数
}
- 类型参数列表:用尖括号
<>
包裹,可包含一个或多个类型参数(如<T>
、<K, V>
); - 类型参数命名:通常使用单个大写字母(约定俗成),常见命名:
T
(Type):表示任意类型;K
(Key):表示键类型;V
(Value):表示值类型;E
(Element):表示集合元素类型。
2.2 单类型参数泛型类
最常用的泛型类形式,适用于只需适配一种类型的场景(如容器类、工具类)。
/*** 单类型参数泛型类示例:简单的链表节点* T:表示节点存储的数据类型*/
class Node<T> {private T data; // 存储的数据private Node<T> next; // 下一个节点(类型与当前节点一致)public Node(T data) {this.data = data;}// getter和setterpublic T getData() { return data; }public void setData(T data) { this.data = data; }public Node<T> getNext() { return next; }public void setNext(Node<T> next) { this.next = next; }
}
使用示例:
public class NodeTest {public static void main(String[] args) {// 创建存储String的节点Node<String> strNode = new Node<>("First");strNode.setNext(new Node<>("Second"));System.out.println(strNode.getNext().getData()); // 输出:Second// 创建存储Integer的节点Node<Integer> intNode = new Node<>(100);intNode.setNext(new Node<>(200));System.out.println(intNode.getNext().getData()); // 输出:200}
}
2.3 多类型参数泛型类
当类需要适配多种相关类型(如键值对)时,可使用多类型参数(如<K, V>
)。
/*** 多类型参数泛型类示例:键值对* K:键类型* V:值类型*/
class Pair<K, V> {private K key;private V value;public Pair(K key, V value) {this.key = key;this.value = value;}// getter和setterpublic K getKey() { return key; }public V getValue() { return value; }public void setKey(K key) { this.key = key; }public void setValue(V value) { this.value = value; }
}
使用示例:
public class PairTest {public static void main(String[] args) {// 键为String,值为Integer(如"age" → 25)Pair<String, Integer> agePair = new Pair<>("age", 25);String key = agePair.getKey();Integer value = agePair.getValue();// 键为Integer,值为String(如1 → "张三")Pair<Integer, String> userPair = new Pair<>(1, "张三");System.out.println(userPair.getKey() + " → " + userPair.getValue()); // 输出:1 → 张三}
}
三、泛型类的使用方式
使用泛型类时,需“指定具体类型参数”(类型实参),语法和注意事项如下。
3.1 基本使用(指定具体类型)
// 格式:类名<具体类型> 对象名 = new 类名<具体类型>();
GenericClass<String> obj = new GenericClass<String>();// Java 7+支持菱形语法(右侧类型可省略)
GenericClass<String> obj = new GenericClass<>(); // 推荐
示例:使用Pair
泛型类:
// 指定K为String,V为Double
Pair<String, Double> pricePair = new Pair<>("apple", 5.99);
System.out.println(pricePair.getKey() + "的价格:" + pricePair.getValue());
3.2 类型参数的限制(有界泛型)
默认情况下,泛型类的类型参数可以是任何类型,但有时需要限制类型范围(如只允许数字类型)。这时可使用“有界泛型”(extends
关键字)。
3.2.1 单边界限制
语法:<T extends 父类型>
,表示T
必须是“父类型”或其子类型。
/*** 有界泛型类示例:计算数值的工具类* T extends Number:限制T必须是Number或其子类(Integer、Double等)*/
class NumberCalculator<T extends Number> {// 计算数组元素的总和public double sum(T[] array) {double total = 0;for (T num : array) {// Number类有doubleValue()方法,子类都可调用total += num.doubleValue();}return total;}
}
使用示例:
public class CalculatorTest {public static void main(String[] args) {// 1. 使用Integer类型(Integer是Number的子类)NumberCalculator<Integer> intCalc = new NumberCalculator<>();Integer[] intArray = {1, 2, 3, 4};System.out.println("整数总和:" + intCalc.sum(intArray)); // 输出:10.0// 2. 使用Double类型(Double是Number的子类)NumberCalculator<Double> doubleCalc = new NumberCalculator<>();Double[] doubleArray = {1.5, 2.5, 3.5};System.out.println("小数总和:" + doubleCalc.sum(doubleArray)); // 输出:7.5// 3. 错误:String不是Number的子类,编译报错NumberCalculator<String> strCalc = new NumberCalculator<>(); // 编译报错}
}
3.2.2 多边界限制(接口+类)
语法:<T extends 类 & 接口1 & 接口2>
,表示T
必须是“指定类的子类”且“实现了指定接口”。
注意:类必须放在第一个位置(只能有一个类,可多个接口)。
/*** 多边界泛型类示例:支持比较和序列化的容器* T extends Number & Comparable<T> & Serializable:* - T必须是Number子类* - 必须实现Comparable接口(支持比较)* - 必须实现Serializable接口(支持序列化)*/
class AdvancedContainer<T extends Number & Comparable<T> & java.io.Serializable> {private T value;public AdvancedContainer(T value) {this.value = value;}// 比较当前值和另一个值public int compareTo(AdvancedContainer<T> other) {return this.value.compareTo(other.value); // 调用Comparable接口的方法}
}
使用示例:
// Integer是Number子类,且实现了Comparable和Serializable
AdvancedContainer<Integer> container1 = new AdvancedContainer<>(10);
AdvancedContainer<Integer> container2 = new AdvancedContainer<>(20);
System.out.println(container1.compareTo(container2)); // 输出:-1(10 < 20)
3.3 泛型类的继承
泛型类可以继承普通类或其他泛型类,继承时需注意类型参数的传递。
3.3.1 泛型类继承普通类
// 普通父类
class Parent {protected String name;
}// 泛型子类继承普通父类
class GenericChild<T> extends Parent {private T data; // 子类自己的泛型参数
}
3.3.2 泛型类继承泛型类
// 父泛型类
class ParentGeneric<T> {protected T parentData;
}// 子类保留父类的泛型参数
class ChildGeneric<T> extends ParentGeneric<T> {private T childData; // 与父类使用相同的T
}// 子类指定父类的具体类型(父类泛型参数固定)
class FixedChild extends ParentGeneric<String> {// 父类的parentData固定为String类型public void setParentData(String data) {super.parentData = data;}
}
使用示例:
// ChildGeneric使用Integer类型(父类和子类的T均为Integer)
ChildGeneric<Integer> child = new ChildGeneric<>();
child.parentData = 100; // 父类的T为Integer
child.childData = 200; // 子类的T为Integer// FixedChild中父类的T固定为String
FixedChild fixedChild = new FixedChild();
fixedChild.setParentData("test"); // 正确(String类型)
fixedChild.parentData = 123; // 编译报错(父类T已固定为String)
四、泛型类的实际应用场景
泛型类在Java类库中被广泛使用(如ArrayList
、HashMap
),实际开发中,以下场景适合使用泛型类。
4.1 容器类(集合、缓存)
容器类是泛型最典型的应用,用于存储和管理不同类型的数据。
/*** 自定义泛型链表*/
public class GenericLinkedList<T> {// 头节点private Node<T> head;// 添加元素public void add(T data) {Node<T> newNode = new Node<>(data);if (head == null) {head = newNode;} else {Node<T> current = head;while (current.getNext() != null) {current = current.getNext();}current.setNext(newNode);}}// 获取指定索引的元素public T get(int index) {Node<T> current = head;for (int i = 0; i < index; i++) {if (current == null) {throw new IndexOutOfBoundsException();}current = current.getNext();}return current.getData();}// 内部节点类(泛型)private static class Node<T> {private T data;private Node<T> next;public Node(T data) {this.data = data;}// getter和setterpublic T getData() { return data; }public Node<T> getNext() { return next; }public void setNext(Node<T> next) { this.next = next; }}
}
使用示例:
public class LinkedListTest {public static void main(String[] args) {// 存储String类型的链表GenericLinkedList<String> strList = new GenericLinkedList<>();strList.add("A");strList.add("B");System.out.println(strList.get(1)); // 输出:B// 存储Integer类型的链表GenericLinkedList<Integer> intList = new GenericLinkedList<>();intList.add(10);intList.add(20);System.out.println(intList.get(0)); // 输出:10}
}
4.2 工具类(通用功能)
工具类通常需要处理多种类型的数据,泛型可避免重复开发。
/*** 泛型工具类:数组操作工具*/
class ArrayUtils<T> {// 交换数组中两个位置的元素public void swap(T[] array, int i, int j) {if (i < 0 || j < 0 || i >= array.length || j >= array.length) {throw new IndexOutOfBoundsException("索引越界");}T temp = array[i];array[i] = array[j];array[j] = temp;}// 查找元素在数组中的索引public int indexOf(T[] array, T target) {for (int i = 0; i < array.length; i++) {if (target.equals(array[i])) {return i;}}return -1;}
}
使用示例:
public class ArrayUtilsTest {public static void main(String[] args) {ArrayUtils<String> strUtils = new ArrayUtils<>();String[] strArray = {"a", "b", "c"};strUtils.swap(strArray, 0, 2);System.out.println(Arrays.toString(strArray)); // 输出:[c, b, a]ArrayUtils<Integer> intUtils = new ArrayUtils<>();Integer[] intArray = {10, 20, 30};System.out.println(intUtils.indexOf(intArray, 20)); // 输出:1}
}
五、泛型类的常见问题与注意事项
5.1 不能使用基本类型作为类型参数
泛型的类型参数必须是引用类型,不能是基本类型(int
、double
等)。
// 错误:不能使用int作为类型参数
GenericContainer<int> container = new GenericContainer<>();// 正确:使用包装类
GenericContainer<Integer> container = new GenericContainer<>();
container.setValue(123); // 自动装箱(int → Integer)
5.2 泛型类型的类型擦除(Type Erasure)
Java泛型是“编译期特性”,编译后会进行类型擦除:字节码中不保留泛型的具体类型,替换为上限类型(如T
擦除为Object
,T extends Number
擦除为Number
)。
示例:
class ErasureDemo<T> {private T data;public T getData() { return data; }
}// 编译后(类型擦除后)的等效代码:
class ErasureDemo {private Object data;public Object getData() { return data; }
}
影响:
- 运行时无法获取泛型的具体类型(如
instanceof
无法判断obj instanceof GenericContainer<String>
); - 不能创建泛型数组(
new T[10]
编译报错,因类型擦除后无法确定数组类型)。
5.3 不能通过类型参数创建对象
由于类型擦除,无法在泛型类中通过new T()
创建对象(编译时不知道T
的具体类型)。
class CreateObjectDemo<T> {// 错误:不能创建T的实例public T create() {return new T(); // 编译报错}// 正确:通过反射创建(需要传递Class对象)public T create(Class<T> clazz) throws InstantiationException, IllegalAccessException {return clazz.newInstance();}
}
使用反射创建对象:
CreateObjectDemo<String> demo = new CreateObjectDemo<>();
String str = demo.create(String.class); // 正确(通过Class对象创建)
5.4 泛型类的静态成员不能使用类型参数
静态成员属于类,而类型参数是对象级别的(每个对象的类型参数可能不同),因此静态成员不能使用泛型类的类型参数。
class StaticMemberDemo<T> {// 错误:静态成员不能使用Tpublic static T staticData;// 错误:静态方法不能使用T作为参数或返回值public static T staticMethod() { return null; }
}
总结
泛型类通过“类型参数化”实现了代码的复用与类型安全:
- 提升代码复用性:一个泛型类可适配多种数据类型,避免重复开发;
- 增强类型安全:编译期检查类型匹配,减少运行时异常;
- 简化代码编写:消除强制类型转换,代码更简洁易读。
实际开发中,泛型类广泛应用于容器类(如集合框架)、工具类、框架底层(如Spring的GenericBeanFactory
)等场景。
若这篇内容帮到你,动动手指支持下!关注不迷路,干货持续输出!
ヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノ