泛型的深入理解

泛型使用之前

在面向对象的世界中,多态算是一种泛化机制,但是它是建立在继承之上的。

            List list = new ArrayList<>();
            list.add("laoqiang");
            list.add(1);
            String s = (String)list.get(0);
            System.out.println(s);
            int a = (int)list.get(1);
            System.out.println(a);
            String s1 = (String)list.get(1);
            System.out.println(s1);

这里写图片描述

存在的缺陷:

  1. 当我们获取一个值的时候,必须进行强制类型转换。
  2. 如果向集合中添加了非预期的类型(如Integer),编译时我们不会收到任何的错误提示。但当我们运行程序时却会报异常。

泛型参数

为了解决上面的问题,我们对集合采用了泛型参数,编译器会自动帮我们检查,避免向集合中插入错误类型的对象,从而使得程序具有更好的安全性。

泛型的类型擦除

            List<String> list = new ArrayList<>();
            List<Integer> list1  = new ArrayList<>();
            System.out.println(list.getClass()==list1.getClass());//true

这是为什么呢,明明我们定义了两种不同的类型?因为,在编译期间,所有的泛型信息都会被擦除,List和List类型,在编译后都会变成List类型(原始类型)。Java中的泛型基本上都是在编译器这个层次来实现的,这也是Java的泛型被称为“伪泛型”的原因。由上面的这段话,也是可以看出泛型也就是在编译的时候,出来吓吓人,运行期就怂了。

反射添加非预期的类型

List<String> list = new ArrayList<String>();
            try {
                list.getClass().getMethod("add", Object.class).invoke(list, 111);
            } catch (IllegalAccessException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IllegalArgumentException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (SecurityException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
             for (int i=0;i<list.size();i++) {    
                    System.out.println(list.get(i));    
                }    

上面的代码可以成功添加进去,这说明ArrayList泛型信息在编译之后被擦除了,只保留了原始类型,类型变量(T)被替换为Object,在运行时,我们可以行其中插入任意类型的对象。但是无法去遍历出来,会报异常。

泛型方法的类型推断

  • 在调用泛型方法的时候,可以指定泛型类型,也可以不指定。
  • 在不指定泛型类型的情况下,泛型类型为该方法中的几种参数类型的共同父类的最小级,直到Object。
  • 在指定泛型类型的时候,该方法中的所有参数类型必须是该泛型类型或者其子类。
        int c = Test241.add(1, 2);
        Number d = Test241.add(1, 1.2);//这两个参数一个是Integer,另一个是Float,所以取同一父类的最小级,为Number 
        Object c1  = Test241.add("1sdsa", 1);//这两个参数一个是Integer,另一个是String,所以取同一父类的最小级,为Object
        Integer a = Test241.<Integer>add(1, 2);//指定了Integer,所以只能为Integer类型或者其子类   
        String e = Test241.<String>add("sdas", "sds");
public static <T> T add (T a,T b) {//方法的泛型,先申明T为泛型,注意上面方法泛型的调用。
            return a;
        }

泛型引用传递

            List<String> list= new ArrayList<Object>();//编译错误
            //list都是String类型的对象,可是它里面实际上已经被我们存放了Object类型的对象,这样,就会有ClassCastException了。
            List<Object> list1 = new ArrayList<String>();//编译错误,这种就相当与你添加String类型,它把你转成Object,这不是和泛型初衷违背了。

泛型实例在运行时查询

            if(list instanceof ArrayList<String>) {//这种是不正确的

            }

java限定了这种类型查询的方式,?为通配符,也即非限定符。

            List<String> list= new ArrayList<String>();
            if(list instanceof ArrayList<?>) {

            }

泛型的静态使用注意点

public class Test241<T> {
    private static T one;//编译出错,你要知道static变量是不需要构造对象调用的,对象都没有创建,你怎么执行泛型类的类型。

}
public static <T> T show(T a) {//你可以编译通过一个static的泛型方法,因为这个泛型方法的泛型是在你在调用静态方法指定的。
        return a;
    }

    Test241.<String>show("aaa");

泛型中?与T区别

  1. T 声明一个泛型类或泛型方法,T是指定的一个死的类型。
  2. ?使用泛型类或泛型方法,而?是随便啦,可以变化的。

参考:这里写链接内容

public class Test241<T> {

    List<?> list = new ArrayList<T>();//T代表一种类型,你可以赋给多个类型的
    //List<T> list1 = new ArrayList<?>();//编译出错,你想想,?可以代表多种类型,而你的T只是一种类型,肯定不行
    public static void main(String[] args) {
        //List<String> list = new ArrayList<String>();
        Test241<?> l = new Test241<>();//在类名你已经指定是泛型,在这里,你要使用,但是你却不指定它具体的类型
    }
}

泛型中的非限定通配符 ?

一种是

使用泛型的占位符

编写泛型方法并不困难,你需要用泛型类型来替代原始类型,比如使用T, E or K,V等被广泛认可的类型占位符。

  public  <K,V> V  put( K key, V value) {  
        return null;  
    }  
    public <E> void  show(E e) {

    }

注意申明在方法中申明泛型占位符,一定要在返回值之前,先记住吧!!!

数组中是否可以使用泛型

答案是否定的,在书中建议我们去使用集合,取代数组,因为他可以在编译期提供安全。

List和原始类型List之间的区别

原始类型和带泛型参数类型之间的主要区别是,在编译时编译器不会对原始类型进行类型安全检查,还有就是带泛型参数的不可以将集合引用直接给一个集合,会出现编译错误。

        List list = new ArrayList();
        List<Object> list1 = new ArrayList<Object>();
        List<String> list2 = new ArrayList<String>();
        list1 = list2;//编译出错,这个问题可以看作泛型的传递的问题
        list = list2;
        list1.add(list2);//可以正常添加
        list.add(list2);//可以正常添加

Java中List

泛型的通配符上界与下界

  List<? extends T> list = new ArrayList();//这个泛型表示可以接受T的子类,作为泛型参数。
       list.add(1);//编译失败
       list.add("sdsd");//编译失败
       list.add(null);//编译成功

带有这种泛型通配符不可以添加任何类型,只可以为空。

参考学习:这里写链接内容

猜你喜欢

转载自blog.csdn.net/venus321/article/details/79831808
今日推荐