黑马程序员--Java基础加强--06.【泛型通配符限定】【个人总结】

来源:互联网 发布:网络摄像机的价格 编辑:程序博客网 时间:2024/06/05 00:27

泛型通配符限定 总结

----------- android培训、java培训、java学习型技术博客、期待与您交流! ------------

本篇日志着重总结extends和super在泛型限定中的使用方法

1.    泛型通配符限定

1). extends, super和? 的基本使用规则

(1). 定义类型参数的规则

[1]. 自定义类型参数限制

定义类型参数的时候,可以使用extends关键字,但是不能使用super关键字或者 ?

[2]. 自定义类型参数格式

       定义的格式:<T>或者<T extends 某种类型>

[2]. 类型参数定义位置

    类型参数可被定义的位置有以下三种:

{1}. 类的class和{之间

e.g. class GenericFuncitonII<T> {…

     class GenericFuncitonIIII<T extends Number>{…

{2}. 非静态方法访问修饰符返回值类型 之间

e.g. public <T> void printFunc(…

     public <T extends Number> void printFunc(Tobj){…

{3}. 静态方法static返回值类型 之间

e.g. public static <T> void printFunc(…

(2). 使用类型参数或者? 的规则

当想使用已定义过参数类型的时候,就用类型参数

没有类型参数可用又想使用泛型的时候,就用?替代类型参数

[1]. <>使用类型参数或者?的格式:

【前提:类型参数T前面被定义过】

{1}. 单独使用类型参数格式:<T>

{2}. 单独使用占位符格式:

<?>,       <? extends某种具体类型><? super某种具体类型>

{3}. 联合使用类型参数占位符格式

       <? extends T><? super T>

【注意】自定义类型变量 如果放在super或者extends前面无法编译通过的。

MyEclipse验证如下:


[2]. 脱离<>使用类型参数格式:直接使用T即可

       此时就可以把这个T看做一种普通数据类型就可以。普通数据类型出现在哪这个T就能现在哪里

       【注意】?是不可以脱离<>使用的!!!!

因为Java的标示符命名规则是:26字母大小写,下划线_,美元符$ 或者 0-9数组。?并不在合法标示符的组成范围之内,所以不可以单独使用。

[3]. 直接使用参数化类型格式原始数据类型<某种固定的数据类型>

       这种情况下,这就是一种固定下来的类型,和普通类型使用的方式是一致的。

[3]. 使用类型参数或者占位符位置(指的是含有<>使用位置)

{1}. 方法参数列表

e.g. public void printColl(ArrayList<?/T> al){…

publicvoid printColl(ArrayList<?extends/superT> al){…

{2}. 方法返回值类型

    e.g. public static Class<?> forName(Stringname,boolean initialize, ClassLoader loader)

{3}. 方法函数体

public <T> void printColl(ArrayList<? extends T> al){

    Iterator<? extends T> it =al.iterator();

    while(it.hasNext()){

        Objectobj =it.next();

        System.out.print(obj+" ");

}

    System.out.println();

}

*****总结】无论是参数化类型直接使用<>中的类型参数,还是连带<>使用?/类型变量这些都视为和普通数据类型一样的。普通类型可以怎么用,这些和泛型有关的类型就可以怎么用【只要是不出现在泛型类型参数定义的位置上就可以】******

2). 使用extends关键字进行泛型扩展的举例

(1). 分析参数化类型作为函数的参数的时候,如何简化统一

[1]. 要简化的代码如下:

public void printColl(ArrayList<Long> al){…}

public void printColl(ArrayList<Integer> al){…}

Long和Integer的直接公共父类是:Number

采用泛型限定的方式进行统一这种重载类似的情况,可以考虑向上限定extends或者向下限定super两种方式进行函数扩展。

[2]. 使用extends泛型限定:有两种解决方式:

{1}. void printColl(ArrayList<?extendsNumber> al){…} //?单独出现

{2}. void printColl(ArrayList<? extendsT> al){…}    //?和T组合出现

采用组合出现这种方式只有一个类型参数TT的来源类类型参数或者方法类型参数

{2}1. 当以类类型参数定义的时候:

创建实例的时候可以指定泛型的参数,后面的操作都会受到这个指定的具体类型限制,所以这种方式我们需要的语义

举例1:定义成泛型类的限制

 

[2]. 使用extends泛型限定:有两种解决方式:

{2}2. 当以方法类型参数定义的时候:

泛型方法不用提前限制类型直接接收参数,会导致只要是泛型,就可以传进来。失去了限定的意义。

【结论】联合出现的时候,类型参数定义到类上面

举例2:定义成泛型方法的没有限制


编译通过,没有办法限制成为Number的子类。String类型的集合同样可以传入这个泛型方法中。

[2]. 使用super泛型限定:只有一种解决方式,就是联合出现方式

{1}. 分析: void printColl(ArrayList<? superInteger> al){…}

            void printColl(ArrayList<? superLong> al){…}

Integer和Long的父类出现在super之前的占位符里面了。但是,?中是不定的。所以想通过类似于<? extends 某个固定类>的方式写成<? super 某个固定类>是不可以的。

如果写成占位符单独出现的话,就会像上面一样,IntegerLong是写死在代码中的,不具有通用型,所以必须用变化的类型参数来统一Integer和Long,那就定义一个类型参数单独出现的占位符改装成占位符和类型参数联合出现的形式。

{2}.  <T>void printColl(ArrayList<? superT> al){…}

本来是限定Number的子类Integer和Long。但是这个时候没有办法从这个方法中Number子类的这种限制。所以,单独使用super进行的限定是没有意义的。

{3}. void printColl(ArrayList<?superT> al){…} 将T定义到泛型类上面

【说明】这样是可以通过类建立的对象的时候来限定住T。但是只能传T的超类对象作为打印的目标:


这种定义的泛型类A的printColl方法不能打印Long类型的ArrayList!!无法实现预期的要求。

【结论】简单的参数化类型的简化一般使用extends作为泛型扩展的关键字即可。如果解决不了,采用super即可。

3). 使用super关键字进行泛型扩展的举例

(1). 背景

如果集合中的元素不具有排序性或者本身具有的排序功能不符合需求的时候,需要向在TreeSet结合初始化的时候,向其中传入Comparator的实现类:

[1]. 这个TreeSet的构造函数是:public TreeSet(Comparator<?super E> comparator);

[2]. Comparator这个接口的定义是:publicinterfaceComparator<T> {…}

(2). 问题

[1]. TreeSet构造函数中的E是什么?

【技巧1!!!

注意:查看别人的API的时候,如果发现未知的数据类型定义的时候,就应该往泛型的类型参数上想!!这个时候马上留意这个类型参数的定义位置:

{1}. 先在这个方法中寻找类型参数的定义  -----若找到,是泛型方法

{2}. 如果没找到,就要回到这个类定义的时候,去寻找这个类型参数的定义。----泛型类

一定要找到,这个类型参数关系到能不能看懂这个带有泛型限定的API方法

答:这个构造方法中没有出现类型参数E的定义,那就回到这个TreeSet这个类去寻找E的定义,如下:

public class TreeSet<E>…{
}

在泛型类TreeSet定义的时候找到了这个E的定义位置。E表示容器中元素的类型,是一个类型参数。

【技巧2明确类型参数之后,来理解这个方法的含义

注意:带有泛型限定的部分要从语义上理解!!!

重点理解占位符和extends或者super之后的类型参数或者固定的某个类型关系!!!!。

public TreeSet(Comparator<?super E>comparator)这个构造方法中:添加的Comparator中比较的对象就是TreeSet这个集合中元素类型E超类/父类

[2]. 用super的原因是什么?可以用extends么?

在(3)中进行了解答。

(3). 使用extends好还是使用super好?

[1]. 需求:将Student类和Worker类都存入TreeSet集合中。Student类和Worker类都继承于Person类。

实体类代码:

class Person{private String name;public Person(String name){this.name =name;}public String getName(){return this.name;}}class Student extends Person{public Student(String name){super(name);}}class Worker extends Person{public Worker(String name){super(name);}}

[2]. 常规做法:为了能将某一个类的实例存入相应的TreeSet集合中,应该对这个类实现相应的Comparator接口

{1}. Student类型对应的Comparator接口如下:

//Student类型class StuComparator implements Comparator<Student>{    public int compare(Student o1, Student o2) {        return o1.getName().compareTo(o2.getName());    }} //Worker类型class WorkerComparator implements Comparator<Worker>{    public int compare(Worker o1, Worker o2) {        return o1.getName().compareTo(o2.getName());    }}

两个compare方法可以发现里面的功能是一样的。这样的代码非常冗余。

将会被应用到TreeSet构造方法

【技巧:接口,写成参数化类型的父类引用】

TreeSet(Comparator<Worker>() workComp);

TreeSet(Comparator<Student>() stuComp);

 [3]. 优化:采用泛型限定优化两个参数化类型

看成Woker和Student构成多态, 统一成直接父类Person,非Object,所以采用泛型限定来升华

{1}. 采用extends进行泛型限定,有两种做法:

{1}1. ?单独出现+固定类

TreeSet(Comparator<? extendsPerson>() personComp);

【说明】由于TreeSet不知道传入的元素E什么类型更不可能知道这个元素的父类是什么类型,所以写死成Person并不可取。

{1}2. ?T同时出现泛型方法

<T> TreeSet(Comparator<? extends T>() personComp);

【说明】泛型方法如果仅仅出现一个泛型限定的参数化类型, 这个意义并不大,限制不住。因为泛型方法传入参数的时候,并不是像泛型类一样,先限定住泛型的类型参数。这样泛型类里面用到的泛型类的类型参数就全部被限定住了。这个舍弃。

{1}3. ?T同时出现泛型类

public class TreeSet<E, T>…{

    TreeSet(Comparator<? extends T>() workComp){…}

}

【说明】做成泛型类,由于E和T是两个不相关的类型参数,所以就必须独立定义到类的上面。因此,就要改变TreeSet的泛型类的定义。

这样就要求创建这个TreeSet的时候,必须指定TreeSet中元素的类型E及T。此时没有办法直接建立E和T的关系。因为E相关的类型被淹没在<? extends T>的?中。无法与T直接建立关系。所以,直接建立TreeSet就指定两个类型E和T是不可取的。

这样这两个

{2}. 只能采用super进行泛型限定,但是只有一种做法:占位符和类型参数同时出现

{2}1. 泛型方法:

<T> TreeSet(Comparator<? super T>() personComp);

【说明】与<T> TreeSet(Comparator<? extends T>() personComp);这种被否决掉的原因相同。

{2}2. 泛型类:

public class TreeSet<E, T>…{

    TreeSet(Comparator<? super T>() Comp){…}

}

这是发现,E是我们创建TreeSet集合的元素的类型。

我们这里,TreeSet添加的都是子类类型,所以E要么是Worker,要么是Student

super后面也是?的子类类型。所以这里的T和TreeSet中定义的E实际上不就是同一个类型么!这样就合并成如下形式:

public class TreeSet<E>…{

    TreeSet(Comparator<? super E>() Comp){…}

}

这种形式可取!!

4). extends 和super使用的场合总结

综合以上的例子,super和extends到底怎么使用?

(1). extends和super使用过程中的共性

都是重载或者重载类似各个子类共性的代码都通过多态类似的形式抽取父类引用作为参数的方法。

(2). extends和super使用过程中的区别

[1]. 需求是统一子类引用方法------需求是抽取之后就替代

如果想使用以父类为引用的方法作为子类引用方法的替代,就满足需求的话。这里应该使用extends进行泛型限定正确。使用super进行限定是错误的。

voidprintColl(ArrayList<Long> al){…}

void printColl(ArrayList<Integer> al){…}

{1}. 统一成extends之后

void printColl(ArrayList<? extends T> al){…}//T最后限定为Number

此时子类被限定在?中。只要T被传为Number,那么?就可以传Number任意的子类。这二个正确。

{2}. 统一成super之后

    void printColl(ArrayList<? super T>al){…}

此时子类被限定在T中。只要T被传为Integer,那么?就一定是Integer的父类。那么此时想传入Long的ArrayList,这个方法就不能接受这个类型的对象,因为Long不是Integer的父类。

【结论】super不适用于统一子类重载方法的需求,但是extends适用

[2]. 需求是:为不同子类函数传入相同的父类的功能  ------需求是抽取之后进行组合

StuComparatorimplements Comparator<Worker>{…}

WorkComparatorimplements Comparator<Student>{…}

抽取成父类:

{1}1. extends 抽取成父类做固定类之后:

PersonComparatorimplements Comparator<? extends T>{…}//T最后定为Person

{1}2. super 抽取成父类存在于?之后:

PersonComparatorimplements Comparator<? super T>{…}//T定为Person的子类

{2}. 由于需求的改变,此时要把这个抽取的类再次传入TreeSet中 -----组合

public class TreeSet<E>…{

    TreeSet(Comparator<xxx>() Comp){…}

}

{2}1. 如果此时使用extends进行泛型限定, 固定的父类和TreeSet的E没有关系。此时只能修改成:

public class TreeSet<E, T>…{

    TreeSet(Comparator<? extends T>() Comp){…}

}

但是,这样就要求定义TreeSet的时候,要指定一个和E无关的类型T,不合适。但是也不算错。

{2}2. 如果此时使用super进行泛型限定, 固定的子类T和TreeSet的E实际上是一个意思,所以此时可以修改成:

public class TreeSet<E>…{

    TreeSet(Comparator<? super E>() Comp){…}

}

super此时更合理。

{2}3. 抽取出来的Person功能

class ComparatorMy implements Comparator<Person>{    public int compare(Person p1, Person p2){        return p1.getName().compareTo(p2.getName());    }}

{2}4. 抽取出来的Person功能传入

Student的TreeSet<Student> 和 TreeSet<Worker>

TreeSet<Student> tsS =new TreeSet<Student>(new ComparatorMy());tsS.add(new Student("asdas:Ben"));tsS.add(new Student("gfs:Ben"));GenericDemoX.printColl(tsS);           TreeSet<Worker> tsW =new TreeSet<Worker>(new ComparatorMy());tsW.add(new Worker("asdaasdasdas-----Ben"));GenericDemoX.printColl(tsW);


【总结】当从重载的子类方法中抽取出来共性父类方法之后,再次组合到 (做参数) 子类特有的方法。如果以子类固定super扩展的话,传入的固定子类与子类有关接收的的方法正好都有相同的子类,此时两个子类参数类型统一,使用super更合适。extends也不算错,但是需要两个类型参数,一个子类,一个父类,不方便。

----------- android培训、java培训、java学习型技术博客、期待与您交流! ------------