美文网首页
泛型 - 作用与定义

泛型 - 作用与定义

作者: 耀恒 | 来源:发表于2019-12-25 15:26 被阅读0次
    image

    概述

    泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?
    顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),
    然后在使用/调用时传入具体的类型(类型实参)。
    泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,
    操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。

    泛型类

    定义

    // 多个泛型以','分隔,如:<T,K>
    public class 类名称 <泛型标识:可以随便写任意标识号,标识指定的泛型的类型> {
      private 泛型标识 /*(成员变量类型)*/ var; 
      .....
    }
    

    示例

    // 此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
    // 在实例化泛型类时,必须指定T的具体类型
    public class Generic<T>{ 
        // key这个成员变量的类型为T,T的类型由外部指定  
        private T key;
        // 泛型构造方法形参key的类型也为T,T的类型由外部指定
        public Generic(T key) {
            this.key = key;
        }
        // 泛型方法getKey的返回值类型为T,T的类型由外部指定
        public T getKey(){ 
            return key;
        }
    }
    
    // 泛型的类型参数只能是类类型(包括自定义类),不能是简单类型
    // 传入的实参类型需与泛型的类型参数类型相同,即为Integer.
    Generic<Integer> genericInteger = new Generic<Integer>(123456);
    // 传入的实参类型需与泛型的类型参数类型相同,即为String.
    Generic<String> genericString = new Generic<String>("key_vlaue");
    Log.d("泛型测试", "key is " + genericInteger.getKey());
    Log.d("泛型测试", "key is " + genericString.getKey());
    

    ==注意:==

    • 泛型的类型参数只能是类类型,不能是简单类型。
    • 不能对确切的泛型类型使用instanceof操作。如下面的操作是非法的,编译时会出错。
      如:if(str instanceof Generic<String>){ }

    泛型接口

    定义

    // 定义一个泛型接口
    public interface Generator<T> {
        public T next();
    }
    

    示例

    正确使用
    /**
     * 传入泛型实参时:
     * 定义一个生产器实现这个接口,虽然我们只创建了一个泛型接口Generator<T>
     * 但是我们可以为T传入无数个实参,形成无数种类型的Generator接口。
     * 在实现类实现泛型接口时,如已将泛型类型传入实参类型,则所有使用泛型的地方都要替换成传入的实参类型
     * 即:Generator<T>,public T next();中的的T都要替换成传入的String类型。
     */
    public class FruitGenerator implements Generator<String> {
        private String[] fruits = new String[]{"Apple", "Banana", "Pear"};
        @Override
        public String next() {
            Random rand = new Random();
            return fruits[rand.nextInt(3)];
        }
    }
    
    错误使用
    /**
     * 未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中
     * 即:class FruitGenerator<T> implements Generator<T>{
     * 如果不声明泛型,如:class FruitGenerator implements Generator<T>,编译器会报错:"Unknown class"
     */
    class FruitGenerator<T> implements Generator<T>{
        @Override
        public T next() {
            return null;
        }
    }
    

    泛型方法

    定义

    /**
     * 泛型方法
     * @param clazz 传入的泛型实参
     * @return T 返回值为T类型
     * 说明:
     *     1)public 与 返回值中间<T>非常重要,可以理解为声明此方法为泛型方法。
     *     2)只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
     *     3)<T>表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。
     *     4)与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。
     */
    public <T> T genericMethod(Class<T> clazz) throws InstantiationException, IllegalAccessException {
            T instance = clazz.newInstance();
            return instance;
    }
    
    /**
     * 泛型方法,可变参数
     * @param args 传入的泛型可变参数
     */
    public <T> void printMsg(T... args) {
        for(T t : args){
            Log.d("泛型测试","t is " + t);
        }
    }
    

    简单示例

    // 主方法
    public static void main(String[] strs) {
        // 通过泛型方法创建对象
        Object obj = genericMethod(Class.forName("android.app.Activity"));
    }
    

    深入泛型方法示例

    public class GenericFruit {
        class Fruit{
            @Override
            public String toString() {
                return "fruit";
            }
        }
    
        class Apple extends Fruit{
            @Override
            public String toString() {
                return "apple";
            }
        }
    
        class Person{
            @Override
            public String toString() {
                return "Person";
            }
        }
    
        class GenerateTest<T>{
            public void show_1(T t){
                System.out.println(t.toString());
            }
    
            // 在泛型类中声明了一个泛型方法,使用泛型E,这种泛型E可以为任意类型。可以类型与T相同,也可以不同。
            // 由于泛型方法在声明的时候会声明泛型<E>,因此即使在泛型类中并未声明泛型,编译器也能够正确识别泛型方法中识别的泛型。
            public <E> void show_3(E t){
                System.out.println(t.toString());
            }
    
            // 在泛型类中声明了一个泛型方法,使用泛型T,注意这个T是一种全新的类型,可以与泛型类中声明的T不是同一种类型。
            public <T> void show_2(T t){
                System.out.println(t.toString());
            }
        }
    
        public static void main(String[] args) {
            Apple apple = new Apple();
            Person person = new Person();
    
            GenerateTest<Fruit> generateTest = new GenerateTest<Fruit>();
            // apple是Fruit的子类,所以这里可以
            generateTest.show_1(apple);
            // 编译器会报错,因为泛型类型实参指定的是Fruit,而传入的实参类是Person
            //generateTest.show_1(person);
    
            // 使用这两个方法都可以成功
            generateTest.show_2(apple);
            generateTest.show_2(person);
    
            // 使用这两个方法也都可以成功
            generateTest.show_3(apple);
            generateTest.show_3(person);
        }
    }
    

    静态方法与泛型

    public class StaticGenerator<T> {
        ....
        ....
        /**
         * 如果在类中定义使用泛型的静态方法,需要添加额外的泛型声明(将这个方法定义成泛型方法)
         * 即使静态方法要使用泛型类中已经声明过的泛型也不可以。
         * 如:public static void show(T t){..},此时编译器会提示错误信息:
         * "StaticGenerator cannot be refrenced from static context"
         */
        public static <T> void show(T t) {
    
        }
    }
    

    泛型方法总结

    泛型方法能使方法独立于类而产生变化,以下是一个基本的指导原则:

    无论何时,如果你能做到,你就该尽量使用泛型方法。也就是说,如果使用泛型方法将整个类泛型化,那么就应该使用泛型方法。另外对于一个static的方法而已,无法访问泛型类型的参数。所以如果static方法要使用泛型能力,就必须使其成为泛型方法。


    泛型优点

    1. 类型安全

    这是最显而易见的,泛型的主要目标是提高 Java 程序的类型安全。通过使用泛型定义的变量的类型限制,可以很容易实现编译期间的类型检测,避免了大量因为使用Object带来的不必要的类型错误。

    没有泛型,这些对Object变量的类型假设就只存在于程序员的头脑中(或者如果幸运的话,还存在于代码注释中),而且每次使用前还需要进行不安全的强制类型转换。

    2. 代码复用

    泛型的一个很大好处就是增加了代码的复用性,比如上面的 Generic 类,就能存取任意类型的对象,而不用为每种类型写一个包装类。

    3. 潜在的性能收益

    泛型为较大的优化带来可能。在泛型的初始实现中,编译器将强制类型转换(没有泛型的话,程序员会指定这些强制类型转换)插入生成的字节码中。但是更多类型信息可用于编译器这一事实,为未来版本的 JVM 的优化带来可能。由于泛型的实现方式,支持泛型(几乎)不需要 JVM 或类文件更改。所有工作都在编译器中完成,编译器生成类似于没有泛型(和强制类型转换)时所写的代码,只是更能确保类型安全而已。Java语言引入泛型的好处是安全简单。泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。

    PECS原则

    • 如果要从集合中读取类型T的数据,并且不能写入,可以使用 ? extends 通配符;(Producer Extends)
    • 如果要从集合中写入类型T的数据,并且不需要读取,可以使用 ? super 通配符;(Consumer Super)
    • 如果既要存又要取,那么就不要使用任何通配符。

    参考:

    java泛型详解

    Java泛型探究一

    Java泛型探究二

    相关文章

      网友评论

          本文标题:泛型 - 作用与定义

          本文链接:https://www.haomeiwen.com/subject/bzpcoctx.html