Java-泛型类的定义与使用

泛型是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");
        // 取出时需要强制转换为String
        String str = (String) container.getValue();

        // 存储整数
        container.setValue(123);
        // 错误:将Integer强制转换为String,运行时抛出ClassCastException
        String num = (String) container.getValue(); 
    }
}

传统方式的缺陷

  • 类型不安全:编译时无法检查类型,错误只能在运行时发现(如IntegerString的异常);
  • 代码冗余:每次获取数据都需要手动强制类型转换;
  • 可读性差:无法通过代码直观判断容器中存储的是什么类型。

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可存储StringInteger等);
  • 可读性强:通过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和setter
    public 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和setter
    public 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类库中被广泛使用(如ArrayListHashMap),实际开发中,以下场景适合使用泛型类。

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和setter
        public 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 不能使用基本类型作为类型参数

泛型的类型参数必须是引用类型,不能是基本类型(intdouble等)。

// 错误:不能使用int作为类型参数
GenericContainer<int> container = new GenericContainer<>();

// 正确:使用包装类
GenericContainer<Integer> container = new GenericContainer<>();
container.setValue(123); // 自动装箱(int → Integer)

5.2 泛型类型的类型擦除(Type Erasure)

Java泛型是“编译期特性”,编译后会进行类型擦除:字节码中不保留泛型的具体类型,替换为上限类型(如T擦除为ObjectT 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> {
    // 错误:静态成员不能使用T
    public static T staticData;

    // 错误:静态方法不能使用T作为参数或返回值
    public static T staticMethod() { return null; }
}

总结

泛型类通过“类型参数化”实现了代码的复用与类型安全:

  1. 提升代码复用性:一个泛型类可适配多种数据类型,避免重复开发;
  2. 增强类型安全:编译期检查类型匹配,减少运行时异常;
  3. 简化代码编写:消除强制类型转换,代码更简洁易读。

实际开发中,泛型类广泛应用于容器类(如集合框架)、工具类、框架底层(如Spring的GenericBeanFactory)等场景。

若这篇内容帮到你,动动手指支持下!关注不迷路,干货持续输出!
ヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノ

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值