Java泛型和Kotlin泛型

1,999 阅读7分钟

Java泛型

什么是泛型

  • 泛型本质是指类型参数化。意思是允许在定义类、接口、方法时使用类型形参,当使用时指定具体类型,所有使用该泛型参数的地方都被统一化,保证类型一致。如果未指定具体类型,默认是Object类型。集合体系中的所有类都增加了泛型,泛型也主要用在集合。
  • 泛型一般在集合中使用居多

如果定义泛型

  • 泛型类 在这里T表示未知类型,泛型类型可以是多个,一般用K V E M表示
  • 泛型类 是在实例化的时候声明泛型的类型
    // 泛型类示例
    public class People<T>{

    }
  • 泛型接口 在这里T表示未知类型 和泛型类的定义一样
    // 泛型接口示例
    public interface PeopleImp<T>{

    }
  • 泛型方法 在这里T表示未知类型
  • 泛型方法 是在使用时声明使用的类型,注意这里和泛型类定义的泛型可能不是一致的
  • 泛型方法 声明<T>才是泛型方法
    // 泛型方法示例
    public <T> void isPeople(T people){

    }

泛型的好处

  • 在编译时确定类型,可以避免ClassCastException
  • 保证类型的安全,个人理解为一种强制的约束

泛型的限制和规则

如果我们定义了泛型,而不去指定它的具体类型,那么默认是Object

泛型的类型参数只能是引用类型,不能使用值类型。

    // 泛型类
    public class People<T>{

    }
    public static void main(String[] args) {
        // 错误代码 :在这里IDE会有错误提示,可以使用Integer代替
       People<int> people = new People<int>();
       // 正确代码
       People<Integer> people = new People<Integer>();
    }

泛型的类型参数可以有多个。

    // 在这里指定2个泛型类型参数
    public static class People<T,E>{

    }

泛型类不是真正存在的类,不能使用instanceof运算符。

    public static void main(String[] args) {
        People<Integer> people = new People<>();
        // 错误代码师范
        if (people instanceof People<Integer> )
    }

泛型类的类型参数不能用在静态申明。

// 错误代码
    public static T  name;
    // 错误代码
    static{
            T name;
    }
    //错误代码
    public static T getT(T t) {
            return t;
    }
  • 以上代码报出异常:不能使一个静态引用指向一个非静态的类型T。 静态和非静态之分就在于静态是编译时类型,动态是运行时类型。 T代表未知类型,如果可以用于静态申明,因为是未知类型, 系统没法指定初始值,手动赋值也不行,因为不知道啥类型, 只有运行时才可以指定。而泛型存在的意义就是为了动态指定具体类型, 增强灵活性和通用性,所以用于静态声明违背了使用原则。为什么实例变量和实例方法可以使用呢? 因为当你使用实例变量或者方法时,就说明对象存在了,即代表泛型参数也指定了。未指定具体类型默认是Object类型。
    public class Generic<T> {
        private T key;
        // 这并不是一个泛型方法
        public T getT() {
            return key;
        }
        // 错误代码,而且不是泛型方法
        public static T getT(T t) {
            return t;
        }
        // 真正的泛型方法
        public static <T> T getT(T t) {
            return t;
        }
    }
  • 首先,要明确一点,泛型作用是确定具体类型。先看一个泛型方法,使用了泛型参数T作为返回值,当使用对象时来调用该方法时,T类型会变成具体类型。
  • 第二个泛型方法是静态的,使用了T作为返回值和方法参数类型,但是静态方法是属于类的,类直接调用的话,T类型无法指定具体类型,那么该方法就没有意义。所以直接报错。(只是把泛型当做参数,并非真正的泛型方法)
  • 第三个也是静态方法,但是该静态方法是自定义一个泛型参数,并非使用类型参数。所以当传入一个具体类型时,该静态方法的就是具体类型了。
  • 两者静态方法的区别就是一个是使用泛型参数,一个是定义泛型方法。

