基础篇——泛型(Generics)

版权声明:旨在技术交流与成长,欢迎大家来交流学习。 https://blog.csdn.net/qq941263013/article/details/82690313

写代码的四点:

     1.明确需求。要做什么?

     2.分析思路。要怎么做?(1,2,3……)

     3.确定步骤。每一个思路要用到哪些语句、方法和对象。

     4.代码实现。用具体的语言代码将思路实现出来。

学习新技术的四点:

     1.该技术是什么?

     2.该技术有什么特点?(使用需注意的方面)

     3.该技术怎么使用?(写Demo)

     4.该技术什么时候用?(在Project中的使用场景 )

----------------------早计划,早准备,早完成。------------------------

为什么要使用泛型?

        Java设计之初并不知道会往容器中存放什么类型的元素,因此元素类型都设定为Object,这样就什么都能放了。但是这么设计有明显的缺点:①取出元素的时候必须进行强制类型转换异常;②如果不小心往集合里加了不相同类型的元素可能会导致类型异常,进行equals、compare比较的时候尤为明显;③在很多地方都需要强制类型转换,增加了变成的复杂度,影响了代码的美观和维护成本。

        因此泛型应运而生。

泛型概述:

        泛型(generics)是Java 1.5中引入的新特性,泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。只要编译时不出现问题,运行时就不会出现ClassCastException(类型转换异常)。

        泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。

        Java集合都实现了泛型,允许程序在创建集合时就可以指定集合元素的类型,例如List<String>就表明这是一个只能存放String类型的List。List<String>就是参数化类型,也就是泛型,而String就是该List<String>泛型的类型参数。

        泛型只在编译阶段有效,在编译之后会进行泛型擦除的操作,不会传递到运行阶段。也就是说编译后的class文件中是不包含任何泛型信息的。

泛型的规则:

        1.泛型的类型参数只能是类类型(包括自定义类)和通配符,不能是简单类型;

        2.同一种泛型可以对应多个版本(类型参数不同),不同版本的泛型类实例是不兼容的;

        3.泛型的类型参数可以有多个,例如<K , V>;

        4.泛型的类型参数可以使用extends语句,形如<T extends Number>称为有界的类型参数。

        5.不能对确切的泛型类型使用 instanceof 操作;

            注:A  instanceof  B:判断A是否是B的实例对象或者B子类的实例对象。

        6.不能创建一个确切的泛型类型的数组;

泛型的好处:

        1.在编译的时候检查类型安全,减少了运行时异常,降低crash率;保证了如果在编译时没有发出警告,则在运行时就一定不会产生ClassCastException(类型转换异常);

        2.集合创建时就指定了集合元素的类型,取出元素的时候不需要强制类型装换了;

        3.提高了代码的复用性、减少维护成本;

泛型类:

        1.定义一个类,在该类名后面添加类型参数声明部分(由尖括号分隔)。

        2.每一个类型参数声明部分可以包括一个或多个类型参数,参数间用逗号隔开。

        3.使用<T>来声明一个类型持有者名称,然后就可以把T当作一个类型来声明成员、参数、返回值类型。T仅仅是个名字,可以自行定义。

/**
 * 泛型类
 */

public class GenericsBox<T> {
    private T t;

    public void add(T t) {
        this.t = t;
    }

    public T get() {
        return t;
    }
}
        //泛型类调用
        GenericsBox<Integer> integerGenericsBox = new GenericsBox<>();
        GenericsBox<String> stringGenericsBox = new GenericsBox<>();

        integerGenericsBox.add(new Integer(25));
        stringGenericsBox.add(new String("年龄"));
        LogUtil.e("泛型类调用", stringGenericsBox.get() + ":" + integerGenericsBox.get());

泛型接口:

        1.定义一个接口,在该接口名后面添加类型参数声明部分(由尖括号分隔)。

        2.每一个类型参数声明部分可以包括一个或多个类型参数,参数间用逗号隔开。

/**
 * 泛型接口
 */

public interface GenericsInterface<T> {

    public abstract void genericsInterface1(T element);

    public abstract <T> void genericsInterface2();
}

泛型方法:

        定义一个泛型方法,该方法在调用时可以接收不同类型的参数。根据传递给泛型方法的参数类型,编译器适当的处理每一个方法调用。

        定义泛型方法的规则:

        1.所有泛型方法声明都有一个类型参数声明部分(由尖括号分隔),该类型参数声明部分在方法返回值类型之前;

        2.每一个类型参数声明部分包括一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符;

        3.类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际参数类型的占位符;

    /**
     * 泛型方法
     * 打印各种类型的数组中的元素
     *
     * @param inputArray
     * @param <T>
     */
    public static <T> void printLog(T[] inputArray) {
        for (T element : inputArray) {
            LogUtil.e("打印数组中的元素", element + "");
        }
    }
        //创建各种类型的数组(Integer、Double、Character)
        Integer[] integerArray = {1, 2, 3, 4, 5};
        Double[] doubleArray = {1.1, 2.2, 3.3, 4.4, 5.5};
        Character[] characterArray = {'a', 'b', 'c', 'd', 'e'};

        printLog(integerArray);
        printLog(doubleArray);
        printLog(characterArray);

