您的位置 首页 java

Java 泛型总结

作为一名 Java 程序员, 泛型 用的真的特别多,写代码的时候,通常也是借助 IDEA 一通提示来使用泛型,要么就是 copy 代码,但是泛型涉及的内容概念你真的懂了吗,如下提到的内容你都能准确的回答上来吗?我面试了不少人,特意问到这个问题,很少有人能准确的回答上来。

前言

泛型产生的历史背景,泛型从 JDK 1.5 开始引入,通过类型擦除在编译器实现,泛型中定义的 T 会被替换为具体的对象。对于 Java 虚拟机 来说是没有感知的。泛型分为泛型类,泛型方法,泛型接口。

泛型的类型限定指具体限定和通配符限定。泛型看起来比较别扭,但是作用非常巨大。

如下图中,我们使用方法的时候,看到一大堆 ? super T R 各种各样和泛型相关的提示,如果不好好学习泛型的知识,就很难看懂代码中的这些提示。

泛型带来的好处,为什么要使用泛型

什么是泛型

泛型就是参数化类型,将类型由原来的具体类型变得参数化,不确定。只有在使用的时候,才传入具体的参数类型。

  • 泛型:把类型明确的工作推迟到创建对象或调用方法的时候才去明确的特殊的类型。
  • 泛型的原则:只要编译期间没有出现错误,那么运行期间就不会出现类型转换异常 ClassCastException。

泛型的好处

1. 代码更加简洁易懂。

2. 集合类型的对象,collection、map 都指定了具体类型,一目了然。获取元素的时候,不需要写强制转换。

     public static void main(String[] args) {
        //没有泛型的情况,需要写类型强转。
        List list = new ArrayList<>();
        list.add(50);
        list.add("张三");
         Integer  a = (Integer)list.get(0);
        String b = (String)list.get(1);
        System.out.println(a);
        System.out.println(b);

        //使用了泛型的写法,已经确定了类型。
        List<Integer> listFx = new ArrayList<>();
        listFx.add(50);
        listFx.add(20);
        for(Integer i : listFx){
            System.out.println(i);
        }
    }
  

3. 程序更加健壮,保证类型安全。

4. 没有强转导致的类型错误。在没有泛型以前,我们使用 object 来代表任意类型。但是,向下转型的时候需要强转,程序不太安全,容易出现类型转换异常 ClassCastException。

5. 有了泛型才可以用增强 for 循环。如下代码:

     public static void main(String[] args) {
        List<String> nameList = new ArrayList<>();
        nameList.add("张三");
        nameList.add("李四");
        nameList.add("王五");
        //增强型 for 循环
        for(String name : nameList){
            System.out.println(name);
        }
    }
  

泛型的使用场景

泛型类

就是把泛型定义在 Java 类上,只有在使用泛型类的时候,才可以确定具体的类型。用户使用的时候,指定什么类型,就是什么类型。不用担心强转的问题和运行时转换异常的问题。定义在类上面的泛型,在类的方法中也可以使用。

如下:示例代码。

     public class BaseFruit<T> {

        private T fruit;

        void handleFruit(T fruit){
            System.out.println(fruit);
        }

        T getFruit(){
            return this.fruit;
        }
    }
  

泛型方法

泛型单独作用在方法上,先要指定<T>。这样方法中才可以使用这个泛型。

如下:示例代码。

     public static <T> T getPerson(T t){
        return t;
    }
  

泛型接口

泛型接口的写法,示例代码。

     public interface BaseDao<T> {

        public List<T> getList(T t);

    }
  

泛型的通配符 K、V、T、E、?

KVTE? 在本质上都是属于 通配符 ,本身没有什么区别,只是大家 编码 时候的一种通用的约定,叫其他字母也可以。只不过可读性没有那么强。

KVTE? 分别代表什么意思?

K、V 是单词 key 和 value 的代表。表示键值对。

代码示例:

     public static <K,V>  HashMap <K,V> getHashMap(K name,Class<V> clazz) throws Exception {
        HashMap<K, V> map = new HashMap<>();
        map.put(name, clazz.newInstance());
        return map;
    }
  

T 是单词 type 的代表,表示一个具体的 Java 类型,一般用在基类上,例如:

     public class BasePerson<T> {

        private T teacher;

        private T doctor;

    }
  

E 是 element 的代表,表示一个元素。一般用在集合里面,集合里面包含的是元素。

     public static <E> List<E> getList(){
        List<E> list = new ArrayList<>();
        return list;
    }
  

? 属于通配符,表示不确定元素。

如下代码示例,第一个是通配符写法,第二个是普通泛型的写法。

     //使用通配符的写法
    static void testFX03(List<?> list){
        System.out.println(list.size());
    }

    //使用 T 泛型的写法,要先指定泛型 T
    static <T> void testFX04(List<T> list){
        System.out.println(list.size());
    }
  
 
  

上界通配符

上界通配符的英文全称:Upper Bounds Wildcards。

上边界限定通配符的写法:<? extends BasePerson>。

简单的意思就是说:给通配符 ? 设置一个上限,例如:<? Extends Person> 通配符必须是 Person 的子类或者本身,他的上限就是 Person。

假设,我们现在有一个基础类 BasePerson 和两个它的派生类 Teacher、Doctor。

基础类:person。

 public class BasePerson {}
  

派生类:Doctor。

     @Data
    public class Doctor extends BasePerson{
        private String name;
    }
  

派生类:Teacher。

     @Data
    public class Teacher extends BasePerson{
        private String name;
    }
  

