Java泛型(Generics(

文章目录

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

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

1. 泛型的基本概念

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

2. 泛型的优点

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

3. 泛型的使用

3.1 泛型类

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

java 复制代码
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 泛型方法

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

java 复制代码
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 泛型接口

定义一个泛型接口:

java 复制代码
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 关键字指定上界,表示类型参数必须是某个类的子类或实现某个接口。

java 复制代码
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 关键字指定下界,表示类型参数必须是某个类的父类。

java 复制代码
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 的子类。

java 复制代码
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 的父类。

java 复制代码
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 的对象可以进行比较,通常用于排序和比较操作。
java 复制代码
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 示例

考虑以下泛型类:

java 复制代码
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]吗?

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

替代方案

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

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

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

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

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

(3) 优先使用集合类

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

相关推荐
Java开发追求者2 分钟前
java 手搓一个http工具类请求传body
java·开发语言·http·调用第三方接口工具
weixin_466485115 分钟前
qt5中使用中文报错error: C2001: 常量中有换行符
android·java·qt
鲤籽鲲1 小时前
C# 事件使用详解
开发语言·c#·c# 知识捡漏
小白学大数据1 小时前
Python爬虫:从人民网提取视频链接的完整指南
大数据·开发语言·爬虫·python·音视频
onlyzzr1 小时前
Leetcode Hot100 第58题 23.合并K个升序链表
java·算法·leetcode
剑海风云1 小时前
73. 矩阵置零
java·数据结构·算法·矩阵
愚戏师1 小时前
Python:函数(一)
开发语言·windows·python
十年一梦实验室2 小时前
【C++】 嵌套类(Nested Class)
开发语言·c++
矛取矛求2 小时前
C++ 模板初阶总结
开发语言·c++
A阳俊yi2 小时前
SpringMVC响应页面及不同类型的数据,
java·前端·javascript