Java泛型从基础到高级

1,305 阅读5分钟
  • 什么是泛型?

  1. 泛型是JDK1.5中引入的一种"参数化类型"特性.
  2. 泛型可以限制参数类型,不需要强制类型转换.
  3. 泛型会在编译器检测类型是否匹配,避免了运行时类型不一致引起的"ClassCastException".
  • 泛型的简单使用

  1. 不使用泛型可以添加任何类型
    ArrayList list = new ArrayList();
    list.add("hello");
    list.add(18);
    list.add('u');
  1. 不使用泛型获取数据会转为Object,使用的时候需要强转才可以
    for (Object o : list) {
        if (o instanceof String) {
            String item = (String) o;
        }else if(o instanceof Integer){
            Integer item = (Integer) o;
        }else if ...
        ...
    }
  1. 使用泛型添加数据必须与定义的泛型统一
    //定义泛型为String类型
    ArrayList<String> list = new ArrayList<>();
    list.add("hello");
    list.add(18); //报错 类型不匹配
    list.add('u'); //报错 类型不匹配
  1. 使用泛型获取数据会自动转为泛型类型,无需强转
    for (String s : list) {
        System.out.println(s);
    }
  • 泛型的定义

  • 学习泛型定义之前,先学习一下泛型的格式
   泛型由"<泛型类型>"组成
   泛型类型可以是任意引用类型或自定义标识
   ********************************
   引用类型: <String> 或 <Object>
   ********************************
   自定义标识符是可以随便写的
   比如: <T> <SB> <TMD> <吃了吗>
   ********************************
   但是通常我们不建议这样写,推荐常用的几个
   E: 元素 (Element)
   K: 关键字 (Key)
   N: 数字 (Number)
   T: 类型 (Type)
   V: 值 (Value)
  • 泛型类
    public class Generic<String>{
        private String item;
    }
    
    或者
    
    public class Generic<T>{
        private T item;
    }
  • 泛型接口
    public interface Generic<String>{
        String getItem();
    }
    
    或者
    
    public interface Generic<T>{
        T getItem();
    }
  • 泛型方法
    //情况1 类没有定义泛型,方法必须定义泛型才可以使用
    public class GenericTest {
        public <T> T getItem(T item) {
            return item;
        }
    }
    
    注意看上下的区别 <T>
    
    //情况2 类已经定义泛型,方法无需定义泛型就可以使用
    public class GenericTest<T> {
        public T getItem(T item) {
            return item;
        }
    }
  • 泛型实战

  • 定义一个简单的类,一个成员变量和get set方法
    public class TestData<T> {
        private T item;

        private void setItem(T item) {
            this.item = item;
        }

        private T getItem() {
            return item;
        }
    }
  • 使用
    //泛型传入String
    public static void main(String[] args) {
        TestData<String> stringData = new TestData<>();
        stringData.setItem("我是String类型");
        String item = stringData.getItem();
    }
    
    或者
    
    //泛型传入Integer
    public static void main(String[] args) {
        TestData<Integer> integerData = new TestData<>();
        integerData.setItem(18);
        Integer item = integerData.getItem();
    }
  • 实战总结:
  1. 泛型限定自定义标识符为T,可以传入任意类型
  2. 类的内部会自动将T转为传入的类型(这里不考虑泛型擦除的情况)
  3. 假入传入的是<String>
    //正常代码
    public class TestData<T> {
        private T item;

        public void setItem(T item) {
            this.item = item;
        }

        public T getItem() {
            return item;
        }
    }

    //转换后的代码
    public class TestData<String> {
        private String item;

        public void setItem(String item) {
            this.item = item;
        }

        public String getItem() {
            return item;
        }
    }
  • 无界通配符

无界通配符 <?> 通常在泛型中使用一个问号标识,可以代表任意类型,例如:

    public class GenericClass {
        //无界通配符泛型参数
        public void setList(List<?> list) {
            System.out.println("List的长度是: "+list.size());
        }
    }
    
    //调用时可以传入任意类型泛型参数
    ArrayList<Integer> integersList = new ArrayList<>();
    ArrayList<Number> numbersList = new ArrayList<>();

    GenericClass genericClass = new GenericClass();
    genericClass.setList(numbersList); //Integer类型
    genericClass.setList(integersList); //Number类型
  • <?> 和 <T> 有什么区别?

