Java泛型

为什么需要泛型

  • 泛型利于代码重用。比如实现针对某一种具体数据类型的功能,将具体数据类型替换为泛型,则可以实现为针对多种数据类型的功能,极大的提高了功能的复用性。

  • 类型安全,让编译器帮助我们进行类型检查。指定泛型中的类型,让java编译器帮助我们检查类型以及类型转换,不再需要我们自己进行类型判断及强制装换。

泛型的使用

//动物类
public class Animal {
    public void eat(){
        System.out.println("动物吃");
    }
}

//猫类
public class Cat extends Animal {
    @Override
    public void eat() {
        System.out.println("猫吃东西");
    }

    public void cry(){
        System.out.println("喵喵叫");
    }
}

//卡菲猫
public class Garfield extends Cat {
}
//泛型类的使用,这种泛型使用方式会导致类型被擦除为所有类的父类Object
//这意味着你在运行期会丢失原本类型的所有信息,无法使用原本类相关的任何属性和方法
public class GenericClass<T> {
    //泛型擦除,查看字节码data的类型为 Ljava/lang/Object; data
    private T data;

    GenericClass(T data){
        this.data = data;
    }

    public T getData(){
        return data;
    }

    public void setData(T data){
        this.data = data;
    }
}

//使用了限制的泛型,通过这种方式可以一定程度上弥补第一种方式的弊端
//因为泛型类型被擦除为指定父类,这样可以使用父类的方法
public class GenericExtendsClass<T extends Animal> {
    //泛型擦除为Animal,查看字节码data的类型为 L easycode/Animal; data
    private T data;

    GenericExtendsClass(T data){
        this.data = data;
    }

    public T getData(){
        return data;
    }

    public void setData(T data){
        this.data = data;
    }

    public void doOtherSomething(){
        //调用了Animal特有的方法
        data.eat();
    }
}

//泛型方法的使用,只能定义静态泛型方法
private static <T> T genericAdd(T a, T b) {
        System.out.println(a + "+" + b + "="+a+b);
        return a;
}

java泛型的本质

c++的泛型是根据模板类生成不同的类达到泛型的目的,但是java的泛型并不是如此的,java是通过泛型擦除的方式来实现泛型,所以java的泛型也被称为伪泛型,即只存在于编译期的泛型,在运行阶段找不到泛型信息。

所以,可以将java的泛型当做是一种类型检查的手段,而不要像c++一样将泛型作为实际的类型来使用。

Java泛型中的约束

  • 泛型不能实例化。因为java的泛型擦除,所以在运行期不知道泛型信息,自然也就无法实例化。

  • 类或接口的静态域中不能引用泛型类型变量,但是可以定义静态泛型方法

  • 基本数据类型无法作为泛型类型。因为泛型擦除其实就是将子类赋值给父类,所以基本数据类型无法作为泛型类型。

  • 无法使用 instanceof 关键字或者 ==运算符 判断泛型类的类型,需要借助别的方法(Type类型),但是对原生数据类型无影响

  • 泛型类的原生数据类型与所传递的泛型无关,无论传递什么类型,原生类都是一样的

  • 泛型数组可以声明但无法实例化。原因同泛型不能实例化,但是可以实例化非泛型数组,然后通过定义变量的泛型来进行类型检查。

    //实例化泛型数组,编译不通过
    //ArrayList<String>[] listArray = new ArrayList<String>[5];
            
    //实例化非泛型数组,编译通过
    //通过给listArray添加泛型定义来进行泛型检查
    ArrayList<String>[] listArray = new ArrayList[5];
            
    //编译不通过
    //listArray[0] = new ArrayList<Integer>();
            
    //编译通过
    listArray[0] = new ArrayList<String>();
    
  • 泛型类不能继承Exception或者Throwable

  • 不能捕获泛型类型限定的异常,只能将泛型类型限定的异常抛出

泛型通配符

泛型通配符主要用于一些参数的接收或返回。

通配符类型

  • <? extends Parent>:指定了泛型类的上界,一般用于获取元素即get first,因为可以肯定元素为Parent的某一个子类或Patent,但是具体是哪一个类型无法确定,所以无法添加,只能获取。

  • <? super Child>:指定了泛型的下界,一般用于添加元素即put first,因为可以肯定元素为Child的父类,所以可以添加Child类型或者Child的子类,都是满足多态存储的。

  • <?>:等价于<? extends Object>,即没有限制的泛型类型。
    <?>比如 List<?>一般作为参数来接收外部的集合,或者返回一个不知道具体元素类型的集合。因为<?>代表未知类型,所以不允许往里面添加元素,只能取出来。比如

    public List<?> getList(){
      return new ArrayList<String>();
    }
    
        //测试 extends 和 super
        List<Animal> animal = new ArrayList<Animal>();
        ArrayList<Cat> cat = new ArrayList<>();
        ArrayList<Garfield> garfield = new ArrayList<>();


        animal.add(new Animal());
        cat.add(new Cat());
        garfield.add(new Garfield());

        //测试赋值操作
        //编译出错,Animal是Cat的父类,不能通过泛型检测
