泛型及java中的泛型

当作笔记整理的~~~  大量转载自:http://www.cnblogs.com/lwbqqyumidi/p/3837629.html

首先,引出堆对象这个概念。

什么是堆对象,就是程序在运行过程中可以随时建立或者删除的对象,可以用new运算符(或malloc函数)或者delete运算符(或free函数)。泛型可以看作是一类堆对象。

泛型是程序设计语言的一种特性。允许程序员在强类型程序设计语言中编写代码时定义一些可变部分,那些部分在使用前必须作出指明。

各种程序设计语言和其编译器、运行环境对泛型的支持均不一样。将类型参数化以达到代码复用提高软件开发工作效率的一种数据类型。

泛型的定义主要有两种:
1.在程序编码中一些包含类型参数类型,也就是说泛型的参数只可以代表类,不能代表个别对象。(这是当今较常见的定义)

2.在程序编码中一些包含参数的类。其参数可以代表类或对象等等。(人们大多把这称作模板)不论使用哪个定义,泛型的参数在真正使用泛型时都必须作出指明

https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/generics/generic-classes  这是微软对于泛型类的解释。

其中指出了泛型类最常用于集合,如链接列表、哈希表、堆栈、队列、树等。 像从集合中添加和移除项这样的操作都以大体上相同的方式执行,与所存储数据的类型无关。

知道定义了,那么使用泛型有哪些好处呢?       这个最后再将,先看看java中的泛型。

 

JAVA中的泛型:

 先看下面这行代码:

public class GenericTest {

    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("qqyumidi");
        list.add(100);

        for (int i = 0; i < list.size(); i++) {
            String name = (String) list.get(i); // 1
            System.out.println("name:" + name);
        }
    }
}

可以看出,这里定义了一个List集合,先加入了String字符串,又加入了Integer类型的值,这是不报错的,因为他们都是Object对象。编译时是正常的,不出错的,但是运行时会报出:“java.lang.ClassCastException”异常。因此,导致此类错误编码过程中不易发现。

上面的代码告诉我们两个信息:

1:当我们将一个对象放入集合中,集合不会记住此对象的类型,当再次从集合中取出此对象时,改对象的编译类型变成了Object类型,但其运行时类型任然为其本身类型。

2:因此,//1处取出集合元素时需要人为的强制类型转化到具体的目标类型,且很容易出现“java.lang.ClassCastException”异常。

泛型就是使集合能够记住集合内元素各类型。

下面有个例子:

public class GenericTest {

    public static void main(String[] args) {
        /*
        List list = new ArrayList();
        list.add("qqyumidi");
        list.add("corn");
        list.add(100);
        */

        List<String> list = new ArrayList<String>();
        list.add("qqyumidi");
        list.add("corn");
        //list.add(100);   // 1  提示编译错误

        for (int i = 0; i < list.size(); i++) {
            String name = list.get(i); // 2
            System.out.println("name:" + name);
        }
    }
}

采用泛型写法后,在//1处想加入一个Integer类型的对象时会出现编译错误,通过List<String>,直接限定了list集合中只能含有String类型的元素,从而在//2处无须进行强制类型转换,因为此时,集合能够记住元素的类型信息,编译器已经能够确认它是String类型了。

结合上面的泛型定义,我们知道在List<String>中,String是类型实参,也就是说,相应的List接口中肯定含有类型形参。且get()方法的返回结果也直接是此形参类型(也就是对应的传入的类型实参)。下面就来看看List接口的的具体定义:

public interface List<E> extends Collection<E> {

    int size();

    boolean isEmpty();

    boolean contains(Object o);

    Iterator<E> iterator();

    Object[] toArray();

    <T> T[] toArray(T[] a);

    boolean add(E e);

    boolean remove(Object o);

    boolean containsAll(Collection<?> c);

    boolean addAll(Collection<? extends E> c);

    boolean addAll(int index, Collection<? extends E> c);

    boolean removeAll(Collection<?> c);

    boolean retainAll(Collection<?> c);

    void clear();

    boolean equals(Object o);

    int hashCode();

    E get(int index);

    E set(int index, E element);

    void add(int index, E element);

    E remove(int index);

    int indexOf(Object o);

    int lastIndexOf(Object o);

    ListIterator<E> listIterator();

    ListIterator<E> listIterator(int index);

    List<E> subList(int fromIndex, int toIndex);
}

我们可以看到,在List接口中采用泛型化定义之后,<E>中的E表示类型形参,可以接收具体的类型实参,并且此接口定义中,凡是出现E的地方均表示相同的接受自外部的类型实参。

 

泛型接口、泛型类和泛型方法也是可以自定义的:

 上面的内容中,讲解了泛型具体的运行过程,接口、类和方法也都可以用泛型去定义,以及相应的使用。  具体的使用过程中,可以分为泛型接口、泛型类和泛型方法。

下面看一个自定义泛型接口,类,方法的例子,与List,ArrayList很相似:

package java_Collection;

public class test01 {

