• 泛型


    一、什么是泛型

      泛型本质是为了参数化类型,就是将类型由原来的具体的类型参数化,通过泛型指定的不同类型来控制形参具体限制的类型,这个类型参数将在使用时(例如:继承或实现这个接口、用这个类型声明变量、创建对象)时确定.

     
    二、为什么需要使用泛型
      举个例子,集合容器类在设计阶段/声明阶段不能确定这个容器中到底存放什么类型的元素,所以在JDK1.5之前只能把元素类型设计为Object,虽然Object类型可以存储任意的类型,但是这样也会有一个缺陷,比如我们往一个集合中存储了一个String类型的数据,存储了一个Integer类型的数据,然后我们遍历这个集合的时候,由于取出来的数据都是Object类型的,没有安全检查,获取数据的时候我们必须要小心翼翼的进行数据的类型强制转换,一个不小心可能就会有类型转换异常等等.
    public class GenericDemo {
        public static void main(String[] args) {
            // 多态
            List list01 = new ArrayList();
            // 由于没有使用泛型,这里的add(Object obj)默认的参数类型是Object,也就是可以添加任何
            // 数据类型的值
            list01.add("abc");
            list01.add(123);
            // 遍历集合取出数据的时候可能会有类型转换异常
            for (int i = 0; i < list01.size(); i++) {
                String value = (String) list01.get(i);
                System.out.println(value);
            }
        }
    }
    

      运行结果: 

    Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
    	at GenericDemo.main(GenericDemo.java:14)
      基于这种现象,JDK1.5之后增加了泛型(Generic)来解决上述问题.因为我们设计集合的时候除了元素的类型不能确定之外,其它部分都是确定的,例如关于这个元素如何保存,如何管理等等都是确定的,因此我们只要把这个元素的类型设计成一个参数,这个参数类型就是泛型.
      可以对比下面两张图有一个更直观的了解.
     

    三、泛型的结构

      泛型作为一个类型参数,可以使用在接口上、类上、方法中,对应的我们将这些结构成为泛型接口、泛型类、和泛型方法.

      1、自定义泛型类(泛型类和泛型接口类似,可以看做是类和接口的区别)

    /**
     * 自定义泛型类
     *
     * @param <E> 类型参数
     * @param <T> 类型参数
     */
    // 泛型类可以有多个类型参数,这里就定义了E、T两个类型参数
    public class Person<E, T> {
        // 使用E类型定义属性id
        private E id;
        // 使用T类型定义属性name
        private T name;
    
        // 使用E T类型定义构造器
        public Person() {
        }
        /*
        使用如下方式定义泛型类的构造方式是错误的,编译就报错
        public Person<E,T>(){
        }
        */
    // 使用E类型定义普通方法(注意:这里不是泛型方法) public E getId() { return id; } public void setId(E id) { this.id = id; } // 使用T类型定义普通方法 public T getName() { return name; } public void setName(T name) { this.name = name; } }

      

      public static void main(String[] args) {
            // 构造器必须指明E和T的类型
            // JDK7开始,由于会推导类型,泛型的简化操作,下面也等价于 Person<Integer, String> person = new Person<>()
            Person<Integer, String> person01 = new Person<Integer, String>();
            // 由于E类型在构建对象的时候定义为Integer,所以ID的类型也被确定为了Integer
            // 所以实例化完成之后操作原来位置的泛型结构必须与指定的泛型类型一致,如果不一致会编译报错,这里被指定为了Integer
            person01.setId(9527);
            Integer id = person01.getId();
            // 由于T类型在构建对象的时候定义为String,所以name的类型也被确定为了String
            person01.setName("xiaomaomao");
            String name = person01.getName();
            System.out.println("id:" + id + "-----" + "name:" + name);
    
            Person<String, String> person02 = new Person<>();
            // 泛型不同的引用不能相互赋值,编译报错
            // person01 = person02;
    
            // .java文件编译之后会生成.class,在编译之后程序会采取去泛型化的措施,也就是说Java中的泛型只有在编译期有效
            // 在编译的过程中,正确检查泛型结果后,会将与泛型有关的信息擦除,并且在对象进入和离开方法的边界处添加类型检查
            // 和类型的强制转关,泛型相关的信息是不会进入到运行阶段的.
            // 这里虽然定义了两个泛型,但是实际上进入运行期的只有一个Person被加载进JVM
            Class person01Class = person01.getClass();
            Class person02Class = person02.getClass();
    
            boolean flag = person01Class.equals(person02Class);
            System.out.println(flag);
    
            // 泛型如果不指定,将会被擦除,所有的参数类型均按照Object类型进行处理,但是它又不等同于Object
            // 所以泛型如果定义了要么就全部不使用,要么就全部使用泛型
            Person person = new Person();
            // 这里设置的ID类型是int
            person.setId(10001);
            // 可以看出返回值类型是Object,泛型被擦除了
            Object id1 = person.getId();
            // 由于我们知道我们设置的是Integer类型的,手动强制转换
            Integer id2 = (Integer )id1;
            System.out.println(id2);
    
            // 泛型的类型只能是类类型,不能指定为基本数据类型,如果是基本数据类型,需要使用其对应的包装类类型代替
            // 下面这种写法是错误的
            // Person<int, float> person03 = new Person<int, float>();
            // 这种写法才是正确的,因为使用的是包装类类型
            Person<Integer, Float> person04 = new Person<Integer, Float>();
        }
    

      测试结果

    id:9527-----name:xiaomaomao
    true
    10001
    

      其它注意事项:

      1、在静态方法中不能使用泛型

    // 静态方法是随着类的加载而加载,其在对象的创建之前就已经存在了,而这个时候T的类型还没有明确,所以编译会报错
    public static T getFieldMessage(T t){
            return t;
    }
    

      2、异常类不能是泛型的

      3、父类中有泛型,子类中可以保留父类中的泛型,也可以指定其它的泛型,下面就演示可能出现的情况
        定义一个父类(泛型为T1、T2)
    // 父类定义了泛型 T1、T2
    class Father<T1,T2>{ private T1 lastName; private T2 intelligence; public void setLastName(T1 lastName){ this.lastName = lastName; } public T1 getLastName(){ return lastName; } public void setintelligence(T2 intelligence){ this.intelligence = intelligence; } public T2 getIntelligence(){ return intelligence; } }

      

      (1)、son<A,B> extends Father{}:子类自己定义泛型A、B,并且擦除父类的泛型T1、T2 

    // 子类定义自己的泛型A、B,并且擦除父类的泛型T1、T2
    class Son01<A,B> extends Father {
        private A id;
        private B firstName;
    
        public A getId() {
            return id;
        }
    
        public void setId(A id) {
            this.id = id;
        }
    
        public B getFirstName() {
            return firstName;
        }
    
        public void setFirstName(B firstName) {
            this.firstName = firstName;
        }
    }
    
    public class GenericDemo {
        public static void main(String[] args) {
            Son01<Integer, String> son01 = new Son01<>();
            son01.setLastName("zhang");
            son01.setintelligence(99.34);
            son01.setFirstName("maomao");
            son01.setId(10001);
            // 获取父类的属性时,由于擦除了父类的泛型,默认获取到的类型是Object,
            // 如果要获取具体的类型,需要强制转换
            String lastName = (String)son01.getLastName();
            Double intelligence  = (Double)son01.getIntelligence();
            // 子类中创建对象的时候指定了泛型为Integer和String,所以这里不需要强制转换
            Integer id = son01.getId();
            String firstName = son01.getFirstName();
    
            System.out.println("son01的id是:"+id+"
    "+"他的名字是:"+lastName+firstName+"
    "+"他的智商是:"+intelligence);
    
        }
    }        

      测试结果

    son01的id是:10001
    他的名字是:zhangmaomao
    他的智商是:99.34

      

      (2)、class Son02<A,B> extends Father<String,Double>:子类自己定义泛型<A,B>,并且会继承父类中确定的泛型<String,Double>

    // 父类中使用了具体的泛型<String,Double>,子类会继承这些泛型,并且子类中
    // 也定义了泛型<A,B>,子类在实例化对象的时候也会确定<A,B>
    class Son02<A,B> extends Father<String,Double> {
        private A id;
        private B firstName;
    
        public A getId() {
            return id;
        }
    
        public void setId(A id) {
            this.id = id;
        }
    
        public B getFirstName() {
            return firstName;
        }
    
        public void setFirstName(B firstName) {
            this.firstName = firstName;
        }
    }
    
    public class GenericDemo {
        public static void main(String[] args) {
            Son02<Integer, String> son02 = new Son02<>();
            son02.setLastName("zhang");
            son02.setintelligence(101.32);
            son02.setFirstName("tutu");
            son02.setId(10005);
          // 由于父类中指定了泛型<String,Double>,所以子类能够继承父类的泛型
            // 所以子类对象获取父类中的属性时,类型就直接确定了,不需要再强制转换
            String lastName = son02.getLastName();
            Double intelligence = son02.getIntelligence();
            // 由于子类自己实例化的时候<A,B>确定诶<Integer,String>,所以子类
            // 获取自己特有的属性时类型也确定了,也不需要进行强制转换
            Integer id = son02.getId();
            String firstName = son02.getFirstName();
    
            System.out.println("son02的id是:"+id+"
    "+"他的名字是:"+lastName+firstName+"
    "+"他的智商是:"+intelligence);
        }
    }
    

      测试结果

    son01的id是:10005
    他的名字是:zhangtutu
    他的智商是:101.32
    

      (3)、class Son03<A,B,T1,T2> extends Father<T1,T2>  子类继承父类的泛型<T1,T2> 并且子类额外定义泛型<A,B>

    class Son03<A,B,T1,T2> extends Father<T1,T2> {
        private A id;
        private B firstName;
    
        public A getId() {
            return id;
        }
    
        public void setId(A id) {
            this.id = id;
        }
    
        public B getFirstName() {
            return firstName;
        }
    
        public void setFirstName(B firstName) {
            this.firstName = firstName;
        }
    }
    
    public class GenericDemo {
        public static void main(String[] args) {
            Son03<Integer, String, String, Double> son03 = new Son03<>();
            son03.setLastName("zhang");
            son03.setintelligence(110.22);
            son03.setFirstName("wawa");
            son03.setId(10008);
            // 子类在实例化的时候确定了从父类中继承的泛型,也确定了自己特有的泛型
            // 所以获取对象时不再需要进行强制转换
            String lastName = son03.getLastName();
            Double intelligence = son03.getIntelligence();
            String firstName = son03.getFirstName();
            Integer id = son03.getId();
    
            System.out.println("son03的id是:"+id+"
    "+"他的名字是:"+lastName+firstName+"
    "+"他的智商是:"+intelligence);
        }
    }
    

      测试结果

    son03的id是:10008
    他的名字是:zhangwawa
    他的智商是:110.22
    

      

      (4)、class Son04<A,B,T1,T2> extends Father<T1,Double> 子类部分继承父类泛型<T1>

    class Son04<A,B,T1,T2> extends Father<T1,Double> {
        private A id;
        private B firstName;
    
        public A getId() {
            return id;
        }
    
        public void setId(A id) {
            this.id = id;
        }
    
        public B getFirstName() {
            return firstName;
        }
    
        public void setFirstName(B firstName) {
            this.firstName = firstName;
        }
    }
    
    public class GenericDemo {
        public static void main(String[] args) {
            Son04<Integer, String, String, Double> son04 = new Son04<>();
            son04.setLastName("zhang");
            son04.setintelligence(121.21);
            son04.setFirstName("xixi");
            son04.setId(10010);
            // 子类在实例化的时候确定了从父类中继承的泛型T1,也确定了自己特有的泛型<A,B>
            // 所以获取对象时不再需要进行强制转换
            String lastName = son04.getLastName();
            Double intelligence = son04.getIntelligence();
            String firstName = son04.getFirstName();
            Integer id = son04.getId();
    
            System.out.println("son04的id是:"+id+"
    "+"他的名字是:"+lastName+firstName+"
    "+"他的智商是:"+intelligence);
        }
    }
    

      测试结果

    son04的id是:10010
    他的名字是:zhangxixi
    他的智商是:121.21
    

      

      2、自定义泛型方法  

        格式: [权限修饰符]  <泛型>  返回值类型 方法名([泛型标识 参数名称])
    public class GenericDemo<T1,T2> {
        private T1 id;
        private T2 name;
        // 泛型方法和泛型类没有必然联系
        public static <E> List fromArrayToList(E[] arr,List<E> list){
            for (E e : arr) {
                list.add(e);
            }
            return list;
        }
    
        public static void main(String[] args) {
            String[] strArr = {"a","b","c","d","e"};
            Integer[] intArr = {1,3,5,7,9};
            
            List<String> strList = new ArrayList<>();
            List<Integer> intList = new ArrayList<>();
            // 方法的泛型为E,当传入参数时,一定要确保E[]和List<E>的参数类型一致
            GenericDemo.fromArrayToList(strArr,strList);
            GenericDemo.fromArrayToList(intArr,intList);
            // 编译报错
            GenericDemo.fromArrayToList(StrArr,intList);
            // 编译报错
            GenericDemo.fromArrayToList(intArr,StrList);
        }
    }
    

      

    四、泛型通配符

       如果B是A的一个子类型(子类或者是子接口),而G是具有泛型声明的类或者接口,G<B>并不是G<A>的一个子类型,例如String类是Object类的一个子类,但是List<String>并不是List<Object>的一个子类型,他们之间没有任何关联关系.

        

     

       1、<?>

      public static void main(String[] args) {
            // List<?>是List<Animal>、List<TomCat>、List<Dog>的父类
            List<?> list01 = new ArrayList<>(); // 所有带有泛型的List集合的父类
            // Object的子类
            List<Animal> list02 = new ArrayList<>();
            // Animal的子类
            List<Cat> list03 = new ArrayList<>();
            // Animal的子类
            List<Dog> list04 = new ArrayList<>();
    
            // List<?>是各种泛型List的父类,根据多态,子类是可以赋值给父类的
            list01 = list02;
            list01 = list03;
            list01 = list04;
    
            // 下面我们试图往list01集合中添加各种元素,结果编译期间直接报错
            // 原因:因为list01它作为所有带有泛型的List的父类,它可以是各种各样的类型,在没有确定类型之前它可以是
            // List<Object>、List<Animal>、List<Cat>、List<Dog>............等等各种各样的类型.我们不能确定
            // list01的类型,所以就不能确定应该往里面添加什么类型的元素
    
            // 如果list01中现在存储的是Cat,你能往一个存放Cat的集合中添加Cat的父类Animal、Object吗?
            // 答案是显然不可以的,因为动物不都是猫,可能还有狗、马、蛇等等,你不能把其它的动物都丢进猫的笼子里.
            list01.add(new Object());
            list01.add(new Animal());
            // 如果list01中现在存储的是Dog,你们往Dog的笼子里面丢Cat进去吗?
            list01.add(new Cat());
            // 如果list01中现在存储的是Cat,你们往Cat的笼子里面丢Dog进去吗?
            list01.add(new Dog());
    
            // 唯一的例外就是null,因为所有对象都有null值
            list01.add(null);
    
            // 可以从list01中获取值,为什么呢?
            // 假设list01中有值,那么我不管它是什么类型的数据,我总可以用Object类型来接收,因为Object是所有类的基类
            Object o = list01.get(0);
            Object o1 = list01.get(1);
        }
    

      

      2、<? extends A>

      public static void main(String[] args) {
    	// List<? extends Animal>是List<Animal>、List<Animal的子类>的父类
    	List<? extends Animal> list01 = new ArrayList<>();
    	// Object类,所有类的父类
    	List<Object> list02 = new ArrayList<>();
    	// Object的子类
    	List<Animal> list03 = new ArrayList<>();
    	// Animal的子类
    	List<Cat> list04 = new ArrayList<>();
    	// Animal的子类
    	List<Dog> list05 = new ArrayList<>();
    	// 编译报错,因为List<? extends Animal>是List<Animal>、List<Animal的子类>的父类,它并不是List<Object>类的父类,他们没有任何关系
    	list01 = list02;
    	// 编译通过,多态
    	list01 = list03;
    	list01 = list04;
    	list01 = list05;
    
    	// 编译报错,List<? extends Animal>里面存储的是Animal或Animal的子类
    	// 假设此时list01存储的正是List<Animal>,你能向一个装动物的笼子里面丢任意东西进去吗?
    	list01.add(new Object());
    	// 假设此时list01存储的正是List<Cat>,你能向一个装猫的笼子里面丢任何动物进去吗?
    	list01.add(new Animal());
    	// 假设此时list01存储的正是List<Dog>,你能向一个装狗的笼子里面丢猫进去吗?
    	list01.add(new Cat());
    	// 假设此时list01存储的正是List<Cat>,你能向一个装猫的笼子里面丢狗进去吗?
    	list01.add(new Dog());
    
    	// List<? extends Animal>存储的最大的元素为Animal,所以里面只要有值,就一定是Animal或者是Animal的子类,根据多态用Animal是可以接收的
    	Animal animal = list01.get(0);
      }
    

      

       3、<? super A>

      public static void main(String[] args) {
            // List<? super Animal>是List<Animal>、List<Animal的父类>的父类
            List<? super Animal> list01 = new ArrayList<>();
            // Object类,所有类的父类
            List<Object> list02 = new ArrayList<>();
            // Object的子类
            List<Animal> list03 = new ArrayList<>();
            // Animal的子类
            List<Cat> list04 = new ArrayList<>();
            // Animal的子类
            List<Dog> list05 = new ArrayList<>();
    
            // 编译通过,多态
            list01 = list02;
            list01 = list03;
            // 编译报错,因为List<? super Animal>是List<Animal>、List<Animal的父类>的父类,它并不是List<子>类的父类,他们之间没有任何关系
            list01 = list04;
            list01 = list05;
    
            // 编译报错,List<? super Animal>里面存储的Animal或Animal的父类
            // 假设存储的就是List<Animal>,你能往一个存储Animal的集合中丢任意东西进去吗?
            list01.add(new Object());
            // list01里面存储的是Animal或Animal的父类,假设存储的就是List<Animal>,我们是可以往一个装动物的笼子里面丢动物、丢猫或者丢狗进去的.
            list01.add(new Animal());
            list01.add(new Cat());
            list01.add(new Dog());
    
            // List<? super Animal>存储的是Animal或Animal的父类,所以里面只要有值,就一定是Animal或者是Animal的父类,根据多态用Object是可以接收的
            Object o = list01.get(0);
        }
    

      4、在使用泛型通配符的时候,应注意以下几点

    // 不能在泛型类的声明上使用泛型通配符(编译报错)
    public class GenericDemo<? extends Animal> {     
    }
    // 不能在泛型方法的声明上使用泛型通配符(返回值前面),编译报错
    public <? super Animal> String testGeneric(E> list){
            return "hell0";
    }
    // 但是在参数列表上是可以使用泛型通配符的
    public <E> String testGeneric(List<? extends Animal> list){
            return "hell0";
    }
    public static void main(String[] args) {
      // 泛型通配符不能用在创建对象上,右边的泛型通配符就是在创建对象
      List<? extends Animal> list01 = new ArrayList<? super Animal>();
    }

      

  • 相关阅读:
    LightGBM原理与实践简记
    关于博客迁移
    关于ModuleNotFoundError: No module named 'conda._vendor.tqdm'的解决方法(macos)
    [科普视频] 植被覆盖度(fCover)科普视频制作
    [基金评审] 2022年青年基金与地区基金评审感悟
    如何快速解决集群异常和机器性能波动
    中科大脑知识图谱平台建设及业务实践
    GraphX 图计算实践之模式匹配抽取特定子图
    隐藏在 Nebula Graph 背后的星辰大海
    leetcode297
  • 原文地址:https://www.cnblogs.com/xiaomaomao/p/13518932.html
Copyright © 2020-2023  润新知