Java 中泛型 T 和 ? 的区别详解
作者:dying?搁浅 发布时间:2022-07-08 00:40:28
泛型中 T 类型变量 和 ? 通配符 区别
定义不同 :T 是类型变量,? 是通配符
使用范围不同:
? 通配符用作 参数类型、字段类型、局部变量类型,有时作为返回类型(但请避免这样做)
T 用作 声明类的类型参数、通用方法的类型参数 (这里注意 类型参数 和 参数类型 是两个概念)
通常我们使用 ? 的时候并并不知道也不关心这个时候的类型,这里只想使用其通用的方法,而且 ? 通配符是无法作用于声明类的类型参数,一般作用于方法和参数上。而 类型变量 T 在类定义时具有更广泛的应用。
在某些程度的使用上 ? 通配符与 T 参数类型是可以等效的,但是 T 参数类型并不支持下界限制 即 T super SomeTing 而 通配符支持 ? super SomeThing
如果你想写一个通用的方法且该方法的逻辑不关心类型那么就大胆的用 ? 通配符来进行适配和限制吧,如果你需要作用域类型(这可能在操作通用数组类型时更明显)或者声明类的类型参数时请使用 T 类型变量
类型参数定义了一种代表作用域类型的变量(例如,T),通配符只是定义了一组可用于泛型类型的允许类型。通配符的意思是“在这里使用任何类型”
在泛型的使用中我们经常可以看到这样的用法:
public class Box<T> {
// T stands for "Type"
private T t;
public void set(T t) { this.t = t; }
public T get() { return t; }
}
List<? extends Integer> intList = new ArrayList<>();
List<? extends Number> numList = intList; // OK. List<? extends Integer> is a subtype of List<? extends Number>
public interface GenericProgressiveFutureListener<F extends ProgressiveFuture<?>> extends GenericFutureListener<F> {
void operationProgressed(F future, long progress, long total) throws Exception;
}
如果你其用法和概念仍有疑问,那不妨继续阅读本文
了解他们的概念:Generic Types 和 Wildcards ,以及使用。
Generic Types 类型变量
通用类型即 T、F、K、V 这样的写法,它是一种是通过类型参数化的通用类或接口,也可以称之为 类型变量
类型变量可以是任何非原始类型:任何类类型、任何接口类型、任何数组类型,甚至是另一个类型变量
按照惯例,类型参数名称是单个大写字母。
最常用的类型参数名称是:
E - 元素(被 Java 集合框架广泛使用)
K - 键
N - 数字
T - 类型
V - 值
S、U、V 等 - 第 2、3、4 种类型
用法
1.声明通用的类型 – 泛型类:
当我们想对通用的对象类型进行操作时我们可能想到使用 Object ,但是使用 Object 在编译时无法进行检查,因为 Object 是所有类的父类,这可能导致我们意图在传入 Integer 并可以取出 Inerger 时,在另一部分代码错误的传入了 String
public class Box {
private Object object;
public void set(Object object) { this.object = object; }
public Object get() { return object; }
}
为了避免上述的问题,我们可以选择使用 类型变量
public class Box<T> {
// T stands for "Type"
private T t;
public void set(T t) { this.t = t; }
public T get() { return t; }
}
你也可以使用多个类型参数
public interface Pair<K, V> {
public K getKey();
public V getValue();
}
public 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; }
}
2.声明通用的方法 – 泛型方法:
泛型方法 是引入自己的类型参数的方法。这类似于声明泛型类型,但类型参数的范围仅限于声明它的方法。允许静态和非静态泛型方法,以及泛型类构造函数。
泛型方法的语法包括一个类型参数列表,在尖括号内,它出现在方法的返回类型之前。对于静态泛型方法,类型参数部分必须出现在方法的返回类型之前。
public class Util {
public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) {
return p1.getKey().equals(p2.getKey()) &&
p1.getValue().equals(p2.getValue());
}
}
public static <T> void printListT(List<T> list) {
for (Object elem : list)
System.out.println(elem + " ");
System.out.println();
}
一个完整的调用是
JestTestMain.<String>printListT(names);
但是通常可以省略类型,这里使用到的功能是 类型推断
JestTestMain.printListT(names);
有界类型参数
同时我们可以对类型参数进行限制通过 extends 关键字
如 <T extends Number> 这里的泛型参数就限制了必须继承于 Number 类。
public static <T extends Number> void printListT(List<T> list) {
for (Object elem : list)
System.out.println(elem + " ");
System.out.println();
}
同时 Java 也支持多重限定,如 <T extends CharSequence & Comparable<T> & Serializable> 但是如果其中限定包含 类 需要写在最前面
public static <T extends CharSequence & Comparable<T> & Serializable> void printListT(List<T> list) {
for (Object elem : list)
System.out.println(elem + " ");
System.out.println();
}
Wildcards 通配符
通配符即指 ?
在泛型代码中,? 表示未知类型。通配符可用于多种情况:
作为参数、字段或局部变量的类型,有时作为返回类型(但请避免这样做)。
通配符从不用作泛型方法调用、泛型类实例创建或超类型的类型参数。
用法
通配符分为 3 种:
1.上界通配符:? extend 上界类型
如List public static void process(List list) { /* ... */ }
我们可以使用上界通配符来放宽对变量的限制
2. * 通配符:?
如 List<?> 这表示未知类型的列表,一般有两种情况下 * 通配符是有用的:
你正在编写可以使用 Object类中提供的功能实现的方法
当代码使用不依赖于类型参数的泛型类中的方法时。例如,List.size或 List.clear。事实上,Class<?> 之所以如此常用,是因为 Class<T>中的大多数方法都不依赖于 T。
如何理解这句话的意思呢?来看一个例子:
public static void printList(List<Object> list) {
for (Object elem : list)
System.out.println(elem + " ");
System.out.println();
}
printList 的意图是想打印任何类型的列表,但是它没有达到目标,其只打印了 Object 实例的列表。它不能打印 List<Integer>、List<String>、List<Double>等,因为它们不是 List<Object> 的子类型。
编译时将会报错。
这里我们换成通配符将正确运行
public class JestTestMain {
public static void main(String[] args) {
List<String> names= Lists.newArrayList();
names.add("张三");
names.add("张三1");
names.add("张三2");
printList(names);
}
public static void printList(List<?> list) {
for (Object elem : list)
System.out.println(elem + " ");
System.out.println();
}
}
打印:
张三
张三1
张三2
这里需要明白的一点是,List<Object> 和 List<?> 并不相同,你可以向 List<Object> 中插入 Object 对象,或者任何其子类对象,但是你只能向 List<?> 中插入 null 值。
3.下界通配符:? super 子类
如:<? super Integer>
假设你要编写一个将 Integer 对象放入列表的方法。为了最大限度地提高灵活性,希望该方法适用于 List<Integer>、List<Number>和 List<Object> 任何可以保存 Integer 值的东西。
public static void addNumbers(List<? super Integer> list) {
for (int i = 1; i <= 10; i++) {
list.add(i);
}
}
类型擦除
我们要知道的一件事儿,编译器在编译时清除了所有类型参数,也就是说会将我们的类型参数进行实际的替换。
就算如此我们仍有使用泛型的理由
Java 编译器在编译时对泛型代码执行更严格的类型检查。
泛型支持编程类型作为参数。
泛型能够实现泛型算法。
Java 语言中引入了泛型以在编译时提供更严格的类型检查并支持泛型编程。为了实现泛型,Java 编译器将类型擦除应用于:
如果类型参数是 * 的,则将泛型类型中的所有类型参数替换为其边界或 Object。因此,生成的字节码仅包含普通的类、接口和方法。
必要时插入类型转换以保持类型安全。
生成桥接方法以保留扩展泛型类型中的多态性。
类型擦除确保不会为参数化类型创建新类;因此,泛型不会产生运行时开销。
下面举两个例子
// 类型擦除前
public class Pair<K, V> {
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
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; }
private K key;
private V value;
}
// 类型擦除后
public class Pair {
public Pair(Object key, Object value) {
this.key = key;
this.value = value;
}
public Object getKey() { return key; }
public Object getValue() { return value; }
public void setKey(Object key) { this.key = key; }
public void setValue(Object value) { this.value = value; }
private Object key;
private Object value;
}
// 类型擦除前
public static <T extends Comparable<T>> int findFirstGreaterThan(T[] at, T elem) {
// ...
}
// 类型擦除后
public static int findFirstGreaterThan(Comparable[] at, Comparable elem) {
// ...
}
最后感兴趣的同学可以参考:
Java Tutorial on Generics
Generics in the Java programming language
来源:https://blog.csdn.net/w903328615/article/details/122365001
猜你喜欢
- 1. matlab的lp2lp函数的作用去归一化 H(s) 的分母2. matlab的lp2lp函数的使用方法[z, p, k]=butta
- @TransactionalEventListener监听事务项目背景最近在项目遇到一个问题A方法体内有 INSERT、UPDATE或者DE
- 问题原因Springboot get请求是参数过长抛出异常:Request header is too large 的问题错误描述java.
- 前言Java 8 提供了一组称为 stream 的 API,用于处理可遍历的流式数据。stream API 的设计,充分融合了函数式编程的理
- 本文讲述了在Java中如何创建和结束线程的最基本方法,只针对于Java初学者。一些高级知识如线程同步、调度、线程池等内容将会在后续章节中逐步
- 昨天有朋友在公众号发消息说看不懂await,async执行流,其实看不懂太正常了,因为你没经过社会的毒打,没吃过牢饭就不知道自由有多重要,没
- JWT可以理解为一个加密的字符串,里面由三部分组成:头部(Header)、负载(Payload)、签名(signature)由base64加
- 1:首先。创建一个springboot项目,这里我使用以及构建好基本框架的脚手架,打开是这个样子:Result类:已经封装好了三种返回类型的
- 此解决方案是针对window的,因为日志默认保存路径在C盘,linux忽略。学习RocketMQ过程中,总是出现com.alibaba.ro
- 本文实例为大家分享了新闻列表分页查询的java代码,供大家参考,具体内容如下package com.ibeifeng.test;//创建新闻
- 一、题目描述题目:同步锁出现的目的就是为了解决多线程安全问题。同步锁的几种方式synchronized1、同步代码块2、同步方法jdk1.5
- this可能是几乎所有有一点面向对象思想的语言都会引用到的变量,java自然不例外。只是,this有多少种用法,我也不知道了,让我们来see
- Spring容器可以自动装配相互协作bean之间的关系,这有助于减少对XML配置,而无需编写一个大的基于Spring应用程序的较多的<
- springboot加载yml文件获不到值今天使用spring boot读取yml文件,这种多层嵌套的竟然无法读取到(value注解spri
- 这篇文章主要介绍了Java List分页功能实现代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的
- 一、介绍Properties文件在Java中主要为配置文件,文件类型为:.properties,格式为文本文件,内容格式为"键=值
- 注册中心呢 就是springcloud的一个核心组件 所有微服务的基石 微服务的核心思想就是分布式 所有的服务分开管理 但这些服务分开后该如
- 文件作为存储数据的单元,会根据数据类型产生很多分类,也就是所谓的文件类型。在对数据文件进行操作时,常常需要根据不同的文件类型来作不同的处理。
- 文件资源操作Spring 定义了一个 org.springframework.core.io.Resource 接口,Resource 接口
- 1.try-catch异常处理说明Java提供try和catch块来处理异常,try块用于包含可能出错的代码。catch块用于处理try块中