关于java泛型大大小小的那些事

558 阅读7分钟

引言

泛型,何为泛型。个人理解。表面上看就是广泛的类型呗,没错就是参数化类型,也就是说类型不再是敲定的了,而是在使用的时候以参数的形式去传入进而确定到底使用什么样的类型。说到这有点迷糊了,举个例子。

class A<T>{
    T value;
}

如上这个就是泛型类啊。简单的讲就是当我们想让value的类型为String的时候就可以这样使用A, ,当我们想让value的类型为Integer时候就可以这样使用A。

泛型的作用

  • type cheking at complie time

  • auto cast

我理解的泛型就两个意义,在编译期间做类型检查以及自动转型。这两个作用在下文中我们慢慢的体会。

基本使用

泛型的具体时候上主要是三个地方:泛型类,泛型接口和泛型方法。

泛型类

在引言中举的例子其实就是一个泛型类。我们再来一个例子。

class Response<T>{
    public String msg;
    public int code;
    public T data;
    
}

服务器返回的数据时data这个字段的类型不固定可能是String 也可能是List 或者boolean型。我们使用泛型这种形式就不必为每形式写一个类,是不是很来劲。

需要注意的是静态成员的类型不能是参数化类型,也就是说不能是泛型。原因是,静态成员属于类,所有的对象持有一份,如果静态成员可以是参数化类型,那么不同对象的时候可能会是不同的类型,这样就造成了冲突。

泛型方法

除了泛型类之外也可能单独在方法上使用泛型, 方法的参数中可以使用泛型变量,方法的返回值中也可以使用泛型变量。例子如下。

class GenericFunctionDemo{
    public void <T> getSomeThing(T parameter){
        //做操作
        
    }
}

泛型通配符(?)

泛型中有通配符这个概念,通配符是用来使用规定好的泛型的。单独的<?>表示可以接受任何类型的数据,他是一种形参。栗子:

List<Object> list ;
list = new Array<String>();
list = new Array<Integer>();

上面这段代码是不合法的。 因为声明了list是List类型,而List和List 不存在继承的关系。为了解决诸如此类的问题,通配符出现了把list声明成List<?>就合法了。

再看下面这段代码

List<?> list = new ArrayList<String>();
list.add("hello");//1

上面这段代码1处会报错,咦?这是怎么回事。?表示可以试任何类型但是不能确定是什么类型,list.add(T t)这个方法的参数是T,声明list的时候传入的参数化类型是"?",而?表示不确定是什么类型,不知道是什么类型所以不能使用这个方法。 但是Object o = list.get(0);这是合法的只是返回的类型是Object。