//        List<? extends Cat> extendsCatFromAnimal = animal;
        List<? super Cat> superCatFromAnimal = animal;

        List<? extends Cat> extendsCatFromCat = cat;
        List<? super Cat> superCatFromCat = cat;

        List<? extends Cat> extendsCatFromCarfield = garfield;
        //编译出错 Garfield是 Cat的子类,不能通过泛型检测
//        List<? super Cat> superCatFromCarfield = carfield;


        //测试add方法
            //编译出错,extends只能获取元素而不能添加元素
//        extendsCatFromCat.add(new Animal());
//        extendsCatFromCat.add(new Cat());
//        extendsCatFromCat.add(new Garfield());

            //编译出错,可以添加Cat及其子类,但是不能添加其父类Animal
//        superCatFromCat.add(new Animal());
        superCatFromCat.add(new Cat());
        superCatFromCat.add(new Garfield());


        //测试get方法
        Object catExtends2 = extendsCatFromCat.get(0);
        Cat catExtends1 = extendsCatFromCat.get(0);
//        Garfield garfield1 = extendsCatFromCarfield.get(0);

        //可以确定Object是所有类的父类
        Object catSuper1 = superCatFromCat.get(0);
        Object catSuper2 = superCatFromAnimal.get(0);

如何获取泛型的参数类型

获取泛型的类型参数类型需要用到Type接口以及它的子接口。
获取泛型的类型参数类型需要用到Type接口以及它的子接口。

Type类型的子接口有ParameterizedType、GenericArrayType、TypeVariable、WildcardType和Class实现类。

public interface ParameterizedType extends Type {
    //private List<String> list;
    //获取<>中的数据类型,即String
    Type[] getActualTypeArguments();
    //获取<>前面的实际类型,即List
    Type getRawType();
    //如果这个类型是某个类型的所属,获得这个所有者类型,否则返回null
    Type getOwnerType();
}
public interface GenericArrayType extends Type {
    // T[]
   //获取数组元素类型,即 T
    Type getGenericComponentType();
}

public interface TypeVariable<D extends GenericDeclaration> extends Type, AnnotatedElement {
    //private T t;
    //获取泛型的上界,可以通过extends通配符指定,默认是Object
    Type[] getBounds();
    //获取声明该类型变量实体,即该变量所在的类、方法等
    D getGenericDeclaration();
    //获得名称,即K、V、E
    String getName();
}
public interface WildcardType extends Type {
    //获取泛型表达式上界
    Type[] getUpperBounds();
    //获取泛型表达式下界
    Type[] getLowerBounds();
   
}

public final class Class<T> implements java.io.Serializable,
                              GenericDeclaration,
                              Type,
                              AnnotatedElement {

Type接口时是Java编程语言中所有类型的公共高级接口,其中的所有类型如下所示:

  • 原始类型(一般的java类包括枚举、注解、非泛型数组),对应 Class 类

  • 参数化类型(List<String>、Map<String, Object>这种形式),对应 ParameterizedType 接口

  • 数组类型(T[]),对应 GenericArrayType 接口

  • 类型变量(T),对应 TypeVariable 接口

  • 基本类型,对应Class对象

举个例子(获取泛型类型)

public class GenericTest {
    static class Example{
        //参数化类型
        private List<String> list;
    }

    @Test
    public void test() throws Exception{
        Class<Example> exampleClass = Example.class;
        Field field = exampleClass.getDeclaredField("list");

        Type type = field.getGenericType();
        Assert.assertTrue(type instanceof ParameterizedType);

        ParameterizedType parameterizedType = (ParameterizedType)type;
        Assert.assertEquals(String.class ,parameterizedType.getActualTypeArguments()[0]);
        Assert.assertEquals(List.class, parameterizedType.getRawType());
    }
}

参考如下文章:
Java泛型
Type类型常用API介绍
Type类型Demo

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 一、概述 Java开发经常会用到泛型,常用的List、Map都用到了,泛型在Java中有很重要的地位,被广泛应用于...
    码农翻身记阅读 251评论 0 1
  • 📓 本文已归档到:「javacore」🔁 本文中的示例代码已归档到:「javacore」 1. 为什么需要泛型 J...
    静默虚空阅读 175评论 1 1
  • 一、学习目标 1.泛型的作用和定义 2.泛型的基本使用 3.泛型中的通配符 4.泛型擦除 5.泛型中的约束和局限 ...
    Heezier阅读 318评论 0 1
  • Java 泛型 1、泛型的精髓是什么 2、泛型方法如何使用 概述: 泛型在java中具有重要地位,在面向对象编程模...
    SeanJX阅读 504评论 0 0
  • 泛型的定义 Java 泛型(generics)是 JDK1. 5 中引入的一个新特性, 泛型提供了编译时类型安全检...
    安仔夏天勤奋阅读 207评论 0 2