java

泛型

2019-10-09  本文已影响0人  spraysss

泛型有关的术语中英对照

术语 中文 例子
Generic Types 泛型 Box<T>
Parameterized Type 参数化泛型 Box<Integer>
Type Parameter 类型参数 T
Raw Types 原生类型 Box
Generic Methods 泛型方法 public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2)
Bounded Type Parameters 有界类型参数 <E extends Number>
Multiple Bounds 多上界 class D <T extends A & B & C> { /* ... */ }
Recursive type bound 递归类型上界 <T extends Comparable<T>>
Type Inference 类型推断
Target Types 目标类型
Wildcards 通配符 ?
Upper Bounded Wildcards 上界通配符 <? extends A>
Unbounded Wildcards 无界通配符
Lower Bounded Wildcards 下界通配符 <? super A>
Wildcard Capture 通配符捕获
Type Erasure 泛型擦除

泛型的好处

Type is the common superinterface for all types in the Java programming language. These include raw types, parameterized types, array types, type variables and primitive types.

Type是java语言中所有类型的公共父接口, 这些类型包括raw typesparameterized typesarray typestype variablesprimitive types

Generic Types

A generic type is a generic class or interface that is parameterized over types

Generic Types(泛型)是类型被参数化的类或者接口,比如:

public class Box<T> {
    // T stands for "Type"
    private T t;

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

Type Parameter 命名规范

泛型实例化

Box<Integer> integerBox = new Box<>();

Raw Types

Box<String> stringBox = new Box<>();
Box rawBox = stringBox;               // OK
Box rawBox = new Box();           // rawBox is a raw type of Box<T>
Box<Integer> intBox = rawBox;     // warning: unchecked conversion
Box<String> stringBox = new Box<>();
Box rawBox = stringBox;
rawBox.set(8);  // warning: unchecked invocation to set(T)

应该尽量避免使用raw type

Generic Methods

泛型方法的泛型参数只在方法内有效,静态泛型方法、非静态泛型方法以及泛型构造方法都是允许的

如下面的例子所示,Util类有个compare的泛型方法用于比较两个Pair对象是否相等

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; }
}

显式调用泛型方法:

Pair<Integer, String> p1 = new Pair<>(1, "apple");
Pair<Integer, String> p2 = new Pair<>(2, "pear");
boolean same = Util.<Integer, String>compare(p1, p2);

类型推断调用泛型方法

Pair<Integer, String> p1 = new Pair<>(1, "apple");
Pair<Integer, String> p2 = new Pair<>(2, "pear");
boolean same = Util.compare(p1, p2);

Bounded Type Parameters

使用extends关键字声明类型参数的上界,extends是一个广义的概念,可以是类继承,也可以是方法实现。

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!
    }
}

Multiple Bounds

<T extends B1 & B2 & B3>

多上界类型变量(type variable)是所有声明的上界的子类,如果其中有一个类和多个接口,那么类必须写在最前面。

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

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

否则会出现编译错误

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

泛型方法和有界类型参数

有界类型参数是实现泛型算法的关键,考虑如下方法计算数组T[]中大于elem的元素个数

public static <T> int countGreaterThan(T[] anArray, T elem) {
    int count = 0;
    for (T e : anArray)
        if (e > elem)  // compiler error
            ++count;
    return count;
}

上述实现虽然很直观,但是无法通过编译,这是因为>只能用于比较诸如 short, int, double, long, float, byte, 和 char这些原子数据类型而无法比较对象
为了解决这个问题可以使用Comparable<T> 接口作为上界

public interface Comparable<T> {
    public int compareTo(T o);
}

最终结果如下

public static <T extends Comparable<T>> int countGreaterThan(T[] anArray, T elem) {
    int count = 0;
    for (T e : anArray)
        if (e.compareTo(elem) > 0)
            ++count;
    return count;
}

泛型继承和子类

面向对象中的继承是一种IS A的关系,子类向父类转换是合法的:

Object someObject = new Object();
Integer someInteger = new Integer(10);
someObject = someInteger;   // OK

