Java泛型

Java泛型

java泛型简介

Oracle官网中对于java泛型的描述:https://docs.oracle.com/javase/tutorial/java/generics/why.html

泛型的用处:泛型使类型(类和接口)在定义类、接口和方法时成为参数,从而使相同的一段代码处理多种不同类型的传入数据

来自官网的例子,使用泛型的函数或类使用的形式:

List<String> list = new ArrayList<String>();
list.add("hello");
String s = list.get(0);   // no cast

而没有使用泛型时,需要强制转换:

List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0);

java中的List接口,提供对不同类型的类的标准方法。想象一下,若没有java泛型,则只能使用强制转换,并带来某些问题。

对于其中的get()函数:

ArrayList.java
    
......

public E get(int index) {
    Objects.checkIndex(index, size);
    return elementData(index);
}

......

可见此函数直接返回容器内的类,不需要强制转换的步骤,程序更加安全。

泛型使程序员能够实现通用算法。 通过使用泛型,程序员可以实现适用于不同类型集合的泛型算法,安全且易于阅读

java泛型类

java创建泛型类的原因是为了容器类的实现。

容器类是用来保存对象的,将对象放入其中,并对放入的一个或多个对象进行操作,如排序,增加,删除,查找等。这就要求容器类需要接收不同类型的类。所以必须使用一个机制,使容器类对所有的类都来者不拒。泛型类应运而生。

java泛型类简介

举个例子,假设定义一个简单的类:

public class Box {
    private Object object;

    public void set(Object object) { this.object = object; }
    public Object get() { return object; }
}

这样的类存在的问题:此类的功能时接收一个类和返回接收的类,所以可能导致接收的类和期望返回的类(可能使用强制转换)不是同一个类型,导致运行时错误。同时在编译时此类错误无法被识别。

于是,改写为java泛型类:

/**
 * Generic version of the Box class.
 * @param <T> the type of the value being boxed
 */
public class Box<T> {
    // T stands for "Type"
    private T t;

    public void set(T t) { this.t = t; }
    public T get() { return t; }
}

java泛型类保证了传入的类型和期望返回的类型是相同的。使用“public class Box”来创建泛型声明,这引入了类型变量 T,代表传入对象的类型。

java泛型类的定义

java泛型类使用如下格式定义:

class name<T1, T2, ..., Tn> { /* ... */ }

类型参数部分由尖括号 (<>) 分隔,跟在类名之后。 它指定类型参数(也称为类型变量)T1、T2、…和 Tn。

在java的文档上有一个类型参数命名约定:

  • E - Element
  • K - Key
  • N - Number
  • T - Type
  • V - Value
  • S,U,V etc. - 2nd, 3rd, 4th types

java泛型类使用

以Box泛型类为例,要从代码中引用泛型 Box 类,您必须执行泛型类型调用,它将 T 替换为一些具体值,例如 Integer:

Box<Integer> integerBox;

可以将泛型类型调用视为类似于普通方法调用,但不是将参数传递给方法,而是将类型参数(在本例中为 Integer)传递给 Box 类本身

与任何其他变量声明一样,此代码实际上并未创建新的 Box 对象。 它只是声明 integerBox 将保存对“Box of Integer”的引用,这就是 Box 的读取方式。 泛型类型的调用通常称为参数化类型。 要实例化这个类,像往常一样使用 new 关键字,但将 放在类名和括号之间:

Box<Integer> integerBox = new Box<Integer>();//泛型的实例化

java泛型函数

java泛型函数简介

和java泛型类类似,泛型函数通过引入所在类的类型参数。类型参数的范围仅限于声明它的方法。 允许使用静态和非静态泛型方法,以及泛型类构造函数。

java泛型函数定义

泛型方法的语法包括一个类型参数列表,在尖括号内,它出现在方法的返回类型之前,例如:

public  <KV>  boolean function(K k,V v)

对于静态泛型方法,类型参数部分必须出现在方法的返回类型之前,例如:

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 class Pair<K, V> {//泛型类声明

    private K key;//使用类型参数
    private V value;

    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }

    public void setKey(K key) { this.key = key; }
    public void setValue(V value) { this.value = value; }
    public K getKey()   { return key; }
    public V getValue() { return value; }
}

有界类型参数

有界类型参数简介

在使用java泛型的时候,可能会希望限制用作参数化类型中的类型参数的类型,也就是限制传入类的类型种类。譬如,只接受Number类型及其子类的对象作为实例化参数。这就是有界类型参数的用途。

有界类型参数的定义

形如:

<T extends Integer>

称为有界类型参数

直接上示例代码:

public class Box<T> {

    private T t;          

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

    public T get() {
        return t;
    }

    public <U extends Number> void inspect(U u){	//有界类型参数
        System.out.println("T: " + t.getClass().getName());
        System.out.println("U: " + u.getClass().getName());
    }

    public static void main(String[] args) {
        Box<Integer> integerBox = new Box<Integer>();
        integerBox.set(new Integer(10));
        integerBox.inspect("some text"); // error: this is still String!
    }
}

上述代码在编译阶段会检查inspect函数中传输的参数类型,并给出错误提示,以保证传入的类型是Number及其子类。

同样也可以在java泛型类声明的时候使用有界类型参数,在编译此类型参数在类中的函数使用参数类型T的时候进行检查,保证类中所有使用T的函数都符合限定的类型:

public class NaturalNumber<T extends Integer> {

    private T n;

    public NaturalNumber(T n)  { this.n = n; }

    public boolean isEven() {
        return n.intValue() % 2 == 0;
    }

    // ...
}

多重有界类型参数的定义

前面的示例说明了使用具有单个边界的类型参数,但类型参数可以具有多个边界