本身就是占位符可以代表任意类型,<?>也可以是任意类型,那有什么区别呢? 下面看一段代码:

    代码片段1:
    
    // <?>定义到类上会报错
    public class GenericClass<?> {
        //<?>定义到方法上也会报错
        public <?> void setList(List<?> list){

        }
    }
    
    // <T>定义到类和方法都不会报错
    public class GenericClass<T> {
        public <T> void setList(List<T> list) {
            System.out.println("List的长度是: "+list.size());
        }
    }
    
    
    代码片段2:
    
    //<T>不能当做泛型参数
    ArrayList<T> list = new ArrayList<>();
    
    //<?>可以当做不确定的泛型参数
    ArrayList<?> list = new ArrayList<>();

总结:

  1. 和< T> 都是泛型通配符,可以当做通用占位符.
  2. 是一个不确定类型,不能定义到类和方法上,通常用于当做形参传入.
  3. < T>是一个确定的类型,可以定义到类和方法上,可以用来做一些操作,例如: T t = getNum();
  • 有界通配符

先来一段代码,研究一下为什么会报错

    //先定义三个类 儿子 爸爸 爷爷,继承关系如下:
    public class Son extends Father{}
    public class Father extends Grandpa{}
    public class Grandpa {}
  
    
    //定义儿子和爸爸的集合
    ArrayList<Son> sons = new ArrayList<>();
    ArrayList<Father> fathers = new ArrayList<>();

    setList(sons); //报错 纳尼??? Son不是Father的子类吗? 为什么会报错?
    setList(fathers); //正常
    
    //接收一个泛型是Father的集合
    private static void setList(List<Father> list){

    }

这里就涉及到了泛型中的extends了,泛型里面是不认类中的继承关系的,所以List只能接收泛型为Father的集合,就算是Father的子类也不行,如果想要泛型也实现继承关系应该怎么办呢? 这里就要用到下面的有界通配符了.

  • 上界通配符 <? extends T>

现在我们把List改成List<? extends Father> 试试

    
    ArrayList<Son> sons = new ArrayList<>();
    ArrayList<Father> fathers = new ArrayList<>();
    ArrayList<Grandpa> grandpas = new ArrayList<>();
    
    setList(sons); //正常
    setList(fathers); //正常
    setList(grandpas); //报错, 因为只能传入Father或Father的[子类]
    
    private static void setList(List<? extends Father> list){

    }
  • 下界通配符 <? super T>

现在我们把List改成List<? super Father> 试试

    
    ArrayList<Son> sons = new ArrayList<>();
    ArrayList<Father> fathers = new ArrayList<>();
    ArrayList<Grandpa> grandpas = new ArrayList<>();
    
    setList(sons); //报错, 因为只能传入Father或Father的[父类]
    setList(fathers); //正常
    setList(grandpas); //正常
    
    private static void setList(List<? super Father> list){

    }

总结:

  1. 上界通配符<? extends T>会限制只能传入T的子类或者T本身
  2. 下界通配符<? super T> 会限制只能传入T的父类或T本身
  • 有界通配符的副作用

    //上界通配符<? extends Father>
    private static void setList(List<? extends Father> list){
        list.add(new Son()) //报错
        list.add(new Father()) //报错
        
        //获取可以用Father
        for (Father father : list) {
            System.out.println(father);
        }
    }
    
    //下界通配符<? super Father>
    private static void setList(List<? super Father> list){
        list.add(new Son()) //正常
        list.add(new Father()) //正常
        list.add(new Grandpa()) //报错, 只能添加Father或Father的子类
        
        //获取只能用Object
        for (Object o : list) {
            System.out.println(o);
        }
    }

总结:

  1. 上界通配符不能添加,只能获取 因为无法确定传来的泛型是T本身还是T的子类,如果是T的子类,那添加T本身一定会报错,所以直接上界通配符直接禁止添加,但是可以正常获取,获取的时候用T来接收.
  2. 下界通配符可以添加,但是只能添加T和T的子类,获取的时候只能用Object接收,因为无法确定传来的泛型是T的哪一个父类,所以只能用最大的父类Object来接收.
  • 泛型擦除

由于泛型是JDK1.5才引入的,为了向下兼容,所以泛型只存在于代码编译期,进入JVM前泛型相关的代码会被擦除掉.

  • 怎么证明泛型被擦除了?

可以看到下面创建了两个泛型不同的集合,但是运行后发现Class都是java.util.ArrayList,并不存在泛型,所以结果为ture

    public static void main(String[] args) {
        ArrayList<Integer> list1 = new ArrayList<>();
        ArrayList<String> list2 = new ArrayList<>();

        System.out.println(list1.getClass() == list2.getClass()); //true
    }
  • 无限制类型擦除,直接变成Object

在这里插入图片描述

  • 有限制类型擦除,变成上限

在这里插入图片描述

  • 维持多态性,系统自动生成桥接方法

在这里插入图片描述