当前位置: 首页 > news >正文

Java泛型(Generics(

文章目录

  • 1. 泛型的基本概念
  • 2. 泛型的优点
  • 3. 泛型的使用
    • 3.1 泛型类
    • 3.2 泛型方法
    • 3.3 泛型接口
  • 4. 限制泛型
    • 4.1 上界(Upper Bound)
    • 4.2 下界(Lower Bound)
  • 5. 通配符
    • 5.1 上界通配符(Upper Bound Wildcard)
    • 5.2 下界通配符(Lower Bound Wildcard)
  • 6. 特殊用例
    • 6.1 `T extends Comparable<T>`
    • 6.2 `List<E> extends Collection<E>`
    • 6.3 `List<? extends Shape>` 和 `List<T extends Shape>`的区别
  • 7. 类型擦除
    • 7.1 类型擦除的过程
    • 7.2 示例
  • 8. T[] ts = new T[5]
      • **替代方案**
        • (1) **使用`Object[]`并强制转型(需谨慎)**
        • (2) **通过反射创建数组**
        • (3) **优先使用集合类**

泛型(Generics)是 Java 中的一种特性,允许在类、接口和方法中定义类型和参数,从而实现参数的类型化。泛型的主要目的是提高代码的重用性和类型安全性。

1. 泛型的基本概念

  • 类型参数:泛型允许你在定义类、接口或方法时使用类型参数(如 TEKV 等),这些参数在使用时会被具体的类型替代。
  • 类型安全:使用泛型可以在编译时检查类型,减少运行时错误。例如,使用泛型的集合类可以确保集合中只包含特定类型的对象。

2. 泛型的优点

  • 代码重用:通过使用泛型,可以编写通用的算法和数据结构,而不需要为每种数据类型编写重复的代码。
  • 类型安全:编译器会检查类型,减少了类型转换错误和 ClassCastException 的风险。
  • 可读性:泛型使得代码更具可读性,明确了方法和类的预期类型。

3. 泛型的使用

3.1 泛型类

定义一个泛型类,可以使用类型参数:

class Box<T> {
    private T item;

    public void setItem(T item) {
        this.item = item;
    }

    public T getItem() {
        return item;
    }
}

// 使用泛型类
Box<String> stringBox = new Box<>();
stringBox.setItem("Hello");
String item = stringBox.getItem(); // 返回 String 类型

3.2 泛型方法

定义一个泛型方法,可以在方法中使用类型参数:

public static <T> void printArray(T[] array) {
    for (T element : array) {
        System.out.println(element);
    }
}

// 使用泛型方法
Integer[] intArray = {1, 2, 3};
printArray(intArray); // 可以打印 Integer 数组

3.3 泛型接口

定义一个泛型接口:

interface Pair<K, V> {
    public K getKey();
    public V getValue();
}

class OrderedPair<K, V> implements Pair<K, V> {
    private K key;
    private V value;

    public OrderedPair(K key, V value) {
        this.key = key;
        this.value = value;
    }

    public K getKey() {
        return key;
    }

    public V getValue() {
        return value;
    }
}

// 使用泛型接口
Pair<String, Integer> pair = new OrderedPair<>("One", 1);

4. 限制泛型

4.1 上界(Upper Bound)

使用 extends 关键字指定上界,表示类型参数必须是某个类的子类或实现某个接口。

public static <T extends Number> void printNumbers(List<T> numbers) {
    for (T number : numbers) {
        System.out.println(number);
    }
}

在这个例子中,T 必须是 Number 类或其子类(如 IntegerDouble 等)。

4.2 下界(Lower Bound)

使用 super 关键字指定下界,表示类型参数必须是某个类的父类。

public static <T> void addNumbers(List<? super T> list, T number) {
    list.add(number);
}

在这个例子中,list 可以是 T 的任何父类的列表,例如 NumberObject

5. 通配符

通配符用于表示未知类型,通常用 ? 表示。通配符分为上界通配符和下界通配符。

可以接收所有的泛型类型,但是又不能够让用户随意修改。这种情况就需要使用通配符?来处理

5.1 上界通配符(Upper Bound Wildcard)

使用 ? extends Type 表示可以接受 Type 的子类。

public class Food {
}
class Fruit extends Food {
}
class Apple extends Fruit {
}
class Banana extends Fruit {
}
class Message<T> { // 设置泛型
    private T message ;
    public T getMessage() {
        return message;
    }
    public void setMessage(T message) {
        this.message = message;
    }
}

class TestDemo {
    public static void main(String[] args) {
        Message<Apple> appleMessage = new Message<>() ;
        appleMessage.setMessage(new Apple());
        fun(appleMessage);
        Message<Banana> bananaMessage = new Message<>() ;
        bananaMessage.setMessage(new Banana());
        fun(bananaMessage);
    }
    // 此时使用通配符"?"描述的是它可以接收任意类型,但是由于不确定类型,所以无法修改
    public static void fun(Message<? extends Fruit> temp){
        //temp.setMessage(new Banana()); //仍然无法修改!
        //temp.setMessage(new Apple()); //仍然无法修改!
        System.out.println(temp.getMessage());
    }
}

在这个例子中,temp 可以是任何 Fruit 的子类列表,适合只读操作。

5.2 下界通配符(Lower Bound Wildcard)

使用 ? super Type 表示可以接受 Type 的父类。

class Food {
}
class Fruit extends Food {
}
class Apple extends Fruit {
}
class Plate<T> {
    private T plate ;
    public T getPlate() {
        return plate;
    }
    public void setPlate(T plate) {
        this.plate = plate;
    }
}

class TestDemo {
    public static void main(String[] args) {
        Plate<Fruit> fruitPlate = new Plate<>();
        fruitPlate.setPlate(new Fruit());
        fun(fruitPlate);
        Plate<Food> foodPlate = new Plate<>();
        foodPlate.setPlate(new Food());
        fun(foodPlate);
    }
    public static void fun(Plate<? super Fruit> temp){
        // 此时可以修改!!添加的是Fruit 或者Fruit的子类
        temp.setPlate(new Apple());//这个是Fruit的子类
        temp.setPlate(new Fruit());//这个是Fruit的本身
        //Fruit fruit = temp.getPlate(); 不能接收,这里无法确定是哪个父类
        System.out.println(temp.getPlate());//只能直接输出
    }
}

在这个例子中,temp 可以是 Fruit 的任何父类的列表,适合写入操作。

6. 特殊用例

6.1 T extends Comparable<T>

  • 定义T extends Comparable<T> 是 Java 泛型中的一种类型边界,用于指定类型参数 T 必须实现 Comparable<T> 接口。
  • 用途:确保类型 T 的对象可以进行比较,通常用于排序和比较操作。
public class Sorter {
    public static <T extends Comparable<T>> void sort(List<T> list) {
        // 排序逻辑
    }
}
  • 比较功能:通过实现 Comparable<T> 接口,类的对象可以使用 compareTo 方法进行比较。
  • 灵活性:可以对任何实现了 Comparable 接口的类型进行排序。

6.2 List<E> extends Collection<E>

  • 定义List<E> extends Collection<E> 表示 List 接口是 Collection 接口的子接口,E 是元素的类型参数。
  • 用途:提供一个有序的集合,允许重复元素,并支持通过索引访问。

6.3 List<? extends Shape>List<T extends Shape>的区别

  • List<? extends Shape>
    • 只读:适合只读取数据,不能添加元素。
    • 灵活性:可以接受任何 Shape 的子类列表。
  • List<T extends Shape>
    • 读写:适合读取和写入数据,可以添加元素。
    • 类型安全:在方法内部可以安全地处理具体类型 T 的对象。

7. 类型擦除

类型擦除是 Java 泛型实现中的一个重要概念,它是指在编译时,Java 编译器会将泛型类型替换为它的原始类型,从而使得泛型在运行时不再保留类型信息。

7.1 类型擦除的过程

  • 编译时处理:在编译过程中,所有的泛型类型参数(例如 TE 等)会被替换为它们的原始类型。通常情况下,这个原始类型是 Object,但在某些情况下(如有上界限制时),可能会被替换为指定的类型。
  • 运行时行为:由于类型信息在运行时被擦除,泛型的类型安全性主要依赖于编译时的检查。这意味着在运行时,无法直接获取泛型的具体类型。

7.2 示例

考虑以下泛型类:

public class Box<T> {
    private T item;

    public void setItem(T item) {
        this.item = item;
    }

    public T getItem() {
        return item;
    }
}

在编译时,Box<T> 会被转换为 Box<Object>,因此在运行时,所有的 T 都会被视为 Object。这意味着:

  • Box<Integer>Box<String> 中,item 都被视为 Object
  • 当从 Box<Integer> 中取出元素时,必须进行类型转换。

8. T[] ts = new T[5]

那为什么,T[] ts = new T[5]; 是不对的,编译的时候,替换为Object,不是相当于:Object[] ts = new Object[5]吗?
Java语法规定创建数组时需要指定一个具体的类型,但是,new T[5] 不被允许,因为 T 是一个类型参数,编译器在编译时无法确定它的具体类型。

关键原因说明
类型擦除泛型类型T在编译后被擦除,JVM无法确定数组的具体类型。
数组的运行时类型数组需要明确的运行时类型信息以进行安全检查。
类型安全性禁止泛型数组可避免潜在的ArrayStoreException和类型污染问题。

替代方案

若需要泛型数组,可以通过以下方式实现:

(1) 使用Object[]并强制转型(需谨慎)
T[] arr = (T[]) new Object[5]; // 编译警告:未检查的强制转换

但这种方式存在类型不安全风险,需确保后续操作不会插入错误类型的元素。

(2) 通过反射创建数组
T[] arr = (T[]) Array.newInstance(clazz, 5); // clazz是Class<T>类型

通过传递Class<T>参数保留类型信息,绕开类型擦除的限制。

(3) 优先使用集合类

更推荐使用ArrayList<T>等集合类,它们天然支持泛型且类型安全。

相关文章:

  • Java定时任务1_定时任务实现方式以及原理
  • 基于JSP和SQL的CD销售管理系统(源码+lw+部署文档+讲解),源码可白嫖!
  • ubuntu ollama+dify实践
  • 基金交易系统的流程
  • 国产主流数据库存储类型简析
  • 接口自动化测试实战(超详细的)
  • 小程序主包方法迁移到分包-调用策略
  • Python区块链应用开发从入门到精通
  • Word 小黑第19套
  • redis 配置
  • mingw工具源码编译
  • SAP BC 记一次 DBCO 链接ORACLE DBCC 连接测试突然失败的问题
  • tomcat配置应用
  • 【区块链+ 医疗健康】基于区块链的医院诊所信息系统 | FISCO BCOS 应用案例
  • 整合记录-持续
  • 监控易东莞运维项目:it监控+机房动环监控+资产管理+配置管理
  • 滑动窗口[判断子集是否满足条件] 力扣:209 ▎2962 ▎3306
  • ArrayList底层结构和源码分析笔记
  • docker3-容器与镜像命令
  • 【算法】动态规划
  • 酒店网站制作/百度seo排名优化软件化
  • 郑州企业网站制作公司/seo sem是啥
  • 专业网站建设微信商城开发/外贸网站模板
  • 网站源码模块/优化大师免费下载
  • 国外做饰品批发网站/seo搜索引擎优化工资薪酬
  • 如何扫描网站漏洞/宁德市有几个区几个县