zoukankan      html  css  js  c++  java
  • java 泛型

    https://segmentfault.com/a/1190000014824002

     https://segmentfault.com/a/1190000005337789

    泛型类
    只能用在成员变量上,只能使用引用类型
    用在方法的内部变量
    可以作为方法的返回值来使用

    泛型接口
    只能用在抽象方法上


    泛型方法
    返回值前面加上 <T> ,用<T>来定义方法的泛型

    /**
    * 自定义泛型类
    *
    * 定义"模版"的时候,泛型用泛型字母:T 代替
    * 在使用的时候指定实际类型
    *
    * @author Administrator
    * @param <T>
    */
    public class Student<T> {

    private T javase;

    //private static T javaee; // 泛型不能使用在静态属性上

    public Student() {
    }

    public Student(T javase) {
    this();
    this.javase = javase;
    }

    public T getJavase() {
    return javase;
    }

    public void setJavase(T javase) {
    this.javase = javase;
    }

    }
    /**
    * 自定义泛型的使用
    * 在声明时指定具体的类型
    * 不能为基本类型
    * @author Administrator
    *
    */
    class Demo02 {
    public static void main(String[] args) {
    //Student<int> Student = new Student<int>(); //不能为基本类型,编译时异常

    Student<Integer> student = new Student<Integer>();
    student.setJavase(85);
    System.out.println(student.getJavase());
    }
    }

    泛型接口
    /**
    * 自定义泛型接口
    *
    * 接口中泛型字母只能使用在方法中,不能使用在全局常量中
    *
    * @author Administrator
    * @param <T>
    */
    public interface Comparator<T1, T2> {

    //public static final T1 MAX_VALUE = 100; //接口中泛型字母不能使用在全局常量中
    //T1 MAX_VALUE;
    public static final int MAX_VALUE = 100;

    void compare(T2 t);
    T2 compare();
    public abstract T1 compare2(T2 t);
    }


    /**
    * 非泛型类中定义泛型方法
    * @author Administrator
    *
    */
    public class Method {

    // 泛型方法,在返回类型前面使用泛型字母
    public static <T> void test1(T t){
    System.out.println(t);
    }

    // T 只能是list 或者list 的子类
    public static <T extends List> void test2(T t){
    t.add("aa");
    }

    // T... 可变参数 ---> T[]
    public static <T extends Closeable> void test3(T...a) {
    for (T temp : a) {
    try {
    if (null != temp) {
    temp.close();
    }
    } catch (Exception e) {
    e.printStackTrace();
    }

    }
    }

    public static void main(String[] args) throws FileNotFoundException {
    test1("java 是门好语言");
    test3(new FileInputStream("a.txt"));
    }
    }

    泛型的继承

    /**
    * 泛型继承
    *
    * 保留父类泛型 ----》泛型子类
    * 不保留父类泛型 -----》子类按需实现
    *
    * 子类重写父类的方法,泛型类型随父类而定 子类使用父类的属性,该属性类型随父类定义的泛型
    *
    * @author Administrator
    *
    * @param <T1>
    * @param <T2>
    */
    public abstract class Father<T1, T2> {
    T1 age;

    public abstract void test(T2 name);
    }

    // 保留父类泛型 ----》泛型子类
    // 1)全部保留
    class C1<T1, T2> extends Father<T1, T2> {

    @Override
    public void test(T2 name) {

    }
    }

    // 2) 部分保留
    class C2<T1> extends Father<T1, Integer> {

    @Override
    public void test(Integer name) {

    }
    }

    // 不保留父类泛型 -----》子类按需实现
    // 1)具体类型
    class C3 extends Father<String, Integer> {

    @Override
    public void test(Integer name) {

    }
    }

    // 2)没有具体类型
    // 泛型擦除:实现或继承父类的子类,没有指定类型,类似于Object
    class C4 extends Father {

    @Override
    public void test(Object name) {

    }

    }

    通配符
    通配符(Wildcards)

    T、K、V、E 等泛型字母为有类型,类型参数赋予具体的值
    ?未知类型 类型参数赋予不确定值,任意类型
    只能用在声明类型、方法参数上,不能用在定义泛型类上

    /**
    * 泛型的通配符 类型不确定,用于声明变量或者形参上面
    *
    * 不能使用在类上 或者 new 创建对象上
    * @author Administrator
    *
    */
    public class Demo04 {

    // 用在形参上
    public static void test(List<?> list) {

    List<?> list2; // 用在声明变量上
    list2 = new ArrayList<String>();
    list2 = new ArrayList<Integer>();
    list2 = new ArrayList<Object>();

    }

    public static void main(String[] args) {
    test(new ArrayList<String>());
    test(new ArrayList<Integer>());
    }

    }


    extends/super
    上限(extends)
    指定的类必须是继承某个类,或者实现了某个接口,即
    ? extends List

    下限(super)
    即父类或本身
    ? super List

    /**
    * extends:泛型的上限 <= 一般用于限制操作 不能使用在添加数据上,一般都是用于数据的读取
    *
    * supper:泛型的上限 >= 即父类或自身。一般用于下限操作
    *
    * @author Administrator
    * @param <T>
    */

    public class Test<T extends Fruit> {

    private static void test01() {
    Test<Fruit> t1 = new Test<Fruit>();
    Test<Apple> t2 = new Test<Apple>();
    Test<Pear> t3 = new Test<Pear>();
    }

    private static void test02(List<? extends Fruit> list) {

    }

    private static void test03(List<? super Apple> list) {

    }

    public static void main(String[] args) {

    // 调用test02(),测试 extends <=
    test02(new ArrayList<Fruit>());
    test02(new ArrayList<Apple>());
    test02(new ArrayList<ReadApple>());
    // test02(new ArrayList<Object>()); Object 不是 Fruit 的子类 ,编译不通过


    // 调用test03() ,测试super >=
    test03(new ArrayList<Apple>());
    test03(new ArrayList<Fruit>());
    //test03(new ArrayList<ReadApple>()); ReadApple < apple,所以不能放入
    }

    }

    class Fruit {

    }

    class Apple extends Fruit {

    }

    class Pear extends Fruit {

    }

    class ReadApple extends Apple {

    }

    泛型嵌套
    从外向里取
    /**
    * 泛型嵌套
    * @author Administrator
    *
    */
    public class Demo05 {


    public static void main(String[] args) {
    Student2<String> student = new Student2<String>();
    student.setScore("优秀");
    System.out.println(student.getScore());

    //泛型嵌套
    School<Student2<String>> school = new School<Student2<String>>();
    school.setStu(student);

    String s = school.getStu().getScore(); //从外向里取
    System.out.println(s);

    // hashmap 使用了泛型的嵌套
    Map<String, String> map = new HashMap<String,String>();
    map.put("a", "张三");
    map.put("b", "李四");
    Set<Entry<String, String>> set = map.entrySet();
    for (Entry<String, String> entry : set) {
    System.out.println(entry.getKey()+":"+entry.getValue());
    }

    }
    }

    public class School<T> {
    private T stu;

    public T getStu() {
    return stu;
    }

    public void setStu(T stu) {
    this.stu = stu;
    }

    }

    public class Student2<T> {
    T score;

    public T getScore() {
    return score;
    }

    public void setScore(T score) {
    this.score = score;
    }
    }

    /**
    * 泛型没有多态
    * 泛型没有数组
    * JDK1.7对泛型的简化
    * @author Administrator
    *
    */
    public class Demo06 {

    public static void main(String[] args) {
    Fruit fruit = new Apple(); // 多态,父类的引用指向子类的对象
    //List<Fruit> list = new ArrayList<Apple>(); //泛型没有多态
    List<? extends Fruit> list = new ArrayList<Apple>();

    //泛型没有数组
    //Fruit<String>[] fruits = new Fruit<String>[10];

    //ArrayList底层是一个Object[],它放数据的时候直接放,取数据的时候强制类型转化为泛型类型
    /*public boolean add(E e) {
    ensureCapacityInternal(size + 1); // Increments modCount!!
    elementData[size++] = e;
    return true;
    }*/

    /*E elementData(int index) {
    return (E) elementData[index];
    }*/


    //JDK1.7泛型的简化,1.6编译通不过
    List<Fruit> list2 = new ArrayList<>();
    }
    }

    擦除带来的问题
    擦除会出现一些问题,下面是一个例子:

    class HasF {
    public void f() {
    System.out.println("HasF.f()");
    }
    }
    public class Manipulator<T> {
    private T obj;

    public Manipulator(T obj) {
    this.obj = obj;
    }

    public void manipulate() {
    obj.f(); //无法编译 找不到符号 f()
    }

    public static void main(String[] args) {
    HasF hasF = new HasF();
    Manipulator<HasF> manipulator = new Manipulator<>(hasF);
    manipulator.manipulate();

    }
    }

    上面的 Manipulator 是一个泛型类,内部用一个泛型化的变量 obj,在 manipulate 方法中,调用了 obj 的方法 f(),但是这行代码无法编译。因为类型擦除,编译器不确定 obj 是否有 f() 方法。解决这个问题的方法是给 T 一个边界:

    class Manipulator2<T extends HasF> {
    private T obj;
    public Manipulator2(T x) { obj = x; }
    public void manipulate() { obj.f(); }
    }
    现在 T 的类型是 <T extends HasF>,这表示 T 必须是 HasF 或者 HasF 的导出类型。这样,调用 f() 方法才安全。HasF 就是 T 的边界,因此通过类型擦除后,所有出现 T 的
    地方都用 HasF 替换。这样编译器就知道 obj 是有方法 f() 的。

    类型擦除的补偿
    类型擦除导致泛型丧失了一些功能,任何在运行期需要知道确切类型的代码都无法工作。比如下面的例子:


    public class Erased<T> {
    private final int SIZE = 100;
    public static void f(Object arg) {
    if(arg instanceof T) {} // Error
    T var = new T(); // Error
    T[] array = new T[SIZE]; // Error
    T[] array = (T)new Object[SIZE]; // Unchecked warning
    }
    }
    通过 new T() 创建对象是不行的,一是由于类型擦除,二是由于编译器不知道 T 是否有默认的构造器。一种解决的办法是传递一个工厂对象并且通过它创建新的实例。

    interface FactoryI<T> {
    T create();
    }
    class Foo2<T> {
    private T x;
    public <F extends FactoryI<T>> Foo2(F factory) {
    x = factory.create();
    }
    // ...
    }
    class IntegerFactory implements FactoryI<Integer> {
    public Integer create() {
    return new Integer(0);
    }
    }
    class Widget {
    public static class Factory implements FactoryI<Widget> {
    public Widget create() {
    return new Widget();
    }
    }
    }
    public class FactoryConstraint {
    public static void main(String[] args) {
    new Foo2<Integer>(new IntegerFactory());
    new Foo2<Widget>(new Widget.Factory());
    }
    }

    另一种解决的方法是利用模板设计模式:

    abstract class GenericWithCreate<T> {
    final T element;
    GenericWithCreate() { element = create(); }
    abstract T create();
    }
    class X {}
    class Creator extends GenericWithCreate<X> {
    X create() { return new X(); }
    void f() {
    System.out.println(element.getClass().getSimpleName());
    }
    }
    public class CreatorGeneric {
    public static void main(String[] args) {
    Creator c = new Creator();
    c.f();
    }
    }
    具体类型的创建放到了子类继承父类时,在 create 方法中创建实际的类型并返回。

    Java 泛型总结(二):泛型与数组
    简介
    上一篇文章介绍了泛型的基本用法以及类型擦除的问题,现在来看看泛型和数组的关系。数组相比于Java 类库中的容器类是比较特殊的,主要体现在三个方面:

    数组创建后大小便固定,但效率更高
    数组能追踪它内部保存的元素的具体类型,插入的元素类型会在编译期得到检查
    数组可以持有原始类型 ( int,float等 ),不过有了自动装箱,容器类看上去也能持有原始类型了
    那么当数组遇到泛型会怎样? 能否创建泛型数组呢?这是这篇文章的主要内容。

    这个系列的另外两篇文章:

    Java 泛型总结(一):基本用法与类型擦除
    Java 泛型总结(三):通配符的使用
    泛型数组
    如何创建泛型数组
    如果有一个类如下:

    class Generic<T> {

    }
    如果要创建一个泛型数组,应该是这样: Generic<Integer> ga = new Generic<Integer>[]。不过行代码会报错,也就是说不能直接创建泛型数组。

    那么如果要使用泛型数组怎么办?一种方案是使用 ArrayList,比如下面的例子:

    public class ListOfGenerics<T> {
    private List<T> array = new ArrayList<T>();
    public void add(T item) { array.add(item); }
    public T get(int index) { return array.get(index); }
    }
    如何创建真正的泛型数组呢?我们不能直接创建,但可以定义泛型数组的引用。比如:

    public class ArrayOfGenericReference {
    static Generic<Integer>[] gia;
    }
    gia 是一个指向泛型数组的引用,这段代码可以通过编译。但是,我们并不能创建这个确切类型的数组,也就是不能使用 new Generic<Integer>[]。具体参见下面的例子:

    public class ArrayOfGeneric {
    static final int SIZE = 100;
    static Generic<Integer>[] gia;
    @SuppressWarnings("unchecked")
    public static void main(String[] args) {
    // Compiles; produces ClassCastException:
    //! gia = (Generic<Integer>[])new Object[SIZE];
    // Runtime type is the raw (erased) type:
    gia = (Generic<Integer>[])new Generic[SIZE];
    System.out.println(gia.getClass().getSimpleName());
    gia[0] = new Generic<Integer>();
    //! gia[1] = new Object(); // Compile-time error
    // Discovers type mismatch at compile time:
    //! gia[2] = new Generic<Double>();
    Generic<Integer> g = gia[0];
    }
    } /*输出:
    Generic[]
    *///:~
    数组能追踪元素的实际类型,这个类型是在数组创建的时候建立的。上面被注释掉的一行代码: gia = (Generic<Integer>[])new Object[SIZE],数组在创建的时候是一个 Object 数组,如果转型便会报错。成功创建泛型数组的唯一方式是创建一个类型擦除的数组,然后转型,如代码: gia = (Generic<Integer>[])new Generic[SIZE]。gia 的 Class 对象输出的名字是 Generic[]。

    我个人的理解是:由于类型擦除,所以 Generic<Integer> 相当于初始类型 Generic,那么 gia = (Generic<Integer>[])new Generic[SIZE] 中的转型其实还是转型为 Generic[],看上去像没转,但是多了编译器对参数的检查和自动转型,向数组插入 new Object() 和 new Generic<Double>() 均会报错,而 gia[0] 取出给 Generic<Integer> 也不需要我们手动转型。

    使用 T[] array
    上面的例子中,元素的类型是泛型类。下面看一个元素本身类型是泛型参数的例子:

    public class GenericArray<T> {
    private T[] array;
    @SuppressWarnings("unchecked")
    public GenericArray(int sz) {
    array = (T[])new Object[sz]; // 创建泛型数组
    }
    public void put(int index, T item) {
    array[index] = item;
    }
    public T get(int index) { return array[index]; }
    // Method that exposes the underlying representation:
    public T[] rep() { return array; } //返回数组 会报错
    public static void main(String[] args) {
    GenericArray<Integer> gai =
    new GenericArray<Integer>(10);
    // This causes a ClassCastException:
    //! Integer[] ia = gai.rep();
    // This is OK:
    Object[] oa = gai.rep();
    }
    }
    在上面的代码中,泛型数组的创建是创建一个 Object 数组,然后转型为 T[]。但数组实际的类型还是 Object[]。在调用 rep()方法的时候,就报 ClassCastException 异常了,因为 Object[] 无法转型为 Integer[]。

    那创建泛型数组的代码 array = (T[])new Object[sz] 为什么不会报错呢?我的理解和前面介绍的类似,由于类型擦除,相当于转型为 Object[],看上去就是没转,但是多了编译器的参数检查和自动转型。而如果把泛型参数改成 <T extends Integer>,那么因为类型是擦除到第一个边界,所以 array = (T[])new Object[sz] 中相当于转型为 Integer[],这应该会报错。下面是实验的代码:

    public class GenericArray<T extends Integer> {
    private T[] array;
    @SuppressWarnings("unchecked")
    public GenericArray(int sz) {
    array = (T[])new Object[sz]; // 创建泛型数组
    }
    public void put(int index, T item) {
    array[index] = item;
    }
    public T get(int index) { return array[index]; }
    // Method that exposes the underlying representation:
    public T[] rep() { return array; } //返回数组 会报错
    public static void main(String[] args) {
    GenericArray<Integer> gai =
    new GenericArray<Integer>(10);
    // This causes a ClassCastException:
    //! Integer[] ia = gai.rep();
    // This is OK:
    Object[] oa = gai.rep();
    }
    }
    相比于原始的版本,上面的代码只修改了第一行,把 <T> 改成了 <T extends Integer>,那么不用调用 rep(),在创建泛型数组的时候就会报错。下面是运行结果:

    Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;
    at GenericArray.<init>(GenericArray.java:15)
    使用 Object[] array
    由于擦除,运行期的数组类型只能是 Object[],如果我们立即把它转型为 T[],那么在编译期就失去了数组的实际类型,编译器也许无法发现潜在的错误。因此,更好的办法是在内部最好使用 Object[] 数组,在取出元素的时候再转型。看下面的例子:

    public class GenericArray2<T> {
    private Object[] array;
    public GenericArray2(int sz) {
    array = new Object[sz];
    }
    public void put(int index, T item) {
    array[index] = item;
    }
    @SuppressWarnings("unchecked")
    public T get(int index) { return (T)array[index]; }
    @SuppressWarnings("unchecked")
    public T[] rep() {
    return (T[])array; // Warning: unchecked cast
    }
    public static void main(String[] args) {
    GenericArray2<Integer> gai =
    new GenericArray2<Integer>(10);
    for(int i = 0; i < 10; i ++)
    gai.put(i, i);
    for(int i = 0; i < 10; i ++)
    System.out.print(gai.get(i) + " ");
    System.out.println();
    try {
    Integer[] ia = gai.rep();
    } catch(Exception e) { System.out.println(e); }
    }
    } /* Output: (Sample)
    0 1 2 3 4 5 6 7 8 9
    java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;
    *///:~

    现在内部数组的呈现不是 T[] 而是 Object[],当 get() 被调用的时候数组的元素被转型为 T,这正是元素的实际类型。不过调用 rep() 还是会报错, 因为数组的实际类型依然是Object[],终究不能转换为其它类型。使用 Object[] 代替 T[] 的好处是让我们不会忘记数组运行期的实际类型,以至于不小心引入错误。

    使用类型标识
    其实使用 Class 对象作为类型标识是更好的设计:

    public class GenericArrayWithTypeToken<T> {
    private T[] array;
    @SuppressWarnings("unchecked")
    public GenericArrayWithTypeToken(Class<T> type, int sz) {
    array = (T[])Array.newInstance(type, sz);
    }
    public void put(int index, T item) {
    array[index] = item;
    }
    public T get(int index) { return array[index]; }
    // Expose the underlying representation:
    public T[] rep() { return array; }
    public static void main(String[] args) {
    GenericArrayWithTypeToken<Integer> gai =
    new GenericArrayWithTypeToken<Integer>(
    Integer.class, 10);
    // This now works:
    Integer[] ia = gai.rep();
    }
    }
    在构造器中传入了 Class<T> 对象,通过 Array.newInstance(type, sz) 创建一个数组,这个方法会用参数中的 Class 对象作为数组元素的组件类型。这样创建出的数组的元素类型便不再是 Object,而是 T。这个方法返回 Object 对象,需要把它转型为数组。不过其他操作都不需要转型了,包括 rep() 方法,因为数组的实际类型与 T[] 是一致的。这是比较推荐的创建泛型数组的方法。

    总结
    数组与泛型的关系还是有点复杂的,Java 中不允许直接创建泛型数组。

    数组的协变
    在了解通配符之前,先来了解一下数组。Java 中的数组是协变的,什么意思?看下面的例子:

    class Fruit {}
    class Apple extends Fruit {}
    class Jonathan extends Apple {}
    class Orange extends Fruit {}

    public class CovariantArrays {
    public static void main(String[] args) {
    Fruit[] fruit = new Apple[10];
    fruit[0] = new Apple(); // OK
    fruit[1] = new Jonathan(); // OK
    // Runtime type is Apple[], not Fruit[] or Orange[]:
    try {
    // Compiler allows you to add Fruit:
    fruit[0] = new Fruit(); // ArrayStoreException
    } catch(Exception e) { System.out.println(e); }
    try {
    // Compiler allows you to add Oranges:
    fruit[0] = new Orange(); // ArrayStoreException
    } catch(Exception e) { System.out.println(e); }
    }
    } /* Output:
    java.lang.ArrayStoreException: Fruit
    java.lang.ArrayStoreException: Orange
    *///:~
    main 方法中的第一行,创建了一个 Apple 数组并把它赋给 Fruit 数组的引用。这是有意义的,Apple 是 Fruit 的子类,一个 Apple 对象也是一种 Fruit 对象,所以一个 Apple 数组也是一种 Fruit 的数组。这称作数组的协变,Java 把数组设计为协变的,对此是有争议的,有人认为这是一种缺陷。

    尽管 Apple[] 可以 “向上转型” 为 Fruit[],但数组元素的实际类型还是 Apple,我们只能向数组中放入 Apple或者 Apple 的子类。在上面的代码中,向数组中放入了 Fruit 对象和 Orange 对象。对于编译器来说,这是可以通过编译的,但是在运行时期,JVM 能够知道数组的实际类型是 Apple[],所以当其它对象加入数组的时候就会抛出异常。

    泛型设计的目的之一是要使这种运行时期的错误在编译期就能发现,看看用泛型容器类来代替数组会发生什么:

    // Compile Error: incompatible types:
    ArrayList<Fruit> flist = new ArrayList<Apple>();
    上面的代码根本就无法编译。当涉及到泛型时, 尽管 Apple 是 Fruit 的子类型,但是 ArrayList<Apple> 不是 ArrayList<Fruit> 的子类型,泛型不支持协变。

    使用通配符
    从上面我们知道,List<Number> list = ArrayList<Integer> 这样的语句是无法通过编译的,尽管 Integer 是 Number 的子类型。那么如果我们确实需要建立这种 “向上转型” 的关系怎么办呢?这就需要通配符来发挥作用了。

    上边界限定通配符
    利用 <? extends Fruit> 形式的通配符,可以实现泛型的向上转型:

    public class GenericsAndCovariance {
    public static void main(String[] args) {
    // Wildcards allow covariance:
    List<? extends Fruit> flist = new ArrayList<Apple>();
    // Compile Error: can’t add any type of object:
    // flist.add(new Apple());
    // flist.add(new Fruit());
    // flist.add(new Object());
    flist.add(null); // Legal but uninteresting
    // We know that it returns at least Fruit:
    Fruit f = flist.get(0);
    }
    }
    上面的例子中, flist 的类型是 List<? extends Fruit>,我们可以把它读作:一个类型的 List, 这个类型可以是继承了 Fruit 的某种类型。注意,这并不是说这个 List 可以持有 Fruit 的任意类型。通配符代表了一种特定的类型,它表示 “某种特定的类型,但是 flist 没有指定”。这样不太好理解,具体针对这个例子解释就是,flist 引用可以指向某个类型的 List,只要这个类型继承自 Fruit,可以是 Fruit 或者 Apple,比如例子中的 new ArrayList<Apple>,但是为了向上转型给 flist,flist 并不关心这个具体类型是什么。

    如上所述,通配符 List<? extends Fruit> 表示某种特定类型 ( Fruit 或者其子类 ) 的 List,但是并不关心这个实际的类型到底是什么,反正是 Fruit 的子类型,Fruit 是它的上边界。那么对这样的一个 List 我们能做什么呢?其实如果我们不知道这个 List 到底持有什么类型,怎么可能安全的添加一个对象呢?在上面的代码中,向 flist 中添加任何对象,无论是 Apple 还是 Orange 甚至是 Fruit 对象,编译器都不允许,唯一可以添加的是 null。所以如果做了泛型的向上转型 (List<? extends Fruit> flist = new ArrayList<Apple>()),那么我们也就失去了向这个 List 添加任何对象的能力,即使是 Object 也不行。

    另一方面,如果调用某个返回 Fruit 的方法,这是安全的。因为我们知道,在这个 List 中,不管它实际的类型到底是什么,但肯定能转型为 Fruit,所以编译器允许返回 Fruit。

    了解了通配符的作用和限制后,好像任何接受参数的方法我们都不能调用了。其实倒也不是,看下面的例子:

    public class CompilerIntelligence {
    public static void main(String[] args) {
    List<? extends Fruit> flist =
    Arrays.asList(new Apple());
    Apple a = (Apple)flist.get(0); // No warning
    flist.contains(new Apple()); // Argument is ‘Object’
    flist.indexOf(new Apple()); // Argument is ‘Object’

    //flist.add(new Apple()); 无法编译

    }
    }
    在上面的例子中,flist 的类型是 List<? extends Fruit>,泛型参数使用了受限制的通配符,所以我们失去了向其中加入任何类型对象的例子,最后一行代码无法编译。

    但是 flist 却可以调用 contains 和 indexOf 方法,它们都接受了一个 Apple 对象做参数。如果查看 ArrayList 的源代码,可以发现 add() 接受一个泛型类型作为参数,但是 contains 和 indexOf 接受一个 Object 类型的参数,下面是它们的方法签名:

    public boolean add(E e)
    public boolean contains(Object o)
    public int indexOf(Object o)
    所以如果我们指定泛型参数为 <? extends Fruit> 时,add() 方法的参数变为 ? extends Fruit,编译器无法判断这个参数接受的到底是 Fruit 的哪种类型,所以它不会接受任何类型。

    然而,contains 和 indexOf 的类型是 Object,并没有涉及到通配符,所以编译器允许调用这两个方法。这意味着一切取决于泛型类的编写者来决定那些调用是 “安全” 的,并且用 Object 作为这些安全方法的参数。如果某些方法不允许类型参数是通配符时的调用,这些方法的参数应该用类型参数,比如 add(E e)。

    当我们自己编写泛型类时,上面介绍的就有用了。下面编写一个 Holder 类:

    public class Holder<T> {
    private T value;
    public Holder() {}
    public Holder(T val) { value = val; }
    public void set(T val) { value = val; }
    public T get() { return value; }
    public boolean equals(Object obj) {
    return value.equals(obj);
    }
    public static void main(String[] args) {
    Holder<Apple> Apple = new Holder<Apple>(new Apple());
    Apple d = Apple.get();
    Apple.set(d);
    // Holder<Fruit> Fruit = Apple; // Cannot upcast
    Holder<? extends Fruit> fruit = Apple; // OK
    Fruit p = fruit.get();
    d = (Apple)fruit.get(); // Returns ‘Object’
    try {
    Orange c = (Orange)fruit.get(); // No warning
    } catch(Exception e) { System.out.println(e); }
    // fruit.set(new Apple()); // Cannot call set()
    // fruit.set(new Fruit()); // Cannot call set()
    System.out.println(fruit.equals(d)); // OK
    }
    } /* Output: (Sample)
    java.lang.ClassCastException: Apple cannot be cast to Orange
    true
    *///:~
    在 Holer 类中,set() 方法接受类型参数 T 的对象作为参数,get() 返回一个 T 类型,而 equals() 接受一个 Object 作为参数。fruit 的类型是 Holder<? extends Fruit>,所以set()方法不会接受任何对象的添加,但是 equals() 可以正常工作。

    下边界限定通配符
    通配符的另一个方向是 “超类型的通配符“: ? super T,T 是类型参数的下界。使用这种形式的通配符,我们就可以 ”传递对象” 了。还是用例子解释:

    public class SuperTypeWildcards {
    static void writeTo(List<? super Apple> apples) {
    apples.add(new Apple());
    apples.add(new Jonathan());
    // apples.add(new Fruit()); // Error
    }
    }
    writeTo 方法的参数 apples 的类型是 List<? super Apple>,它表示某种类型的 List,这个类型是 Apple 的基类型。也就是说,我们不知道实际类型是什么,但是这个类型肯定是 Apple 的父类型。因此,我们可以知道向这个 List 添加一个 Apple 或者其子类型的对象是安全的,这些对象都可以向上转型为 Apple。但是我们不知道加入 Fruit 对象是否安全,因为那样会使得这个 List 添加跟 Apple 无关的类型。

    在了解了子类型边界和超类型边界之后,我们就可以知道如何向泛型类型中 “写入” ( 传递对象给方法参数) 以及如何从泛型类型中 “读取” ( 从方法中返回对象 )。下面是一个例子:

    public class Collections {
    public static <T> void copy(List<? super T> dest, List<? extends T> src)
    {
    for (int i=0; i<src.size(); i++)
    dest.set(i,src.get(i));
    }
    }
    src 是原始数据的 List,因为要从这里面读取数据,所以用了上边界限定通配符:<? extends T>,取出的元素转型为 T。dest 是要写入的目标 List,所以用了下边界限定通配符:<? super T>,可以写入的元素类型是 T 及其子类型。

    无边界通配符
    还有一种通配符是无边界通配符,它的使用形式是一个单独的问号:List<?>,也就是没有任何限定。不做任何限制,跟不用类型参数的 List 有什么区别呢?

    List<?> list 表示 list 是持有某种特定类型的 List,但是不知道具体是哪种类型。那么我们可以向其中添加对象吗?当然不可以,因为并不知道实际是哪种类型,所以不能添加任何类型,这是不安全的。而单独的 List list ,也就是没有传入泛型参数,表示这个 list 持有的元素的类型是 Object,因此可以添加任何类型的对象,只不过编译器会有警告信息。

    总结
    通配符的使用可以对泛型参数做出某些限制,使代码更安全,对于上边界和下边界限定的通配符总结如下:

    使用 List<? extends C> list 这种形式,表示 list 可以引用一个 ArrayList ( 或者其它 List 的 子类 ) 的对象,这个对象包含的元素类型是 C 的子类型 ( 包含 C 本身)的一种。
    使用 List<? super C> list 这种形式,表示 list 可以引用一个 ArrayList ( 或者其它 List 的 子类 ) 的对象,这个对象包含的元素就类型是 C 的超类型 ( 包含 C 本身 ) 的一种。
    大多数情况下泛型的使用比较简单,但是如果自己编写支持泛型的代码需要对泛型有深入的了解。这几篇文章介绍了泛型的基本用法、类型擦除、泛型数组以及通配符的使用,涵盖了最常用的要点,泛型的总结就写到这里

  • 相关阅读:
    汇编入门——使用DOSBox写一个HelloWorld以及相关软件安装
    HCNA-链路聚合(手工模式)
    逆向工程-真码保存在系统文件破解QQ游戏对对碰助手
    逆向工程-获得IPsearch的注册码
    遇见tongtong的思绪
    rhel7--06-预习--磁盘分区命令
    rhel7--05--第三章管道符与重定向符
    rhel7--01--安装
    虚拟机Centos8,没有网络,wired图标消失
    kali是靶体,内置工具可就地取材------网络安全法要天天供奉
  • 原文地址:https://www.cnblogs.com/handsome1013/p/9988032.html
Copyright © 2011-2022 走看看