<T extends B1 & B2 & B3>

具有多个边界的类型变量是边界中列出的所有类型的子类型。如果边界之一是类,则必须首先指定它

Class A { /* ... */ }
interface B { /* ... */ }
interface C { /* ... */ }

class D <T extends A & B & C> { /* ... */ }

如果未首先指定绑定 A,则会出现编译时错误

class D <T extends B & A & C> { /* ... */ }  // compile-time error

泛型的继承和子类

如您所知,只要类型兼容,就可以将一种类型的对象分配给另一种类型的对象。

泛型也是如此。您可以执行泛型类型调用,将 Number 作为其类型参数传递,如果参数与 Number 兼容,则允许任何后续的 add 调用:

Box<Number> box = new Box<Number>();
box.add(new Integer(10));   // OK
box.add(new Double(10.1));  // OK

看看下面的示例

public void boxTest(Box<Number> n) { /* ... */ }

boxTest接受一个类型为Box的参数,但在使用中不能传入Box或者Box,因为不是Box的子类型。

所以可以有这样一张图:

diagram showing that Box is not a subtype of Box

给定两个具体类型 A 和 B(例如,Number 和 Integer),无论 A 和 B 是否相关,MyClass 与 MyClass 都没有关系。 MyClass 和 MyClass 的共同父对象是 Object

那么,java泛型类有怎样的继承关系呢:

diagram showing a sample collections hierarchy: ArrayList is a subtype of List, which is a subtype of Collection.

以 Collections 类为例,ArrayList 实现了 List,而 List 扩展了 Collection。 所以 ArrayList 是 List 的子类型,List 是 Collection 的子类型。 只要不改变类型参数,类型之间的子类型关系就会保留。

若泛型类存在多个类型参数,也可以从但类型参数的泛型类中继承

interface PayloadList<E,P> extends List<E> {
  void setPayload(int index, P val);
  ...
}

PayloadList 的以下参数化是 List 的子类型

  • PayloadList<String,String>
  • PayloadList<String,Integer>
  • PayloadList<String,Exception>

它们的关系如图所示

diagram showing an example PayLoadList hierarchy: PayloadList<String, String> is a subtype of List, which is a subtype of Collection. At the same level of PayloadList<String,String> is PayloadList<String, Integer> and PayloadList<String, Exceptions>.

java通配符

在java泛型中,称为通配符的问号 ( ? )表示未知类型。通配符可用于多种情况:作为参数、字段或局部变量的类型;有时作为返回类型。通配符永远不会用作泛型方法调用、泛型类实例创建或超类型的类型参数。

上界通配符

在Java编程中,可以使用上界通配符来放宽对类型参数的限制。

举一个例子

public static void process(List<? extends Foo> list) { /* ... */ }

上界通配符,<? extends Foo>,其中Foo是任何类型的,匹配Foo和任何Foo的子类。下列process方法代码可以遍历使用list容器的Foo对象或者Foo类的子类对象的List容器类:

public static void process(List<? extends Foo> list) {
    for (Foo elem : list) {
        // ...
    }
}

无界通配符

无界通配符类型使用通配符 ( ? )指定,例如List<?>。这称为未知类型列表。在两种情况下,无界通配符是一种有用的方法:

  • 如果您正在编写可以使用Object类中提供的功能实现的方法。
  • 当代码不依赖于泛型类中的类型参数时。例如,List.sizeList.clear。事实上,Class<?>之所以如此常用,是因为Class<T> 中的大多数方法都不依赖于T

内部没有使用T或者其他类型参数的函数:

public static void printList(List<?> list) {
    for (Object elem: list)
        System.out.print(elem + " ");
    System.out.println();
}

下界通配符

下界通配符将未知类型限制为特定类型或该类型的父类型

以下代码将数字 1 到 10 添加到列表的末尾,该方法适用于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);
    }
}

有关于通配符的子类型

如泛型的继承和子类中所述,泛型类或接口之间的关系不仅仅因为它们的类型之间存在关系。但是可以使用通配符来创建通用类或接口之间的关系。

给定两个非泛型类

class A { /* ... */ }
class B extends A { /* ... */ }

编写下列代码是正确的

B b = new B();
A a = b;

此示例显示常规类的继承遵循此子类型规则:如果B扩展A ,则类B是类A的子类型。此规则不适用于泛型类型:

List<B> lb = new ArrayList<>();
List<A> la = lb;   // compile-time error

上述代码可以看出List并不是List的子类型

于是在学习了java通配符后,有这么一种关系:

图显示 List 和 List 的公共父项是未知类型的列表

List和List共同的父级是List<?>,而List和List之间没有关系。

为了在这些类之间创建关系,以便代码可以通过List<Integer>的元素访问Number的方法,请使用上限通配符:

List<? extends Integer> intList = new ArrayList<>();
List<? extends Number>  numList = intList;  // OK. List<? extends Integer> is a subtype of List<? extends Number>

IntegerNumber的子类型,而numListNumber对象的列表,所以现在intListInteger对象的列表)和numList之间存在关系。下图显示了使用上限和下限通配符声明的几个List类之间的关系。

根据上述规则可以列出通用List类声明的层次结构

图表显示 List 是 List 的子类型。 List 是 List 的子类型。

对泛型的限制

这里贴处官网链接

  • 无法使用原始类型实例化泛型类型
  • 无法创建类型参数的实例
  • 不能声明类型为类型参数的静态字段
  • 不能对参数化类型使用 Casts 或instanceof
  • 无法创建参数化类型的数组
  • 无法创建、捕获或抛出参数化类型的对象
  • 无法将每个重载的形式参数类型擦除为相同原始类型的方法重载
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值