    public static void main(String[] args){
        Bos<String> bos = new Bos<String>("gogo");
        System.out.println(bos.getData());
    }

}

class Bos<T>{
    private T data;
    
    public Bos(){
        
    }
    public Bos(T data){
        this.data = data;
    }
    public T getData(){
        return data;
    }
}

在泛型接口、泛型类和泛型方法的定义过程中,我们常见的如T、E、K、V等形式的参数常用于表示泛型形参,由于接收来自外部使用时候传入的类型实参。那么对于不同传入的类型实参,生成的相应对象实例的类型是不是一样的呢?

    public static void main(String[] args){
        Bos<String> bos1 = new Bos<String>("gogo");
        Bos<Integer> bos2 = new Bos<Integer>(1234);
        System.out.println("bos1 name:"+bos1.getClass());      //bos1 name:class java_Collection.Bos
        System.out.println("bos2 name:"+bos2.getClass());      //bos2 name:class java_Collection.Bos
        System.out.println(bos1.getClass()==bos2.getClass());  //true
    }

根据上面的实验可以得出,使用泛型的时候,传进了不同的泛型实参,但是并没有生成了真正的不同类型,在内存中传入不同类型的泛型实参的泛型类只有一个,就是原先的最基本类型,我们可以从逻辑上理解成不同的类型。

具体原因是什么呢? 这与java提出泛型这一概念的目的有关,导致其只是作用于代码的编译阶段,在编译过程中,对于正确检验泛型结果后,会将泛型的相关信息擦出,也就是说,编译成功的.class文件中是不包含任何泛型信息的。泛型的信息是不会进入到运行时阶段。  可以总结为:泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型。

 

下面就是类型通配符的概念了。

现在,Bos<Integer>和Bos<Number>实际上都是Bos类型,现在需要继续探讨:

那么在逻辑上,类似于Box<Number>和Box<Integer>是否可以看成具有父子关系的泛型类型呢?

下面看:

public class GenericTest {

    public static void main(String[] args) {

        Box<Number> name = new Box<Number>(99);
        Box<Integer> age = new Box<Integer>(712);

        getData(name);
        
        //The method getData(Box<Number>) in the type GenericTest is 
        //not applicable for the arguments (Box<Integer>)
        getData(age);   // 1

    }
    
    public static void getData(Box<Number> data){
        System.out.println("data :" + data.getData());
    }

}

在 //1处会报错,The method getData(Box<Number>) in the t ype GenericTest is not applicable for the arguments (Box<Integer>)。显然,Box<Number>在逻辑上不能视为Box<Integer>的父类。那么,原因何在呢?

可以用反证法说明。

好,那我们回过头来继续看“类型通配符”中的第一个例子,我们知道其具体的错误提示的深层次原因了。那么如何解决呢?总不能再定义一个新的函数吧。这和Java中的多态理念显然是违背的,因此,我们需要一个在逻辑上可以用来表示同时是Box<Integer>和Box<Number>的父类的一个引用类型,由此,类型通配符应运而生。

 

类型通配符一般是使用 ? 代替具体的类型实参。注意了,此处是类型实参,而不是类型形参!且Box<?>在逻辑上是Box<Integer>、Box<Number>...等所有Box<具体类型实参>的父类。由此,我们依然可以定义泛型方法,来完成此类需求。

public class GenericTest {

    public static void main(String[] args) {

        Box<String> name = new Box<String>("corn");
        Box<Integer> age = new Box<Integer>(712);
        Box<Number> number = new Box<Number>(314);

        getData(name);
        getData(age);
        getData(number);
    }

    public static void getData(Box<?> data) {
        System.out.println("data :" + data.getData());
    }

}

有时候,我们还可能听到类型通配符上限和类型通配符下限。具体有是怎么样的呢?

在上面的例子中,如果需要定义一个功能类似于getData()的方法,但对类型实参又有进一步的限制:只能是Number类及其子类。此时,需要用到类型通配符上限。

public class GenericTest {

    public static void main(String[] args) {

        Box<String> name = new Box<String>("corn");
        Box<Integer> age = new Box<Integer>(712);
        Box<Number> number = new Box<Number>(314);

        getData(name);
        getData(age);
        getData(number);
        
        //getUpperNumberData(name); // 1
        getUpperNumberData(age);    // 2
        getUpperNumberData(number); // 3
    }

    public static void getData(Box<?> data) {
        System.out.println("data :" + data.getData());
    }
    
    public static void getUpperNumberData(Box<? extends Number> data){
        System.out.println("data :" + data.getData());
    }

}

此时,显然,在代码//1处调用将出现错误提示,而//2 //3处调用正常。

类型通配符上限通过形如Box<? extends Number>形式定义,相对应的,类型通配符下限为Box<? super Number>形式,其含义与类型通配符上限正好相反,在此不作过多阐述了。

注意: java中没有所谓的泛型数组一说。

posted @ 2017-07-20 21:50  lzycw  阅读(183)  评论(0编辑  收藏  举报