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


猜你喜欢
- 前言在上篇文章讲到了如何配置单数据源,但是在实际场景中,会有需要配置多个数据源的场景,比如说,我们在支付系统中,单笔操作(包含查询、插入、新
- Java连接SQLServer 2008数据库的步骤:1.到微软官方下载jdbc 并解压,得到sqljdbc.jar和sqljdbc4.ja
- 本文实例讲述了C#数据结构之单链表(LinkList)实现方法。分享给大家供大家参考,具体如下:这里我们来看下“单链表(LinkList)”
- 前言最近我在项目写了几万行代码,小伙伴担心会让程序启动速度变慢,所以本渣就来做测试。 本渣使用了代码创建器,创建了 1000 个垃圾文件,这
- 其实我们在社交网络上面所发出的任何信息, 都希望能够保留下来. 那么如何实现呢?数据持久化数据持久化, 就是将内存中的瞬时数据保存在存储设备
- 场景:简单工厂时候,我设计了一个场景,有三种剑去打怪,这时候,需求变化了,我三种剑变成了,匕首、剑以及木棒,想要用工厂方法来实现,怎么弄?1
- WebView 网页滚动截屏,可对整个网页进行截屏而不是仅当前屏幕哦! 注意若Web页面存在position:fixed; 的话得在调用前设
- C# 和 java 比较:java 中使用的是接口。C# 使用委托机制,可以用时 + 运算符进行注册,直接多播。而 java 中是一般是使用
- 概述java中的序列化可能大家像我一样都停留在实现Serializable接口上,对于它里面的一些核心机制没有深入了解过。直到最近在项目中踩
- 执行完post请求后,通常来讲一个最佳实践就是执行重定向。重定向将丢弃原始请求数据,原始请求中的模型数据和请求都会消亡。可以有效避免用户浏览
- 本文实例讲述了java实现mp3合并的方法。分享给大家供大家参考。具体实现方法如下:package test;import java.io.
- 本文实例讲述了java读取properties配置文件的方法。分享给大家供大家参考。具体分析如下:这两天做java项目,用到属性文件,到网上
- 今天给大家带来的是一块用WPF 实现魔方的小游戏,先看一下效果图 代码如下,先写一个类,用来判断是否可以移动using System;usi
- 1、如果只是一个简单的springboot demo,用以下配置就行新建config类```import org.springf
- 前言此节假日为严格按照国家要求的双休和法定节假日并且包含节假日的补班信息,大家可根据自己的需求自定义处理哦。以下为Maven配置,是程序用到
- 引言♀ 小AD:明哥,我终于出了这口恶气了。♂ 明世隐:打爽了是吧。♀ 小AD:那必须的,打十盘我赢九盘,我随意。♂ 明世隐:那小朋友不是搞
- 场景描述在项目开发的过程中,需要修改调试的时候偶每次都需要重启项目浪费时间,下面是我整理的两种常用的两种方式方式一修改启动配置方式(主要针对
- 前言Guava是google公司开发的一款Java类库扩展工具包,内含了丰富的API,涵盖了集合、缓存、并发、I/O等多个方面。使用这些AP
- 本文实例为大家分享了Android实现随手指移动小球的具体代码,供大家参考,具体内容如下这个随手指移动小球,首先要使用paint画笔在can
- 1、conditional注解介绍含义: 基于条件的注解作用: 根据是否满足某一个特定条件来决定是否创建某个特定的bean意义: Sprin