Effective-Java——泛型

By timebusker on May 14, 2018

泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。 假定我们有这样一个需求:写一个排序方法,能够对整型数组、字符串数组甚至其他任何类型的数组进行排序,该如何实现? 答案是可以使用 Java 泛型。 使用 Java 泛型的概念,我们可以写一个泛型方法来对一个对象数组排序。然后,调用该泛型方法来对整型数组、浮点数数组、字符串数组等进行排序。

Java 泛型

  • Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。
  • 泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。

特性

泛型只在编译阶段有效。
在编译之后程序会采取去泛型化的措施。也就是说Java中的泛型,只在编译阶段有效。在编译过程中,正确检验泛型结果后, 会将泛型的相关信息擦出,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。也就是说,泛型信息不会进入到运行时阶段。

泛型类

泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分。

泛型类的类型参数声明部分也包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量, 是用于指定一个泛型类型名称的标识符。因为他们接受一个或多个参数,这些类被称为参数化的类或参数化的类型。

package com.timebusker.generics;

/**
 * @DESC:泛型类测试
 * @author: timebusker
 * @date:2018/5/14
 */
public class GenericsClassTest<T> {
    private T key;

    public T getKey() {
        return key;
    }

    public void setKey(T key) {
        this.key = key;
    }

    public static void main(String[] args) {
        int[] ii = {1, 2, 3};

        GenericsClassTest test = new GenericsClassTest<int[]>();
        test.setKey(ii);
        System.out.println(test.getKey().toString());
        // 在Java1.7/1.8利用type inference,让Java自动推导出相应的类型参数
        GenericsClassTest testi = new GenericsClassTest();
        testi.setKey(ii);
        System.out.println(testi.getKey().toString());
    }
}

泛型方法

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

定义泛型方法的规则:

  • 所有泛型方法声明都有一个类型参数声明部分(由尖括号分隔),该类型参数声明部分在方法返回类型之前。
  • 每一个类型参数声明部分包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。
  • 类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际参数类型的占位符。
  • 泛型方法体的声明和其他方法一样。注意类型参数只能代表引用型类型,不能是原始类型(像int,double,char的等)。
package com.timebusker.generics;

import org.junit.Test;

/**
 * @DESC:泛型方法测试
 * @author: timebusker
 * @date:2018/5/14
 */
public class GenericMethodTest {
    // 泛型方法 printArray
    public static <E> void printArray(E[] inputArray) {
        // 输出数组元素
        for (E element : inputArray) {
            System.out.printf("%s ", element);
        }
        System.out.println();
    }

    @Test
    public void testPrintArray() {
        // 创建不同类型数组: Integer, Double 和 Character
        Integer[] intArray = {1, 2, 3, 4, 5};
        Double[] doubleArray = {1.1, 2.2, 3.3, 4.4};
        Character[] charArray = {'H', 'E', 'L', 'L', 'O'};

        System.out.println("整型数组元素为:");
        printArray(intArray); // 传递一个整型数组

        System.out.println("\n双精度型数组元素为:");
        printArray(doubleArray); // 传递一个双精度型数组

        System.out.println("\n字符型数组元素为:");
        printArray(charArray); // 传递一个字符型数组
    }

    // ############################################################################
    class Pair<K, V> {
        private K key;
        private V value;

        public Pair(K key, V value) {
            this.key = key;
            this.value = value;
        }

        public void setKey(K key) {
            this.key = key;
        }

        public void setValue(V value) {
            this.value = value;
        }

        public K getKey() {
            return key;
        }

        public V getValue() {
            return value;
        }
    }

    public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) {
        return p1.getKey().equals(p2.getKey()) && p1.getValue().equals(p2.getValue());
    }

    @Test
    public void testCompare() {
        Pair<Integer, String> p1 = new Pair<>(1, "apple");
        Pair<Integer, String> p2 = new Pair<>(2, "pear");

        boolean same = GenericMethodTest.<Integer, String>compare(p1, p2);
        // 在Java1.7/1.8利用type inference,让Java自动推导出相应的类型参数
        boolean samea = GenericMethodTest.compare(p1, p2);
        System.out.println(same + "\t" + samea);
    }
}

