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>”来创建泛型声明,这引入了类型变量 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<Integer> 的读取方式。 泛型类型的调用通常称为参数化类型。 要实例化这个类,像往常一样使用 new 关键字,但将 <Integer> 放在类名和括号之间:

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

 

java泛型函数

java泛型函数简介

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

java泛型函数定义

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

public  <K,V>  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<Number>的参数,但在使用中不能传入Box<Integer>或者Box<Double>,因为不是Box<Number>的子类型。

所以可以有这样一张图:

diagram showing that Box<Integer> is not a subtype of Box<Number>

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

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

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

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

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

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

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

  • PayloadList<String,String>

  • PayloadList<String,Integer>

  • PayloadList<String,Exception>

它们的关系如图所示

diagram showing an example PayLoadList hierarchy: PayloadList<String, String> is a subtype of List<String>, which is a subtype of Collection<String>. 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<B>并不是List<A>的子类型

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

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

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

为了在这些类之间创建关系,以便代码可以通过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<Integer> 是 List<?  extends Integer> 和 List<?super Integer>。 列表<?  extends Integer> 是 List<?  extends Number> 是 List<?> 的子类型。 List<Number> 是 List<?  超级号码>和列表>? 扩展数字>。 列表<?  super Number> 是 List<?  super Integer> 是 List<?> 的子类型。

 

对泛型的限制

这里贴处官网链接

  • 无法使用原始类型实例化泛型类型

  • 无法创建类型参数的实例

  • 不能声明类型为类型参数的静态字段

  • 不能对参数化类型使用 Casts 或instanceof

  • 无法创建参数化类型的数组

  • 无法创建、捕获或抛出参数化类型的对象

  • 无法将每个重载的形式参数类型擦除为相同原始类型的方法重载

posted @ 2021-07-31 11:05  mockingj  阅读(321)  评论(0)    收藏  举报