泛型解决什么问题?

集合容器类,在定义阶段不确定实际存储什么类型的对象,在jdk1.5之前只能将类型设计为Object类型,但是这意味着“误操作”、以及取出时要强制转型等问题。
image.png
泛型解决方案:为了更好解决诸如容器类,“虽然管理元素的流程是确定的,但是类型不确定”的问题。定义接口、类时,将类型设计为一个参数(或者说标识),比如等就是类型参数,即泛型;当实例化该类时,才指明类型参数,这叫泛型擦除。
image.png

简单说泛型的概念: 定义泛型、泛型擦除。

  1. 1. 在定义类、接口时: 将具体的类型替换为TVKE等标识,就叫泛型。
  2. public class ArrayList<E> extends AbstractList<E>
  3. implements List<E>, RandomAccess, Cloneable, java.io.Serializable
  4. {
  5. ...
  6. }
  7. 2. 使用类、接口时: 使用具体的类型,比如String ,来指明先前的标识,这叫泛型擦除。
  8. List<String> list = new ArrayList<String>(); // 注:由于类型推断的存在,所以右边可以写new ArrayList<>();
  9. 3. 另外,也有泛型方法
  10. public V add(K key,V value){
  11. return this.dic.put(key,value);
  12. }

泛型概念:所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返
回值及参数类型。这个类型参数将在使用时(例如,继承或实现这个接口,用这个类型声明变量、
创建对象时确定(即传入实际的类型参数,也称为类型实参)。

泛型的类型擦除:

首先,java的泛型仅编译器存在,可作为类型检测等机制。而运行期并不存在泛型,比如编译器有ArrayList和ArrayList两种类型,但在运行期的类型统一为ArrayList,这叫类型擦除, 当然也有限制的类型擦除。
在运行期内:

  • 无限制通配符,诸如List, 在类型擦除后,会被直接替换为List类型;
  • 上边界通配符,诸如List会被替换为List类型;
    1. public static <E> void test(E e){
    2. // 无限制的类型擦除,运行期的e类型为Object,所以即使编译期允许,运行期e也不一定是Person类或其子类,即e不可以添加到List<Person>中。
    3. List<Person> personList = new ArrayList<>();
    4. // personList.add(e); // 不被允许
    5. }
    6. public static <E extends Person> void test(E e){
    7. // 带上界的类型擦除, 运行期的e类型为Person,所以只要编译期允许,那么运行期e一定是Person类或其子类,即e可以添加到List<Person>中。
    8. // <E super Person> 不存在这种写法,因为带下界的类型擦除,
    9. List<Person> personList = new ArrayList<>();
    10. personList.add(e);
    11. }
    12. public static <E extends Life> void test2(E e){
    13. // 带上界的类型擦除, 运行期的e类型为Life,所以即使编译期允许,运行期e也不一定是Person类或其子类,即e不可以添加到List<Person>中。
    14. List<Person> personList = new ArrayList<>();
    15. // personList.add(e); // 不被允许
    16. }
    17. // public static <E super Person> void test3(E e){
    18. // // 不存在 <E super Person> 语法
    19. // }

    为什么要用通配符呢?(要区别于泛型)

    java里面类和类之间是有继承关系的,但是集合是没有继承这个概念的,通配符就是来解决这个问题的,让集合也有类似子父类关系,这样便可以限制add()和get()等操作。

    注意:下面对象为List list的三种情况: List<? > list、List<? extends T> list、 List<? super T> list ,核心在于理解 ?所允许的类型下界和类型上界,以及add和get涉及的强转问题。

    1. 无边界的通配符:?被称作无界通配符,并不是真的无界,它的默认实现是? extends Object。 结论见 ? extends T
    2. 上边界通配符号:? extends T对应协变关系,表示?必须是T或者T的子类。
    3. 下边界通配符号: ? super T对应逆变关系,使用了下界通配符? super T
    1. public static void test_super(){
    2. // 此时List<?>中的 ? 的类型下界为Person,类型上界为Object。
    3. List<? super Person> list = new ArrayList<>();
    4. // list.add(new Object()); // 不允许,因为 ?可以是Object的子类,但是new Object()不可强转为Object的子类。
    5. list.add(new Person()); // 允许,因为 ? 一定是Person类及其父类,所以new Person()可以被强转。
    6. list.add(new Student()); // 允许,同上。
    7. Object e = list.get(0); // 允许,因为 ? 都是Object的子类,所以被强转为Object类型。
    8. // Person e1 = list.get(0); // 不允许,因为 ? 不一定是Person类及其子类
    9. // Student e2 = list.get(0); // 不允许,同上
    10. }
    1. public static void test_extends(){
    2. // 此时List<?>中的 ? 的类型上界为person, 但不存在类型下界。
    3. List<? extends Person> list = new ArrayList<>();
    4. // list.add(new Object()); // 不允许,因为 ?可以是Person类及其子类,它们都是Object的子类,可是new Object()不可强制转为其子类。
    5. // list.add(new Person()); // 不允许,因为 ? 一定是Person类或其子类,而new Person()不可强制转为Person的子类。
    6. // list.add(new Student()); // 不允许,因为 ? 一定是Person类或其子类,有可能 ?是同时也是Student的子类,此时无法强转。
    7. Object e = list.get(0); // 允许,因为 ? 都是Object的子类,所以被强转为Object类型。
    8. Person e1 = list.get(0); // 允许,因为 ? 都是是Person类及其子类,所以可以强转为Person类型。
    9. // Student e2 = list.get(0); // 不允许,因为 ? 可能是Student的父类,比如?就是Person类型,此时不可被强转为Student类型。
    10. }

    代码案例

    • List<? extends Person> 可作为List们的父类
    • List<? super Person> 可作为List们的父类
    • List<? extends Person> 中的 ?,表示?任意Person及其全部子类,所以? 不一定能充当Person及其子类的当前类或父类,且一定不是Life和Object的父类。
    • List<? super Person> 中的 ?,表示?任意Person及其全部父类,所以 ?一定是Person及其子类的当前类或父类,而一定不是Object类的父类 ```java package xj.java.exer2;

    import java.util.ArrayList; import java.util.List;

    /**

    • @author jia
    • @create 2021-11-29 10:54 上午 */

    class Life extends Object {} class Student extends Person{} class ManStudent extends Student{} class Person extends Life{}

    public class Test{ public static void main(String[] args) {

    1. List<? extends Person> extensPersons = new ArrayList<>();
    2. List<? super Person> superPersons = new ArrayList<>();
    3. List<Object> objects = new ArrayList<>();
    4. List<Life> life = new ArrayList<>();
    5. List<Person> persons = new ArrayList<>();
    6. List<Student> students = new ArrayList<>();
    7. List<ManStudent> manStudents = new ArrayList<>();
    8. // List<? extends Person> 可作为List<Person及其子类>们的父类
    9. extensPersons = persons;
    10. extensPersons = students;
    11. extensPersons = manStudents;
    12. // List<? super Person> 可作为List<Person及其父类>们的父类
    13. superPersons = persons;
    14. superPersons = objects;

    // superPersons = students; // 不被允许

    1. // 注意:List<? extends Person> 中的 ?,表示?任意Person及其全部子类,所以? 不一定能充当Person及其子类的的当前类或父类,且一定不是Life和Object的父类。

    // extensPersons.add(new Student()); // 不被允许 // extensPersons.add(new Person()); // 不被允许 // extensPersons.add(new Life()); // 不被允许 // extensPersons.add(new Object()); // 不被允许

    1. // 注意:同上,List<? super Person> 中的 ?,表示?任意Person及其全部父类,所以 ?一定是Person及其子类的当前类或父类,而一定不是Object类的父类
    2. superPersons.add(new Person()); //
    3. superPersons.add(new Student()); //

    // superPersons.add(new Life()); // 不被允许 // superPersons.add(new Object()); // 不被允许 } }

    ```

    一些注意点:

    泛型的构造器: 构造器被调用时,即是创建对象时,该使用场景本身不需要范型。

    image.png

    不同泛型的引用: 泛型存在于编译期,而在运行期已被擦除,所以如果不同泛型的引用可以相互赋值,那么运行期可能会出问题,比如将String对象赋给Integer对象。

    image.png

    调用时范型不被指定,也会被擦除。

    image.png

    静态方法不可使用类的泛型 ?? ? 为什么我测试可以使用,jdk15

    异常类不可以泛型的

    image.png

    泛型数组的实例化

    image.png