如果定义了泛型,不指定具体类型,泛型默认指定为Ojbect类型。

泛型使用?作为类型通配符,表示未知类型,可以匹配任何类型。因为是未知,所以无法添加元素。

    public static void main(String[] args) {
        List<?> list = new ArrayList<>();
        list.add(new People<String>());
    }

类型通配符上限:<? extends T> 子类的泛型类型也属于泛型类型的子类,Java不允许将一个子类的泛型类型对象赋值给父类的对象声明,使用协变可以这么做,但是不推荐

  • ?代表是T类型本身或者是T的子类型,也表示implements。常用于泛型方法,避免类型转换。也叫做协变,在这里不能直接添加list如果是父类的list,不能直接添加子类的对象
    public void addPeople(List<? extends T> people){
        
    }
    
    public interface Animals {
        void look();
    }
    
    public class Dog implements Animals {
        @Override
        public void look() {}
    }
    
    public class Pig implements Animals {
        @Override
        public void look() {}
    }
    
    public class AnimaDemo {
        public static void main(String[] args) {
            Pig pig = new Pig();
            Dog dog = new Dog();
            List<Pig> pigs = new ArrayList<>();
            List<Dog> dogs = new ArrayList<>();
            pigs.add(pig);
            dogs.add(dog);
    
            List<? extends Animals> animals = new ArrayList<>();
            // 在这里无法添加子类对象
            // <? extends Animals>是一个未知类型,编译器也不知道他是什么类型,是一个限制条件
            animals.add(dog);
            // 协变 子类类型赋值给父类声明  使用通配符这里不会出错
            animals = pigs;
        }
        // 打印所有动物的看方法 使用通配符上限进行协变  
        public static void println(List<? extends Animals> anima){
            System.out.println(anima.look())
        }
        println(pigs)
        // 错误代码 只能使用父类的泛型参数调用
        public static void println(List<Animals> anima){
            System.out.println(anima.look())
        }
        //IDE 会报错提示
        println(pigs)
    }

类型通配符下限。<? super T>,?代表T类型本身或者是T的父类型。也叫作逆变或者反变

  • 父类的泛型类型对象赋值给子类的泛型类型对象声明
    public class AnimaDemo {
        public static void main(String[] args) {
            Pig pig = new Pig();
            Dog dog = new Dog();
            List<Pig> pigs = new ArrayList<>();
            List<Dog> dogs = new ArrayList<>();
            pigs.add(pig);
            dogs.add(dog);
    
            List<? super Animals> animals = new ArrayList<>();
            List<Object> animalObjs = new ArrayList<>();
            // 逆变
            animals = animalObjs
            // 在这里可以添加子类对象
            animals.add(dog);
            // 错误代码 不可以添加父类对象
            animals.add(new Object())
        }
    }

除了通配符可以实现限制,类、接口和方法中定义的泛型参数也能限制上限和下限。

泛型的类型擦除

  • 无限制的类型擦除,如果泛型参数没有任何限制,比如<T> <?> 那么擦除之后是Object
  • 有限制的类型擦除,如果泛型参数有上限 <? extends Animals> ,擦除之后是Animals类型
  • 有限制的类型擦除,如果泛型参数有上限 <? super Animals> ,擦除之后是Object类型

一个被人们讲烂了的例子

List<String> l1 = new ArrayList<String>();
List<Integer> l2 = new ArrayList<Integer>();
		
System.out.println(l1.getClass() == l2.getClass());
// 输出true
// 在这里l1和l2类型擦除之后的Class都是List,所以会返回true

Kotlin的泛型

在Kotlin中泛型与Java一样都是不可变,但是和Java型变这块有一定的区别

在Kotlin中协变使用out表示逆变(下限)用in表示(上限)

协变

逆变

声明:本笔记是自己巩固泛型的时候对应网上文章和书籍的一些理解,如有搬运出于对原作者的尊重,会在尾部注明搬运链接,如果侵权请联删除

以上文章参考