?只能在声明的时候使用,不能出现在创建对象的时候,new ArrayList这样是不可以的。而List list = null;这样没有问题。

  • 所以List<?>能给他赋值任何类型的List如List list List,但是不能给他赋值任何类型的数据,因为不知道他存的到底是什么类型。
  • List 只能赋值给他List ,但能给他添加任何类型的数据。

    带有下边界的通配符

    List<? extend Number> list;
    list = new ArrayList<Integer>();//1
    list = new ArrayList<Float>();//2
    list = new ArrayList<String>();//3
    Number number = list.get(0);//4
    
    带有下边界的通配符,?只能是Number类型及其子类。所以上面这段代码3是不合法的。代码4也是没有问题的,因为的语义已经表明了list存放的元素肯定是Number及其子类类型,父类引用子类没毛病。 但是!不能使用list.add();即使add方法出入的是Number及其子类的对象也不行。原因其实可以能想明白。 ``` List list; list = new ArrayList();//1 list.add(new Float<1.2f>);//2 ``` 代码2处如果合理,不就说明可以往指明了元素为Integer的List对象中加Float类型对象了吗?这样很显然是不合理的。 ## 带有上边界的泛型 ``` List list = null; list = new ArrayList(); Object o = list.get(0);//1 list.add(new Integer(1)); ``` 表示,?是Integer及其父类类型。1处的get方法可以使用没问题,但是返回的是Object类型因为只能确定list中存放的元素类型为Integer及其父类类型但是不能确定到底是哪个父类。add方法是可以使用了无论父类是Number 还是Object都可以往里放,还是那句话父类引用子类没毛病。试想如下代码,是没有问题的 ``` List< Object> list = new ArrayList(); list.add(new Integer(1)); ``` ## PECS原则总结 - 如果要从集合中读取类型T的数据,并且不能写入,可以使用 ? extends 通配符;(Producer Extends) - 如果要从集合中写入类型T的数据,并且不需要读取,可以使用 ? super 通配符;(Consumer Super) - 如果既要存又要取,那么就不要使用任何通配符。 **例子:** ``` public void printNumber(List list){ for(Number number :list){ System.out.println(number.floatValue()); } } public List addNumbers(List list){ list.add(new Integer(1)); list.add(new Float(1.1f)); return list; } public void printList(List list){ for(Object o:list){ System.out.println(); } } ``` # 泛型擦除 java中泛型:类型参数只存在于编译期,在运行时,Java 的虚拟机 并不知道泛型的存在。 带来的问题,比如多态失效。 ``` class Test{ public void test(List list){ } public void test(List list){ } } ``` 上面这段代码在编译时候是不通过,原因就是类型擦除。 # 问题 ## List和List区别

    他们之间的区别主要有两点

    • List的引用只能指向List对象不能指向除类型为Object之外的list;而List<?>可以。
    • List 可以往里面添加任何类型的对象,因为所有的对象所属的类都是Object的子类;而List<?>不行,因为他可以指向任何对象的List但是他不能确定是哪一个具体对象的集合,所以不能往里添加任何对象,但是可以在里面取数据,取出来的都是Object对象。
      List<Object> listObg = new ArrayList<Object>();
      List<?> list = new ArrayList<Object>();
      list = new ArrayList<String>();
      
      • 可以往Listlist中添加任何对象,因为所有的对象都是Object的子类对象,而List<?> 不能,因为不能确定?是什么类型。

        List 和List<?>区别

        • 编译器并不会对List进行类型检查,会对List<?>进行类型检查
        • List

        ?和T的区别

        我们这这里的T称之为自定义泛型类型,定义一种特定的类型;?称之为通配符泛型,代表未知的类型。

        Class <T extends Comparable<? super T>> 表达什么意思?

        T的限定是Comparable的子类,Comparable中的限定是至少是T的父类。 例子:

         class Apple implements Comparable<Apple>{
                int weight = 0;
                @Override
                public int compareTo(@NonNull Apple o) {
                    return this.weight - o.weight;
                }
            }
            class RedApple extends Apple{
        
            }
            class MyData <T extends Comparable<T>>{
                    T data;
            }
            private void test(){
                MyData<RedApple> data1 = null;//编译错误
                MyData<Apple> data2 = null;//ok
            }
        

        为什么不能创建泛型数组

        由于 Java 使用擦除(erasure)实现的泛型,在运行时无法知道确切的类型信息,因此不能创建相应类型的数组。

        静态成员不能是泛型类型

        因为泛型是要在对象创建的时候才知道是什么类型的,而对象创建的代码执行先后顺序是static的部分,然后才是构造函数等等。所以在对象初始化之前static的部分已经执行了,如果你在静态部分引用的泛型,那么毫无疑问虚拟机根本不知道是什么东西,因为这个时候类还没有初始化。因此在静态方法、数据域或初始化语句中,为了类而引用泛型类型参数是非法的。

        • 泛型的作用:编译时做类型检查,取使用数据的时候做自动类型转换。
        • 泛型使用:泛型类和泛型方法。
        • 类型通配符,是一种类型实参,表示任何泛型类型。结合 extends和super使用,分为带上边界的泛型和带下边界的泛型。通常情况下,我们在使用泛型的遵循PECS原则,也就是只读不写的时候使用?extends;只写不读的时候使用?super。其实List<?> 和List<? extends Object>是等价的。