Java 中泛型 T 和 ? 的区别详解

目录
  • 泛型中 T 类型变量 和 ? 通配符 区别
  • Generic Types 类型变量
    • 用法
    • 2.声明通用的方法 – 泛型方法:
    • 有界类型参数
  • Wildcards 通配符
    • 1.上界通配符:? extend 上界类型
    • 2.无界通配符:?
    • 3.下界通配符:? super 子类
  • 类型擦除

泛型中 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 TypesWildcards,以及使用。

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

到此这篇关于Java 中泛型 T 和 ? 的区别详解的文章就介绍到这了,更多相关Java 泛型T和? 区别内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • java 在观察者模式中使用泛型T的实例

    被观察者 public class Observable<T> { List<Observer> observers = new ArrayList<Observer>(); boolean changed = false; /** * Adds the specified observer to the list of observers. If it is already * registered, it is not added a second time. *

  • 浅谈三分钟学习Java泛型中T、E、K、V、?的含义

    泛型是Java中一个非常重要的内容,对于Java进阶学习是必须要掌握的知识点之所以说这个知识点重要,如果你有过阅读过一些开源框架的代码,那你一定会看到源码中有很多地方使用到了泛型. 随便举两个例子,一个List,一个Map. 看了上面的源码,简单聊一下泛型,也就是回顾一下泛型的相关知识,来源百度百科. [泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数.这种参数类型可以用在类.接口和方法的创建中,分别称为泛型类.泛型接口.泛型方法.Java语

  • 一看就懂 详解JAVA泛型通配符T,E,K,V区别

    1. 先解释下泛型概念 泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数.这种参数类型可以用在类.接口和方法的创建中,分别称为泛型类.泛型接口.泛型方法.Java语言引入泛型的好处是安全简单. 在Java SE 1.5之前,没有泛型的情况的下,通过对类型Object的引用来实现参数的"任意化","任意化"带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的.对于强制类型转

  • 详谈Java中的Object、T(泛型)、?区别

    因为最近重新看了泛型,又看了些反射,导致我对Object.T(以下代指泛型).?产生了疑惑. 我们先来试着理解一下Object类,学习Java的应该都知道Object是所有类的父类,注意:那么这就意味着它的范围非常广!首先记住这点,如果你的参数类型时Object,那么的参数类型将非常广! <Thinking in Java>中说很多原因促成了泛型的出现,最引人注目的一个原因就是为了创造容器类.这个要怎么来理解呢?我的理解是,可以抛开这个为了创造容器类这个,而是回到泛型的目的是限定某种类型上来.

  • Java 中泛型 T 和 ? 的区别详解

    目录 泛型中 T 类型变量 和 ? 通配符 区别 Generic Types 类型变量 用法 2.声明通用的方法 – 泛型方法: 有界类型参数 Wildcards 通配符 1.上界通配符:? extend 上界类型 2.无界通配符:? 3.下界通配符:? super 子类 类型擦除 泛型中 T 类型变量 和 ? 通配符 区别 定义不同 :T 是类型变量,? 是通配符 使用范围不同: ? 通配符用作 参数类型.字段类型.局部变量类型,有时作为返回类型(但请避免这样做) T 用作 声明类的类型参数.

  • 基于Java中throw和throws的区别(详解)

    系统自动抛出的异常 所有系统定义的编译和运行异常都可以由系统自动抛出,称为标准异常,并且 Java 强烈地要求应用程序进行完整的异常处理,给用户友好的提示,或者修正后使程序继续执行. 语句抛出的异常 用户程序自定义的异常和应用程序特定的异常,必须借助于 throws 和 throw 语句来定义抛出异常. throw是语句抛出一个异常. 语法:throw (异常对象); throw e; throws是方法可能抛出异常的声明.(用在声明方法时,表示该方法可能要抛出异常) 语法:[(修饰符)](返回

  • Java中接口和抽象类的区别详解

    需求:接口是否可继承接口?抽象类是否可实现(implements)接口?抽象类是否可继承实体类(concrete class)?抽象类中是否可以有静态的main方法? 先说明二者的定义,然后聊聊需求,最后分析二者的区别. 含有abstract修饰符的类即为抽象类,抽象类不能创建实例对象.含有抽象方法的类必须定义为abstract class.在abstract class中,方法不必是抽象的,但是抽象方法必须在具体子类中实现,所以,不能有抽象构造方法或抽象静态方法.子类如果没有实现抽象父类中的所

  • java中ArrayList和LinkedList的区别详解

    ArrayList和LinkedList都实现了List接口,有以下的不同点: 1.ArrayList是基于索引的数据接口,它的底层是数组.它可以以O(1)时间复杂度对元素进行随机访问.与此对应,LinkedList是以元素列表的形式存储它的数据,每一个元素都和它的前一个和后一个元素链接在一起,在这种情况下,查找某个元素的时间复杂度是O(n). 2.相对于ArrayList,LinkedList的插入,添加,删除操作速度更快,因为当元素被添加到集合任意位置的时候,不需要像数组那样重新计算大小或者

  • Java中Exception和Error的区别详解

    世界上存在永远不会出错的程序吗?也许这只会出现在程序员的梦中.随着编程语言和软件的诞生,异常情况就如影随形地纠缠着我们,只有正确的处理好意外情况,才能保证程序的可靠性. java语言在设计之初就提供了相对完善的异常处理机制,这也是java得以大行其道的原因之一,因为这种机制大大降低了编写和维护可靠程序的门槛.如今,异常处理机制已经成为现代编程语言的标配. 今天我要问你的问题是,请对比Exception和Error,另外,运行时异常与一般异常有什么区别? 典型回答 Exception和Error都

  • Java中堆和栈的区别详解

    当一个人开始学习Java或者其他编程语言的时候,会接触到堆和栈,由于一开始没有明确清晰的说明解释,很多人会产生很多疑问,什么是堆,什么是栈,堆和栈有什么区别?更糟糕的是,Java中存在栈这样一个后进先出(Last In First Out)的顺序的数据结构,这就是java.util.Stack.这种情况下,不免让很多人更加费解前面的问题.事实上,堆和栈都是内存中的一部分,有着不同的作用,而且一个程序需要在这片区域上分配内存.众所周知,所有的Java程序都运行在JVM虚拟机内部,我们这里介绍的自然

  • Java中Vector与ArrayList的区别详解

    首先看这两类都实现List接口,而List接口一共有三个实现类,分别是ArrayList.Vector和LinkedList.List用于存放多个元素,能够维护元素的次序,并且允许元素的重复.3个具体实现类的相关区别如下:1.ArrayList是最常用的List实现类,内部是通过数组实现的,它允许对元素进行快速随机访问.数组的缺点是每个元素之间不能有间隔,当数组大小不满足时需要增加存储能力,就要讲已经有数组的数据复制到新的存储空间中.当从ArrayList的中间位置插入或者删除元素时,需要对数组

  • java中SynchronizedList和Vector的区别详解

    前言 Vector是java.util包中的一个类. SynchronizedList是java.util.Collections中的一个静态内部类. 在多线程的场景中可以直接使用Vector类,也可以使用Collections.synchronizedList(List list)方法来返回一个线程安全的List. 那么,到底SynchronizedList和Vector有没有区别,为什么java api要提供这两种线程安全的List的实现方式呢? 首先,我们知道Vector和Arraylis

  • java线程中start和run的区别详解

    这篇文章主要介绍了java线程中start和run的区别详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 public class Test1 extends Thread { @Override public void run() { while (true) { System.out.println(Thread.currentThread().getName()); } } public static void main(String[

  • Java中Stream流中map和forEach的区别详解

    目录 什么是 stream 流 Map forEach 使用场景 不是很难的知识,但是今天犯错了,记录一下 什么是 stream 流 我们在使用集合或数组对元素进行操作时往往会遇到这种情况:通过对不同类型的存储元素,按照特定条件进行查找.排序.等操作时往往会写一大段代码,而且更要命的是,不同类型的数据,操作的方法也不一样,比如一个存储 Student 实体类和一个只存储 String 类型的集合俩者的操作步骤肯定大不一样且无法通用,而 stream API 就解决了这些问题,对数据操作时进行了统

随机推荐