泛型的优缺点和泛型的使用场景

时间:2022-08-08 21:17:14
泛型类和泛型方法

泛型类:具有一个或多个类型变量的类,称之为泛型类

       比如:classA<T> {

}

                Class A<k,v>{

                }

泛型方法:具有一个或多个类型变量的方法,称之为泛型方法

        比如:public<T> T fun(T t1) {}

        但是请注意:public Tfun(T t1) {},不是泛型方法。

使用泛型的规则

1.    泛型可以使用在返回类型和参数类型上面

比如:public T fun(T t) {}

2.    泛型可以使用在成员变量上面和类型上面

比如:classA<T> {

              private T bean;

                  }

3.    泛型可以使用在局部变量上面

比如:publicvoid fun2() {

               T b = …..;

        }

4.    泛型不可以new

比如:new T();//不行的!

5.    在创建泛型类实例时,需要为其类型变量赋值

比如:A<String>a = new A<String>();

  * 如果创建实例时,不给类型变量赋值,那么会有一个警告!

       6.泛型方法与泛型类没什么关系,泛型方法不一定非要在泛型类中

泛型的继承和实现

       1.规则:

              *子类不是泛型类:需要给父类传递类型常量

              比如:class AA extends A<String> {},这个时候AA不是泛型类,而A是泛型类,这个时候需要指定子类中泛型的类型常量,这个时候父类中所有的T都会被String替换

        * 子类是泛型类:可以给父类传递类型常量,也可以传递类型变量

                     比如:class AA3<E> extends A<E> {}

                              class AA3<E> extends A<String> {}

泛型的局限性

就拿集合来说:

        List<String>list = new ArrayList<String>();

        //List<Object>list2 = new ArrayList<String>();

局限性1:

集合等号两边所传递的值必须相同否则会报错,原因就是Java中的泛型有一个擦除的机制,就是所编译器期间编译器认识泛型,但是在运行期间Java虚拟机就不认识泛型了,有兴趣的可以通过反编译来看一下,那么运行期间就会变成Listlist = new ArrayList ();如果最终变成这个样子了,那么传入泛型还有什么意思,所以在程序编译期间就报错,这样泛型就得以应用了(这个实际上是引用c++中的模板没有用好才导致的,Java中用泛型的场景就是写一个通用的方法)。

局限性2:

   现在要写一个比较通用的方法。

              publicvoid fun1(List<Object> list){

              System.out.println("泛型方法");

          }

       但是在调用的时候传入的String类型变量就会报错

       publicvoid test2(){

              List<String>list = new ArrayList<String>();

              //fun1(list);报错

       }

原因无他,就是泛型擦除,理由同局限性1,那么怎么办呢?

重载吧!类型变量为String的一个,Integer的一个。         

public void fun1(List<String> list){

           System.out.println("泛型方法1");

    }

    public void fun1(List<Integer>list){

           System.out.println("泛型方法2");

     }

这个时候编译器又报错了,为啥呢?还是泛型擦除,当运行的时候会导致,参数都变成List  list,那么这两个方法都变成一个了。,这,我晕,会不会觉得,泛型好垃圾。别急,接下来人家Java才不傻,给你提供了另一种方法,就是通配符。

泛型的通配符

泛型的通配符就是:?,

?表示的意思:

它表示一个不确定的类型,它的值会在调用时确定下来

那么这个时候通用的方法就能做了。

public void fun1(List<? extends Object>list){

              System.out.println("泛型方法");

          }

这个时候用

public void test2(){

              List<String>list = new ArrayList<String>();

              fun1(list);

       }

就不报错了。

也可以这样写:List<?> list = newArrayList<String>();

通配符注意事项

通配符不能使用在new的后面,否则会报错,为什么呢?因为如果写在new的后面不就相当于直接确定类型变量了吗,那么这就不是通配符了。

通配符的局限性

1.当只是用?的时候,参数类型为和返回值为通配符的方法是不能使用的。

public void print(List<?> list) {

//            list.add("hello");

//            list.get(0);

       }

 

原因是什么呢?因为list.add("hello");添加的时候,添加的类型可能是String的,也可能是Integer还可能是其他类型,这个时候还会出现泛型擦除的现象。list.get(0);的时候,可能返回String的,也可能是Integer还可能是其他类型,同样会出现泛型擦除。但是可以用Object接收,因为object是所有类的父类,比如:Object s = list.get(0);

2.通配符可以继承:

比如:? extends Number,这个时候通配符就被限制为了Number和” Number的子类型

但是这种情况也有一个特点,就是参数类型为通配符的泛型不能用,但是返回值类型为泛型的可以用。

public void print(List<? extends Number > list) {

//        list.add(1);

    Number num = list.get(0);

    }

因为参数类型为泛型的时候,参数可以为long的,也可能是Integer的还可能是其他类型,而返回值为泛型的,就可以用Number来接收了。

3.通配符可以有上界。

       比如:? superInteger,这个时候通配符就被限制为了Integer和” Integer的父类型

但是这种情况也有一个特点,就是返回值类型为泛型的不能用,但是参数类型为通配符的泛型的可以用。

public void print(List<? super Integer > list) {

list.add(5);

    //Number num = list.get(0);

    }

因为返回值为泛型的时候,返回值可以为Number的,也可能是Object的还可能是其他类型就是Integer的父类型,会发生泛型擦除。而参数为泛型的,就可以放入Integer或者Integer的父类就可以了。

通配符的应用场景

集合中的添加集合的方法是这样定义的。

boolean addAll(Collection<? extendsE> c)

 

List<Number> numList = new ArrayList<Number>();

List<Integer> intList = newArrayList<Integer>();

numList.addAll(intList);//addAll(Collection<?extends Number> c), 传递的是List<Integer>

 

当numList调用addAll方法的时候,Collection<? extends E> c 中的E变成了Number类型,然后就变成了Collection<? extends Number> c,然后就可以往这个方法中传递Integer类型

numList并不是<?extends Number>的引用,所以可以使用参数类型含有通配符的泛型。