边界符(可定义上界)

限制那些被允许传递到一个类型参数的类型种类范围。例如,一个操作数字的方法可能只希望接受Number或者Number子类的实例。这就是有界类型参数的目的。
现在我们要实现这样一个功能,查找一个泛型数组中大于某个特定元素的个数。
要声明一个有界的类型参数,首先列出类型参数的名称,后跟extends关键字,最后紧跟它的上界。

    public static <T> int countGreaterThan(T[] anArray, T elem) {
        int count = 0;
        for (T e : anArray) {
            if (e > elem)  // 编译不通过
                ++count;
        }
        return count;
    }

这样很明显是错误的,因为除了short, int, double, long, float, byte, char等原始类型,其他的类并不一定能使用操作符>,所以编译器报错,那怎么解决这个问题呢?答案是使用边界符。

# 接口
public interface Comparable<T> {
    public int compareTo(T o);
}

# 实现类
package com.timebusker.generics;

/**
 * @DESC:边界符测试
 * @author: timebusker
 * @date:2018/5/14
 */
public class GenericsBorderTest {
    /**
     * 现在我们要实现这样一个功能,查找一个泛型数组中大于某个特定元素的个数。
     */

//    public static <T> int countGreaterThan(T[] anArray, T elem) {
//        int count = 0;
//        for (T e : anArray) {
//            if (e > elem)  // 编译不通过
//                ++count;
//        }
//        return count;
//    }

    /**
     * 比较三个值并返回最大值
     *
     * @param x
     * @param y
     * @param z
     * @param <T>
     * @return
     */
    public static <T extends Comparable<T>> T maximum(T x, T y, T z) {
        T max = x;
        // 假设x是初始最大值
        if (y.compareTo(max) > 0) {
            //y 更大
            max = y;
        }
        if (z.compareTo(max) > 0) {
            // 现在 z 更大
            max = z;
        }
        // 返回最大对象
        return max;
    }

    public static void main(String args[]) {
        System.out.printf("%d, %d 和 %d 中最大的数为 %d\n\n", 3, 4, 5, maximum(3, 4, 5));
        System.out.printf("%.1f, %.1f 和 %.1f 中最大的数为 %.1f\n\n", 6.6, 8.8, 7.7, maximum(6.6, 8.8, 7.7));
        System.out.printf("%s, %s 和 %s 中最大的数为 %s\n", "pear", "apple", "orange", maximum("pear", "apple", "orange"));
    }


    /**
     * 为泛型添加上边界,即传入的类型实参必须是指定类型的子类型
     *
     * @param x
     * @param y
     * @param z
     * @param <T>
     * @return
     */
    public static <T extends Integer> T maximum(T x, T y, T z) {
        T max = x;
        // 假设x是初始最大值
        if (y.intValue()> 0) {
            //y 更大
            max = y;
        }
        if (z.compareTo(max) > 0) {
            // 现在 z 更大
            max = z;
        }
        // 返回最大对象
        return max;
    }
}

通配符(可定义下界)

类型通配符一般是使用?代替具体的类型参数。例如 List<?> 在逻辑上是List,List 等所有List<具体类型实参>的父类。

import java.util.*;
 
public class GenericTest {
     
    public static void main(String[] args) {
        List<String> name = new ArrayList<String>();
        List<Integer> age = new ArrayList<Integer>();
        List<Number> number = new ArrayList<Number>();
        
        name.add("icon");
        age.add(18);
        number.add(314);
 
        getData(name);
        getData(age);
        getData(number);
       
   }
 
   public static void getData(List<?> data) {
      System.out.println("data :" + data.get(0));
   }
}

类型通配符下限通过形如 List<? super Number>来定义,表示类型只能接受Number及其三层父类类型,如Objec类型的实例。

相关文章