静态方法与泛型:

        如果在类中定义使用泛型的静态方法,需要添加额外的泛型类型参数声明,即将该静态方法定义为泛型方法。即使静态方法要使用泛型类中已经声明过的类型参数也不可以。

/**
 * 静态方法与泛型
 */

public class GenericsStatic<T> {

    public static <T> void show(T t) {
    }

    //以下是错误的
//    public static void show(T t) {
//    }
}

有界的类型参数:

        限定被允许传递到一个类型参数的类型种类范围。

        1.要声明一个有界的类型参数,首先列出类型参数的名称,后跟extends关键字,继承上界。例如:上界通过形如<T extends Comparable<T>>来定义,表示类型只能接受Comparable及其下层子类类型。这就是有界的类型参数的目的。

        注意:<T extends Comparable<T>>这里的限定使用关键字extends,后面可以是类也可以是接口。extends统一的表示了原有的extends和implements的概念,应该理解为T类型是实现了Comparable接口的类型,或者是继承了XX类的类型。

        2.泛型仍要遵循Java单继承、多实现的体系,所以当某个类型参数需要使用extends限定,且有多种类型的时候,只能存在一个类,并且类写在第一位,接口列在后面。形如<T extends Number & Comparable & Callback>

    /**
     * 泛型方法
     * 比较三个值并返回最大值
     *
     * @param x
     * @param y
     * @param z
     * @param <T>
     * @return
     */
    public static <T extends Comparable<T>> T maxValue(T x, T y, T z) {
        //假设x是最大值
        T max = x;

        //如果y比max大,则将y赋值给max
        if (y.compareTo(max) > 0) {
            max = y;
        }
        //如果z比max大,则将z赋值给max
        if (z.compareTo(max) > 0) {
            max = z;
        }
        //返回最大值
        return max;
    }
        //输出最大值
        LogUtil.e("最大值为:", maxValue(3, 7, 5) + "");
        LogUtil.e("最大值为:", maxValue(3.3, 3.7, 5.3) + "");
        LogUtil.e("最大值为:", maxValue("apple", "pear", "orange"));

类型通配符(?)

        为了解决类型被限制死了不能动态根据实例来确定的缺点,引入了类型通配符。类型通配符一般使用 ? 代替具体的类型参数。例如:List<?>在逻辑上是List<String>、List<Integer>等所有List<具体类型实参>的父类。

        1.如果只指定了<?>,而没有extends,则默认是允许Object及其下的任何Java类,也就是任意类。

        2.通配符也可以向上限制,通过形如List<? extends Comparable>来定义,表示类型只能接受Comparable及其下层子类类型;

        3.通配符还可以向下限制,通过形如List<? super Comparable>来定义,表示类型只能接受Comparable及其上层父类类型;

        因为getData()的参数是List<?>类型,所以name、age、number都可以作为这个方法的实参,这就是通配符的作用。

        因为getNumberData()的参数是有上限的List<? extends Number>类型的,如此定义就是类型通配符泛型值接受Number及其下层子类类型。

    /**
     * 类型通配符的使用
     */
    public static void getData(List<?> data) {
        LogUtil.e("类型通配符的使用", data.get(0) + "");
    }
    
    public static void getNumberData(List<? extends Number> data) {
        LogUtil.e("类型通配符的使用(Number)", data.get(0) + "");
    }
        //类型通配符
        List<String> name = new ArrayList<>();
        List<Integer> age = new ArrayList<>();
        List<Character> blood = new ArrayList<>();

        name.add("张三");
        age.add(25);
        blood.add('A');

        getData(name);
        getData(age);
        getData(blood);

//        getNumberData(name);
        getNumberData(age);
//        getNumberData(blood);

通配符的PECS原则:

        1.Producer  Extends:使用<?  extends  T>的集合类,只能作为Producer(生产者)向外提供(get)元素;而不能作为Consumer(消费者)对外获取(add)元素;

        2.Consumer  Super:使用<?  super  T>的集合类,只能作为Consumer(消费者)对外获取(add)元素;而不能作为Producer(生产者)向外提供(get)元素;

        3.如果同时需要读取以及写入,那就不能使用通配符。

        //通配符的PECS原则
        List<? extends String> extendsList = new ArrayList<>();
        List<? super String> superList = new ArrayList<>();

//        extendsList.add("李四");
        String s = extendsList.get(0);
        superList.add("张三");
//        Object s1 = superList.get(0);

猜你喜欢

转载自blog.csdn.net/qq941263013/article/details/82690313