在泛型中,也同样适用:

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) { /* ... */ }

这个参数接受什么类型呢,它可以接受Box<Integer>Box<Double>吗,答案是否定的,因为它们不是Box<Number>的子类

️无论类A和类B有什么关系,MyClass<A>MyClass<B>都没有任何关系,它们的公共父类是Object

通过extends 和implements来维持继承关系在泛型中依然有效,以ArrayList为例,

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
public interface List<E> extends Collection<E>

ArrayList<E>实现了List<E>,List<E>继承了 Collection<E>. 因此 ArrayList<String>List<String>的子类,List<String>Collection<String>的子类. 因此只要类型参数E保持一致(在本例中是String),继承关系就是有效的:

现在假设我们定义了自己的接口,并且添加了额外的泛型参数 P

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

那么泛型类继承关系如下:


Type Inference

java 编译器通过方法调用和相应声明执行类型推断
如下面例子所示,第二个参数会被推断为Serializable

static <T> T pick(T a1, T a2) { return a2; }
Serializable s = pick("d", new ArrayList<String>());

类型推断和泛型方法

public class BoxDemo {

  public static <U> void addBox(U u, 
      java.util.List<Box<U>> boxes) {
    Box<U> box = new Box<>();
    box.set(u);
    boxes.add(box);
  }

  public static <U> void outputBoxes(java.util.List<Box<U>> boxes) {
    int counter = 0;
    for (Box<U> box: boxes) {
      U boxContents = box.get();
      System.out.println("Box #" + counter + " contains [" +
             boxContents.toString() + "]");
      counter++;
    }
  }

  public static void main(String[] args) {
    java.util.ArrayList<Box<Integer>> listOfIntegerBoxes =
      new java.util.ArrayList<>();
    BoxDemo.<Integer>addBox(Integer.valueOf(10), listOfIntegerBoxes);
    BoxDemo.addBox(Integer.valueOf(20), listOfIntegerBoxes);
    BoxDemo.addBox(Integer.valueOf(30), listOfIntegerBoxes);//类型推断
    BoxDemo.outputBoxes(listOfIntegerBoxes);
  }
}

输出结果如下:

Box #0 contains [10]
Box #1 contains [20]
Box #2 contains [30]

类型推断和泛型类的实例化

泛型类的实例化使用<>来启用类型推断

Map<String, List<String>> myMap = new HashMap<>();

泛型和非泛型类的泛型构造器类型推断

️无论是泛型类还是非泛型类都可以拥有泛型方法,对于泛型类,构造器的泛型参数可以和类声明的泛型参数不同,比如:

class MyClass<X> {
  <T> MyClass(T t) {
    // ...
  }
}

考虑MyClass的实例化:

MyClass<Integer> myObject = new MyClass<>("");

在这个例子中类的泛型参数XInteger,构造器的类型参数T被推断为String

It is important to note that the inference algorithm uses only invocation arguments, target types, and possibly an obvious expected return type to infer types. The inference algorithm does not use results from later in the program.

类型推断并不能在调用过程中传递

Target Types

java编译器使用目标类型来推断泛型方法调用的类型参数,考虑Collections.emptyList:

static <T> List<T> emptyList();

考虑如下赋值语句,泛型参数T被推断为String

List<String> listOne = Collections.emptyList();
//List<String> listOne = Collections.<String>emptyList();

考虑如下场景:

void processStringList(List<String> stringList) {
    // process stringList
}

在java SE7,如下语句无法编译通过:

processStringList(Collections.emptyList());

并且会给出如下的错误

List<Object> cannot be converted to List<String>

因此在java SE7中必须显示指定泛型参数的类型

processStringList(Collections.<String>emptyList());

Java SE 8强化了类型推断, target type也可以应用于 method arguments上,因此不用显示的指定

processStringList(Collections.emptyList());

Wildcards

在泛型中是通配符,表示未知类型。通配符可以被用在很多地方比如:

Upper Bounded Wildcards

如果你想写一个方法能够接受List<Integer>, List<Double>, List<Number>作为参数,可以使用上界通配符:

List<? extends Number>

相对于List<Numeber>严格接受Number类型元素,List<? extends Number>可以接受Number及其子类作为元素。

public static double sumOfList(List<? extends Number> list) {
    double s = 0.0;
    for (Number n : list)
        s += n.doubleValue();
    return s;
}

如下代码打印sum = 6.0

List<Integer> li = Arrays.asList(1, 2, 3);
System.out.println("sum = " + sumOfList(li));

如下代码打印 sum = 7.0:

List<Double> ld = Arrays.asList(1.2, 2.3, 3.5);
System.out.println("sum = " + sumOfList(ld));

Unbounded Wildcards

List<?>这种形式称之为无界通配符,代表一个未知类型的list,有两种类型比较适合使用无界通配符:

看下面这个例子:

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

printList本意是打印任何类型list中的元素,但是上述代码却只能打印List<Object>无法打印 List<Integer>, List<String>, List<Double>,因为这些不是List<Object>的子类。想要实现这个功能可以使用List<?>

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

因为对应任意的一个具体A类型, List<A> 是 List<?>的子类,所以可以打印任何类型的list

List<Integer> li = Arrays.asList(1, 2, 3);
List<String>  ls = Arrays.asList("one", "two", "three");
printList(li);
printList(ls);

List<Object> 和List<?> 是不同的。List<Object>可以插入Object及其子类,而List<?>只能插入null

Lower Bounded Wildcards

下界通配符写法<? super A>

假设你想写一个方法用于在list中存放一些整数,为了最大化灵活性,希望可以支持任何可以存放Integer的List,比如List<Integer>, List<Number>, List<Object>:

public static void addNumbers(List<? super Integer> list) {
    for (int i = 1; i <= 10; i++) {
        list.add(i);
    }
}

通配符和子类

虽然IntegerNumber的子类,List<Integer>却不是List<Number>的子类,事实上这个两个类没有关系,它们的公共父类是List<?>.

更一般的:


Wildcard Capture and Helper Methods

编译器对通配符的推断称之为 wildcard capture

import java.util.List;

public class WildcardError {

    void foo(List<?> i) {
        i.set(0, i.get(0));
    }
}

在java7的环境中使用javac编译会报错:

WildcardError.java:6: error: method set in interface List<E> cannot be applied to given types;
    i.set(0, i.get(0));
     ^
  required: int,CAP#1
  found: int,Object
  reason: actual argument Object cannot be converted to CAP#1 by method invocation conversion
  where E is a type-variable:
    E extends Object declared in interface List
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Object from capture of ?
1 error

增加hleper类修复错误

public class WildcardFixed {

    void foo(List<?> i) {
        fooHelper(i);
    }


    // Helper method created so that the wildcard can be captured
    // through type inference.
    private <T> void fooHelper(List<T> l) {
        l.set(0, l.get(0));
    }

}

Type Erasure

java编译器在运行代码时会进行泛型擦处:

类型参数保证了泛型类在运行时不会参数额外的Class对象,因此泛型不会增加运行时的负担。如下例所示,不同的泛型化A类的实例是同一个Class对象

public class Test {
    static class  A<T>{

    }
    public static void main(String[] args) {
        System.out.println(new A<Integer>().getClass()==new A<String>().getClass());//true
    }
}

Erasure of Generic Types

public class Node<T> {

    private T data;
    private Node<T> next;

    public Node(T data, Node<T> next) {
        this.data = data;
        this.next = next;
    }

    public T getData() { return data; }
    // ...
}

java 编译器会将T替换为Object

public class Node {

    private Object data;
    private Node next;

    public Node(Object data, Node next) {
        this.data = data;
        this.next = next;
    }

    public Object getData() { return data; }
    // ...
}

对于如下代码:

public class Node<T extends Comparable<T>> {

    private T data;
    private Node<T> next;

    public Node(T data, Node<T> next) {
        this.data = data;
        this.next = next;
    }

    public T getData() { return data; }
    // ...
}

Java编译器会将T替换为第一个有界类,也就是Comparable

public class Node {

    private Comparable data;
    private Node next;

    public Node(Comparable data, Node next) {
        this.data = data;
        this.next = next;
    }

    public Comparable getData() { return data; }
    // ...
}

Erasure of Generic Methods

java编译器同样会对泛型方法上的类型参数进行擦除

// Counts the number of occurrences of elem in anArray.
//
public static <T> int count(T[] anArray, T elem) {
    int cnt = 0;
    for (T e : anArray)
        if (e.equals(elem))
            ++cnt;
        return cnt;
}

擦除后的结果如下:

public static int count(Object[] anArray, Object elem) {
    int cnt = 0;
    for (Object e : anArray)
        if (e.equals(elem))
            ++cnt;
        return cnt;
}

考虑如下代码:

class Shape { /* ... */ }
class Circle extends Shape { /* ... */ }
class Rectangle extends Shape { /* ... */ }

定义如下泛型方法:

public static <T extends Shape> void draw(T shape) { /* ... */ }

java编译器擦除后的结果如下:

public static void draw(Shape shape) { /* ... */ }

Effects of Type Erasure and Bridge Methods

有时候直接泛型擦除会出现问题,这时候编译器会创建Bridge Methods作为泛型擦除的补充

public class Node<T> {

    public T data;

    public Node(T data) { this.data = data; }

    public void setData(T data) {
        System.out.println("Node.setData");
        this.data = data;
    }
}

public class MyNode extends Node<Integer> {
    public MyNode(Integer data) { super(data); }

    public void setData(Integer data) {
        System.out.println("MyNode.setData");
        super.setData(data);
    }
}

考虑如下代码:

MyNode mn = new MyNode(5);
Node n = mn;            // A raw type - compiler throws an unchecked warning
n.setData("Hello");     
Integer x = mn.data;    // Causes a ClassCastException to be thrown.

,编译器生成的代码自动添加了一个Bridge method

public class Node {

    public Object data;

    public Node(Object data) { this.data = data; }

    public void setData(Object data) {
        System.out.println("Node.setData");
        this.data = data;
    }
}
class MyNode extends Node {

    // Bridge method generated by the compiler
    //
    public void setData(Object data) {
        setData((Integer) data);
    }

    public void setData(Integer data) {
        System.out.println("MyNode.setData");
        super.setData(data);
    }

    // ...
}

Non-Reifiable Types

java 泛型的一些限制

实战

fastjosn中使用TypeReference处理泛型反序列化

测试一下:

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;

import java.util.Map;

public class TypeReferenceTest {
    public static <K, V> Map<K, V> parseToMap(String json,
                                              Class<K> keyType,
                                              Class<V> valueType) {
        return JSON.parseObject(json,
                new TypeReference<Map<K, V>>(keyType, valueType) {
                });
    }

    public static <K, U, V> Map<K, Map<U, V>> parseToMapMap(String json,
                                                            Class<K> keyType,
                                                            Class<U> uType,
                                                            Class<V> valueType) {
        return JSON.parseObject(json,
                new TypeReference<Map<K, Map<U, V>>>(keyType, uType, valueType) {
                });
    }

    public static class Model {
        private String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return "Model{" +
                    "name='" + name + '\'' +
                    '}';
        }
    }

    public static void main(String[] args) {
        String json = "{1:{name:\"ddd\"},2:{name:\"zzz\"}}";

        Map<Integer, Model> map = parseToMap(json, Integer.class, Model.class);
        System.out.println(map);//{1=Model{name='ddd'}, 2=Model{name='zzz'}}

        //嵌套泛型
        Map<Integer, Map<String, String>> mapMap = parseToMapMap(json, Integer.class, String.class, String.class);
        System.out.println(mapMap);//{1={name=ddd}, 2={name=zzz}}

    }


}

[https://github.com/alibaba/fastjson/wiki/TypeReference](https://github.com/alibaba/fastjson/wiki/TypeReference

上一篇下一篇

猜你喜欢

热点阅读