百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

Java 泛型使用教程(java泛型的作用及使用场景)

wxin55 2025-05-02 13:59 1 浏览 0 评论

简介

Java 泛型是 JDK 5 引入的一项特性,它提供了编译时类型安全检测机制,允许在编译时检测出非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。

泛型的好处:

  • 编译期检查类型安全
  • 避免强制类型转换(cast
  • 代码更通用,更易重用

泛型的基本使用

泛型类

public class Box<T> {
    private T content;

    public void set(T content) {
        this.content = content;
    }

    public T get() {
        return content;
    }
}

使用:

Box<String> stringBox = new Box<>();
stringBox.set("Hello");
String value = stringBox.get(); // 无需强转

泛型方法

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

使用:

String[] arr = {"A", "B", "C"};
Util.printArray(arr);

泛型接口

public interface Converter<F, T> {
    T convert(F from);
}

public class StringToIntegerConverter implements Converter<String, Integer> {
    public Integer convert(String from) {
        return Integer.parseInt(from);
    }
}

泛型中的通配符:?

? 通配符

表示任意类型:

public void printList(List<?> list) {
    for (Object item : list) {
        System.out.println(item);
    }
}

? extends T(上界通配符)

表示 TT 的子类(只读,不能写):

public void printNumbers(List<? extends Number> list) {
    for (Number num : list) {
        System.out.println(num);
    }
}

不能添加元素:

list.add(10); //  报错

? super T(下界通配符)

表示 TT 的父类(可以写入,但取出只能当作 Object):

public void addNumbers(List<? super Integer> list) {
    list.add(10); //  OK
}

类型擦除(Type Erasure)

泛型在编译后会被擦除,JVM 不知道泛型类型,全部变成 Object

List<String> list = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
System.out.println(list.getClass() == list2.getClass()); // true

正因如此:

  • 不能使用 new T()
  • 不能使用 T.class
  • 不能判断 instanceof T

常见的泛型类库例子

  • List<T>:存储 T 类型的集合
  • Map<K, V>:泛型键值对
  • Optional<T>:包装返回值
  • Comparable<T>:排序比较接口
  • Function<T, R>:函数式接口
  • Callable<T>:异步任务的返回类型
  • Future<T>:异步计算的结果

进阶技巧

泛型数组不允许:

List<String>[] lists = new List<String>[10]; //  编译错误

泛型静态方法必须声明<T>:

public static <T> void print(T value) {
    System.out.println(value);
}

实际项目中 Java 泛型的用法大全

通用返回封装类(统一 API 响应格式)

public class Result<T> {
    private int code;
    private String message;
    private T data;

    public Result(int code, String message, T data) {
        this.code = code;
        this.message = message;
        this.data = data;
    }

    public static <T> Result<T> success(T data) {
        return new Result<>(200, "Success", data);
    }

    public static <T> Result<T> fail(String message) {
        return new Result<>(500, message, null);
    }

    // getter/setter omitted
}

通用分页结果类

public class PageResult<T> {
    private List<T> records;
    private long total;

    public PageResult(List<T> records, long total) {
        this.records = records;
        this.total = total;
    }
}

结合 Spring 使用泛型的示例

通用 Service 接口

public interface BaseService<T, ID> {
    T findById(ID id);
    List<T> findAll();
    T save(T entity);
    void deleteById(ID id);
}

抽象 Service 实现类

public abstract class AbstractBaseService<T, ID> implements BaseService<T, ID> {

    @Autowired
    protected JpaRepository<T, ID> repository;

    @Override
    public T findById(ID id) {
        return repository.findById(id).orElse(null);
    }

    @Override
    public List<T> findAll() {
        return repository.findAll();
    }

    @Override
    public T save(T entity) {
        return repository.save(entity);
    }

    @Override
    public void deleteById(ID id) {
        repository.deleteById(id);
    }
}

具体 Service

@Service
public class UserService extends AbstractBaseService<User, Long> {
    // 可以扩展额外业务逻辑
}

Java 泛型 与 C#.net 泛型比较

Java 泛型 与 C# (.NET) 泛型有很多相似之处,C# 泛型的设计部分参考了 Java。但它们在类型擦除、协变/逆变、约束、运行时行为等方面有显著的不同。

特性

Java 泛型

C# (.NET) 泛型

类型擦除

是(编译期擦除)

否(保留类型信息)

运行时可反射获取泛型类型

基本类型支持

不直接支持(需使用包装类如 Integer

支持,例如 List<int>

泛型约束

限制(只能 extends,不支持多个约束)

强大(支持 where T : class, new(), 多个接口)

协变/逆变支持

通过通配符 ? extends/super

直接使用 out/in 关键字

泛型数组

不支持(如 new T[] 编译错误)

支持

泛型方法

支持

支持

类型擦除 vs 保留类型

  • Java
List<String> list = new ArrayList<>();
List<Integer> intList = new ArrayList<>();
System.out.println(list.getClass() == intList.getClass()); // true

Java 在编译时擦除了泛型信息,List<String>List<Integer> 其实是同一个字节码类。

  • C#
List<string> list = new List<string>();
List<int> intList = new List<int>();
Console.WriteLine(list.GetType() == intList.GetType()); // false

C# 会为不同泛型参数生成不同的类实例,因此保留类型信息。

泛型约束

  • Java
public class Repository<T extends BaseEntity> {
    public void save(T entity) { }
}
  • C#
public class Repository<T> where T : BaseEntity, new() {
    public void Save(T entity) { }
}

C# 支持更丰富的泛型约束,例如要求是引用类型 class、值类型 struct、必须有无参构造函数 new() 等。

协变与逆变(Covariance & Contravariance)

  • Java 使用通配符
List<? extends Number> numbers;  // 只能读取,不能添加
List<? super Integer> integers;  // 只能添加 Integer
  • C# 使用 in / out
interface ICovariant<out T> { }
interface IContravariant<in T> { }

C# 在接口中用 in/out 明确支持协变逆变,且更强大、更类型安全。

泛型数组

  • Java
T[] array = new T[10]; // 编译错误
  • C#
T[] array = new T[10]; // 合法

基本类型泛型

  • Java
List<int> list = new ArrayList<>(); // 编译错误
List<Integer> list = new ArrayList<>(); // 正确
  • C#
List<int> list = new List<int>(); // 正确,泛型支持值类型

总结

特性

Java

C#

类型安全

灵活性

(类型擦除限制)

(运行时保留泛型)

泛型数组

基本类型支持

(需包装)

泛型约束

一般

强大

协变逆变

复杂、通配符语法

简洁、原生支持

性能

需装箱

无装箱(对值类型更快)

协变逆变详解

协变(Covariance)和逆变(Contravariance)是泛型类型系统中用于处理子类和父类之间的泛型关系的重要机制。

Java 的协变(Covariant)与逆变(Contravariant)

Java 使用 通配符(wildcards) 来支持:

协变(? extends T) — 只读

  • 意思是:某个未知类型是 T 的子类
  • 常用于“只能读”的场景
public void readAnimals(List<? extends Animal> animals) {
    for (Animal animal : animals) {
        System.out.println(animal);
    }
}

可以传入 List<Cat>、List<Dog> 或 List<Animal>,但不能添加新元素。

animals.add(new Cat()); //  编译错误

逆变(? super T) — 只写

  • 意思是:某个未知类型是 T 的父类
  • 常用于“只能写”的场景
public void addCats(List<? super Cat> cats) {
    cats.add(new Cat());     //  合法
    // cats.add(new Animal()); //  不安全
}

可以传入 List<Cat>、List<Animal>、List<Object>,但不能安全地读取具体类型:

Object obj = cats.get(0); // 只能作为 Object 使用

C# 中的协变与逆变

  • C# 协变(out)
interface IReadOnlyList<out T> {
    T Get(int index);
}

IReadOnlyList<Animal> animals = new List<Cat>(); //  协变成功
  • C# 逆变(in)
interface IWriter<in T> {
    void Write(T value);
}

IWriter<Cat> writer = new AnimalWriter(); //  逆变成功

完整示例:协变 & 逆变

import java.util.*;

class Animal { }
class Cat extends Animal { }
class Dog extends Animal { }

public class VarianceDemo {

    public static void main(String[] args) {
        List<Cat> cats = new ArrayList<>();
        cats.add(new Cat());

        readAnimals(cats); //  协变
        addCats(cats);     //  逆变
    }

    // 协变:读取
    public static void readAnimals(List<? extends Animal> animals) {
        for (Animal a : animals) {
            System.out.println("Animal: " + a);
        }
        // animals.add(new Dog()); //  编译错误
    }

    // 逆变:写入
    public static void addCats(List<? super Cat> animals) {
        animals.add(new Cat()); // 
    }
}

总结

特性

协变

逆变

Java 关键字

? extends T

? super T

C# 关键字

out T

in T

适合场景

读取数据

写入数据

是否可写

是否可读

(只能当 Object)

相关推荐

ES6中 Promise的使用场景?(es6promise用法例子)

一、介绍Promise,译为承诺,是异步编程的一种解决方案,比传统的解决方案(回调函数)更加合理和更加强大在以往我们如果处理多层异步操作,我们往往会像下面那样编写我们的代码doSomething(f...

JavaScript 对 Promise 并发的处理方法

Promise对象代表一个未来的值,它有三种状态:pending待定,这是Promise的初始状态,它可能成功,也可能失败,前途未卜fulfilled已完成,这是一种成功的状态,此时可以获取...

Promise的九大方法(promise的实例方法)

1、promise.resolv静态方法Promise.resolve(value)可以认为是newPromise方法的语法糖,比如Promise.resolve(42)可以认为是以下代码的语...

360前端一面~面试题解析(360前端开发面试题)

1.组件库按需加载怎么做的,具体打包配了什么-按需加载实现:借助打包工具(如Webpack的require.context或ES模块动态导入),在使用组件时才引入对应的代码。例如在V...

前端面试-Promise 的 finally 怎么实现的?如何在工作中使用?

Promise的finally方法是一个非常有用的工具,它无论Promise是成功(fulfilled)还是失败(rejected)都会执行,且不改变Promise的最终结果。它的实现原...

最简单手写Promise,30行代码理解Promise核心原理和发布订阅模式

看了全网手写Promise的,大部分对于新手还是比较难理解的,其中几个比较难的点:状态还未改变时通过发布订阅模式去收集事件实例化的时候通过调用构造函数里传出来的方法去修改类里面的状态,这个叫Re...

前端分享-Promise可以中途取消啦(promise可以取消吗)

传统Promise就像一台需要手动组装的设备,每次使用都要重新接线。而Promise.withResolvers的出现,相当于给开发者发了一个智能遥控器,可以随时随地控制异步操作。它解决了三大...

手写 Promise(手写输入法 中文)

前言都2020年了,Promise大家肯定都在用了,但是估计很多人对其原理还是一知半解,今天就让我们一起实现一个符合PromiseA+规范的Promise。附PromiseA+规范地址...

什么是 Promise.allSettled()!新手老手都要会?

Promise.allSettled()方法返回一个在所有给定的promise都已经fulfilled或rejected后的promise,并带有一个对象数组,每个对象表示对应的pr...

前端面试-关于Promise解析与高频面试题示范

Promise是啥,直接上图:Promise就是处理异步函数的API,它可以包裹一个异步函数,在异步函数完成时抛出完成状态,让代码结束远古时无限回掉的窘境。配合async/await语法糖,可...

宇宙厂:为什么前端离不开 Promise.withResolvers() ?

大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发。1.为什么需要Promise.with...

Promise 新增了一个超实用的 API!

在JavaScript的世界里,Promise一直是处理异步操作的神器。而现在,随着ES2025的发布,Promise又迎来了一个超实用的新成员——Promise.try()!这个新方法简...

一次搞懂 Promise 异步处理(promise 异步顺序执行)

PromisePromise就像这个词的表面意识一样,表示一种承诺、许诺,会在后面给出一个结果,成功或者失败。现在已经成为了主流的异步编程的操作方式,写进了标准里面。状态Promise有且仅有...

Promise 核心机制详解(promise机制的实现原理)

一、Promise的核心状态机Promise本质上是一个状态机,其行为由内部状态严格管控。每个Promise实例在创建时处于Pending(等待)状态,此时异步操作尚未完成。当异步操作成功...

javascript——Promise(js实现promise)

1.PromiseES6开始支持,Promise对象用于一个异步操作的最终完成(包括成功和失败)及结果值的表示。简单说就是处理异步请求的。之所以叫Promise,就是我承诺,如果成功则怎么处理,失败怎...

取消回复欢迎 发表评论: