Java 泛型

文章目录

  • 泛型
    • 概念
    • 举例描述
  • 泛型的使用
    • 泛型接口
    • 泛型类
    • 泛型方法
    • 类型通配符
    • 类型擦除

泛型

概念

描述

  • 泛型是程序设计语言的一种特性,在编写代码类的时候不指定具体类型,用一个参数变量表示,在具体实例化使用时才声明指定类型。比如 ArrayList,在定义 ArrayList 类的时候是不知道要保存什么样的对象,所以编写该类代码时使用 ArrayList,用一个参数变量来表示要保存的对象类型。当实例化 ArrayList 用来保存对象时,声明列表要保存的具体对象类型,比如该列表用来保存字符串对象,实例化时使用 new ArrayList,指定了具体类型。

意义

  • 泛型本质就是参数化类型类型参数化,用参数变量来表示要传入的类型,并且该参数变量也只可以代表类。主要作用就是在编译时保证类型安全,提高代码的重用率,将运行时期可能发生的异常提前到编译时期暴露。泛型的类型参数在编译时会被消除,不能直接使用基本值类型作为泛型类型参数。

举例描述

不使用泛型

  • 在没有使用泛型之前,通常使用 Object 类来表示可变的类型。使用 Object 来接收对象在进行类型转换时存在安全风险,容易导致 ClassCastException 异常。

    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("cat");
        list.add("dog");
        list.add(18);
        for (Object o : list) {
            String s = (String) o;
            System.out.println(s);
        }
    }
    

    没有使用泛型时,使用 Object 类接收对象,之后获取元素进行向下转型得到加入时的类型元素,再进行后续操作。这里本想在列表中加入字符串对象,但不小心添加了一个整数类型的对象,该代码在编译时不会报错,但在运行时进行类型转换的时候报 ClassCastException 异常。

使用泛型

  • 使用泛型在编译期间就能确定类型,从而保证类型安全。

    public static void main(String[] args) {
        List<String> list = new ArrayList();
        list.add("cat");
        list.add("dog");
        // list.add(18); // 如果加入这行代码,在编译时便报错。 
        for (Object o : list) {
            String s = (String) o;
            System.out.println(s);
        }
    }
    

    使用泛型在编译期间就进行确定了类型,当不小心传入其他类型的对象时,在编译期间就会报错,不会遗留到运行时,在运行时遍历元素向下转型便不会出现 ClassCastException 异常。提高代码的重用率。

泛型的使用

主要有泛型接口、泛型类、泛型方法、泛型通配符和类型擦除。

只有使用尖括号 <类型参数符号> 的形式才是声明泛型,如 ,单独的类型参数符号表示使用类型参数,如 T。

声明泛型的位置是在类或接口名的后面,如:public class GenericClass;或在方法返回值之前,如: public T genericMethod(T t)。

一般使用 T、E、K、V 符号表示泛型参数。T 代表 Type、E 代表 Element、K 代表 Key,V 代表 Value, 通常用于键值对的表示。

泛型接口

修饰符 interface接口名<代表泛型的变量> { }

  • 例:
    public interface GenericInterface<T> {
        void show(T t);
    }
    

当实现类实现该接口时有两种方式:

  1. 实现类实现接口,同时指定泛型类型。
  2. 实现类实现接口但不指定泛型,这个类也就成了泛型类。

泛型类

修饰符 class 类名<代表泛型的变量> { }

  • 例:
    public class GenericClass<T> {
        public void show(T t){
            ArrayList<T> ts = new ArrayList<>();
            ts.add(t);
            System.out.println("实例化时指定类型");
        }
    }
    

实例化 GenericClass 时指定具体类型参数,之后该实例化对象调用方法时只能传入指定的类型对象。

泛型方法

泛型方法相对于泛型类来说使用粒度更小。泛型类指定的泛型可以在整个类中使用,若多个方法使用该泛型时,在泛型类实例化并指定类型后,这些方法就只能使用指定类型。而泛型方法指定的泛型只针对该方法,只能在该方法中使用,可以在调用该方法时指定具体类型,这样就与泛型类对象无关,代码更加灵活。

修饰符 <代表泛型的变量> 返回值类型 方法名(参数){ }

  • 例:

    public class GenericClass<T> {
    
        public T method(T t){
            System.out.println("该方法不是泛型方法,实例化时指定类型参数,该方法只能传入该类型对象:" + t);
            return t;
        }
    
        // 这里泛型方法使用的参数与泛型类一样(可以不一样),但是并不是同一个,两者毫无关联。
        // 泛型方法使用的 T 都是声明泛型方法中的T,而不是泛型类中T。
        public <T> T genericMethod(T t){
            System.out.println("该方法是泛型方法,使用方法时指定类型参数,与泛型类指定的类型参数无关:" + t);
            return t;
        }
    
        public static void main(String[] args) {
            GenericClass<String> stringGenericClass = new GenericClass<>();
            stringGenericClass.method("string");
            stringGenericClass.genericMethod(1);
        }
    }
    

类型通配符

类型通配符

类型通配符一般是使用 ? 代替具体的类型实参。类型通配符是类型实参,而不是类型形参。使用时一般用在方法参数中,表示可以接受该类所有类型的泛型变量。

当在一个不是泛型类中使用泛型类的对象作为方法参数传入时,需要明确指定泛型类中类型参数,否则编译会报错。

  • 例:

        // public void show(List list){ // 报错
        public void show(List<String> list){
            for (Object o : list) {
                System.out.println(o);
            }
        }
    

以上代码在传入泛型类参数时需要指定类型实参( 是类型形参, 是类型实参),但是这样限制了代码的通用性。本来该方法传参是可以使用所有类型实参的泛型类对象,而不是某个具体类型实参的泛型类对象,但是定义该方法时参数需要指定类型实参,导致该方法使用非常局限。

  • 例:

    public class OrdinaryClass {
    
        public void show(List<String> list){
            for (Object o : list) {
                System.out.println(o);
            }
        }
    
        public static void main(String[] args) {
            ArrayList<String> strings = new ArrayList<>();
            ArrayList<Integer> integers = new ArrayList<>();
            OrdinaryClass ordinaryClass = new OrdinaryClass();
            ordinaryClass.show(strings);
            // ordinaryClass.show(integers); // 加入该代码编译报错。
        }
    }
    

基于以上考虑,便提出使用 ? 通配符来表示任意泛型类的类型实参,这样保证了定义方法传入泛型类作为参数时,该方法能接收所有类型实参的泛型类对象,只与泛型类有关,与泛型类中的类型实参无关,提高代码重用性。

  • 例:

    public class OrdinaryClass {
    
        public void show(List<?> list){
            for (Object o : list) {
                System.out.println(o);
            }
        }
    
        public static void main(String[] args) {
            ArrayList<String> strings = new ArrayList<>();
            ArrayList<Integer> integers = new ArrayList<>();
            OrdinaryClass ordinaryClass = new OrdinaryClass();
            ordinaryClass.show(strings);
            ordinaryClass.show(integers); // 加入该代码不会报错
        }
    }
    

通配符上限

:表示只能匹配类型参数为 T 类型或其子类。

  • 如:List 能接收类型参数为 Number 及其子类。

通配符下限

:表示只能匹配类型参数为 T 类型或其父类。

  • 如:List 能接收类型参数为 Integer 及其父类。

类型擦除

在泛型内部,无法获得任何有关泛型参数类型的信息,泛型只在编译阶段有效,生成的class文件中将不再带有泛形信息,以此使程序运行效率不受到影响,这个过程称之为“擦除”。

泛型类型在逻辑上可看成是多个不同的类型,但是其实质都是同一个类型。因为泛型是在JDK5之后才出现的,需要处理 JDK5之前的非泛型类库。擦除的核心动机是它使得泛化的客户端可以用非泛化的类库实现,反之亦然,这经常被称为"迁移兼容性"。

  • 例:
    public static void main(String[] args) throws NoSuchFieldException {
        ArrayList<String> strings = new ArrayList<>();
        ArrayList<Integer> integers = new ArrayList<>();
        System.out.println(strings.getClass().getSimpleName());
        System.out.println(integers.getClass().getSimpleName());
        System.out.println(strings.getClass() == integers.getClass());
    }
    
    输出:
    ArrayList
    ArrayList
    true

所有泛型类型参数,若没有设置泛型上限,则编译之后统一擦除为Object类型,若设置了泛型上限,则编译之后统一擦除为相应的泛型上限。

无上限类型擦除,擦除后实际类型为 Object。

  • 例:
    public class GenericClass<T> {
        private T var;
        
        public static void main(String[] args) throws NoSuchFieldException {
            GenericClass<String> stringGenericClass = new GenericClass<>();
            Class<? extends GenericClass> aClass = stringGenericClass.getClass();
            Field var = aClass.getDeclaredField("var");
            System.out.println(var.getType().getSimpleName());
        }
    }
    
    
    输出:Object

有上限类型擦除,擦除后实际类型为上限类型。

  • 例:
    public class GenericClass<T extends Number> {
    
        private T var;
    
        public static void main(String[] args) throws NoSuchFieldException {
            GenericClass<Integer> stringGenericClass = new GenericClass<>();
            Class<? extends GenericClass> aClass = stringGenericClass.getClass();
            Field var = aClass.getDeclaredField("var");
            System.out.println(var.getType().getSimpleName());
        }
    
    }
    
    输出:Number

你可能感兴趣的:(Java,java)