如下:

testFX 方法的参数类型是 List<? extends BasePerson>,因此 testFX 方法的入参可以是 List<Teacher> 和 List<Doctor>,因为 Teacher 和 Doctor 都是 BasePerson 的子类。体现了泛型的上界限制。

     public static void main(String[] args) {
        List<Teacher> list = new ArrayList<>();
        List<Doctor> list1 = new ArrayList<>();
        testFX(list);
        testFX(list1);
    }
    static void testFX(List<? extends BasePerson> list){
        System.out.println(list.size());
    }
  

现在定义一个公交车类 bus,Doctor 和 Teacher 都将作为一名乘客,乘坐巴士。

     @Data
    public class Bus<T> {
        private T passenger;

        public Bus(T passenger) {
            this.passenger = passenger;
        }
    }
  

我们创建一个 Bus,泛型为 Teacher。如下,但是编译错误。
Teacher 和 BasePerson 拥有继承关系。但是 Bus<BasePerson> 和 new Bus<Teacher> 两者之间没有关系,不能向上兼容。那么改怎么处理?这个时候上下界通配符就体现了作用。

错误写法:

 Bus<BasePerson> bus = new Bus<Teacher>(new Teacher());
  

上界通配符的写法:

 Bus<? extends BasePerson> bus = new Bus<Teacher>(new Teacher());
  

解释如下:

  • Bus<? extends BasePerson> :表示只要是继承了 BasePerson 的对象,都可放在巴士里面。
  • new Bus<Teacher>(new Teacher()) :因为 teacher 继承了 BasePerson。所以,两种 bus 发生了关系,可以直接进行传递。

下界通配符

下界通配符的英文全称:Lower Bounds Wildcards。

下边界限定通配符的写法:<? super Person>。

下界通配符和上界通配符表达的是一个相反的概念。表达通配符最低的界限,通配符的类型必须都是 Person 的父类。

 Bus<? super BasePerson> bus = new Bus<>(new Teacher());
  

假设:我们现在定义一个 Animal 类,BasePerson 类和 Dog 类继承 Animal 类。

代码示例:

Animal 动物类

     public class Animal {
    }
  

Dog 类,狗狗类

     public class Dog extends Animal{

        private String name;

    }
  

Person 类,人类。

     public class BasePerson extends Animal{

        private String name;

    }
  

测试类:只有父类可以传递。

方法 testFX01 和方法 testFX02 都可以使用 List<Animal> 作为入参,原因是 Animal 分别是 Dog 和 BasePerson 的父类。

     public static void main(String[] args) {
        List<Animal> list = new ArrayList<>();
        testFX01(list);
        testFX02(list);

    }
    //入参 Dog 的父类
    static void testFX01(List<? super Dog> list){
        System.out.println(list.size());
    }
    //入参 BasePerson 的父类
    static void testFX02(List<? super BasePerson> list){
        System.out.println(list.size());
    }
  

上下界通配符的不足,PECS 原则简单理解。

1. 上界通配符不支持写入,<? extends T> 使用的时候 set 方法失效。

举例:我们定义一个公交车类 Bus,里面乘坐了一名老师 Teacher。

Bus 类

     public class Bus<T> {

        private T person;

        public Bus(T person) {
            this.person = person;
        }

        public T getPerson() {
            return person;
        }

        public void setPerson(T person) {
            this.person = person;
        }
    }
  

测试代码

我们通过 Bus 的构造器创造 bus 对象,里面传入 Teacher 张三。 但是调用 bus 的 setPerson 方法传入李四。这个时候代码就会编译出错。

     public static void main(String[] args) {
        Bus<? extends BasePerson> bus = new Bus<>(new Teacher("张三"));
        bus.setPerson(new Teacher("李四"));
    }
  

错误信息如下:

     Error:(77, 23) java: 不兼容的类型: com.example.demo.fx.Teacher 无法转换为 capture#1, 共 ? extends com.example.demo.fx.BasePerson
  

报错的原因

编译器只知道 Bus 内是 BasePerson 或者他的子类,不知道具体的类型,也许是 Teacher 类,也许是 Doctor 类。无法确定他的类型,所以都不允许。

2、下界通配符 <? super T> 取对象的时候,只能配合 Object 对象进行使用。

如下代码:用 Dog 接收,则会编译失败。只能用 Object 接收。

     public static void main(String[] args) {
        Bus<? super Dog> bus = new Bus<>(new Dog("二狗"));
        Dog person = bus.getPerson(); //报错,不能确定类型
        Object obj = bus.getPerson(); //只能用 Object 基类接收
    }
  

什么是 PECS 原则?

全称:Producer Extends Consumer Super。

根据上下界通配符的两个限制原因,得出以下两条原则。

  • 往外读取多的,通常使用上界 Extends
  • 网内存储多的,通常使用下界 Super

总结

泛型是 Java 语言一种进步,功能强大,可以简化我们的代码,提高代码的稳定性,减少类型转换错误。同时,她也有一些不足,滥用泛型也会造成一定的理解困难,降低代码可读性。我们应该根据自己的实际需求,合理的使用泛型。

文章来源:智云一二三科技

文章标题:Java 泛型总结

文章地址:https://www.zhihuclub.com/194542.shtml

关于作者: 智云科技

热门文章

发表回复

您的电子邮箱地址不会被公开。

1条评论

  1. Nodular endocardial infiltrates quality lesions cause significant variability in diagnosis of ISHLT grade 2 and 3A rejection in cardiac allograft recipients

网站地图