探秘 Java 中的泛型(Generic)

本文包括:

  1. JDK5之前集合对象使用问题
  2. 泛型的出现
  3. 泛型应用
  4. 泛型典型应用
  5. 自定义泛型——泛型方法
  6. 自定义泛型——泛型类
  7. 泛型的高级应用——通配符(wildcard)
  8. 泛型通配符的扩展阅读
Paste_Image.png

泛型(Generic)

1、JDK5之前集合对象使用问题

  1. 可以向集合添加任何类型对象

  2. 从集合取出对象时,数据类型丢失,使用与类型相关方法,强制类型转换。

  3. 程序存在安全隐患

2、泛型的出现

  1. JDK5中的泛型允许程序员使用泛型技术限制集合的处理类型

     List<String> list = new ArrayList<String>();
    
  2. 注意:泛型是提供给javac编译器使用的,它用于限定集合的输入类型,让编译器在源代码级别上,即挡住向集合中插入非法数据。但编译器编译完带有泛型的java程序后,生成的.class文件中将不再带有泛型信息,因此程序运行效率不受影响,这个过程称为“擦除”。

  3. 泛型的基本术语,以ArrayList<E>为例:"<>"读作typeof

    • ArrayList<E>中的E称为类型参数变量
    • ArrayList<Integer>中的Integer称为实际类型参数。
    • 整个ArrayList<Integer>称为参数化类型ParameterizedType

3、 泛型应用

  • 类型安全检查

  • 编写通用Java程序(Java框架)

4、泛型典型应用

  1. 使用Type-Safe的集合对象

    • List

    • Set

    • Map

  2. List示例:

     //使用类型安全List
     List<String> list = new LinkedList<String>();
     //因为使用泛型,只能添加String类型元素
     list.add("aaa");
     list.add("bbb");
     list.add("ccc");
     
     //遍历List有三种方法
     
     //方法一:因为List是有序的(存入顺序和取出顺序一样),通过size和get方法进行遍历
     for (int i = 0; i < list.size(); i++) {
         String s = list.get(i);
         System.out.println(s);
     }
    
     //方法二:因为List继承Collection接口,通过Collection的iterator进行遍历
     Iterator<String> iterator = list.iterator();
     //遍历iterator通过迭代器hasNext和next方法进行遍历
     while (iterator.hasNext()) {
         String s = iterator.next();
         System.out.println(s);
     }
    
     //方法三:JDK5引入了foreach循环结构,通过foreach结构遍历List
     for (String s : list) {
         System.out.println(s);
     }
    
  3. Set示例:

     //使用类型安全Set
     Set<String> set = new TreeSet<String>();
    
     set.add("asd");
     set.add("fdf");
     set.add("bxc");
    
     //取出Set元素有两种方法,因为Set是无序的,所以比List少一种遍历方法
     //方法一:Set继承Collection,所以可以使用Iterator遍历
     Iterator<String> iterator = set.iterator();
     while (iterator.hasNext()) {
         String s = iterator.next();
         System.out.println(s);
     }
    
     //方法二:JDK5引入了foreach
     for (String s : set) {
         System.out.println(s);
     }
    
  4. Map示例:

     //使用类型安全的Map -- 因为Map是一个键值对结构,执行两个类型泛型
     Map<String, String> map = new HashMap<String, String>();
    
     map.put("aaa", "111");
     map.put("bbb", "222");
    
     //取出Map元素有两种方法
     //方法一:通过Map的keySet()进行遍历
     Set<String> keys = map.keySet(); // 获得key的集合
     for (String key : keys) {
         System.out.println(key + ":" + map.get(key));
     }
    
     //方法二:通过map的entrySet(),获得每一个键值对。
     Set<Map.Entry<String, String>> entrySet = map.entrySet(); //每个元素都是一个键值对
    
     for (Entry<String, String> entry : entrySet) {
         //通过entry的getKey()和getValue()获得每一个键值对的键和值
         System.out.println(entry.getKey() + ":" + entry.getValue());
     }
    

5、自定义泛型——泛型方法

  1. Java中的普通方法、构造方法和静态方法中都可以使用泛型。方法使用泛型前,必须对泛型进行声明,语法:<T>,T可以是任意字母,但通常必须要大写。<T>通常需放在方法的返回值声明之前。
    例如:

     public static <T> void doxx(T t);
    
  2. 假设有这样一个需求,要求实现指定位置上数组元素的交换,这个数组中的元素可能是int型,可能是String类型。

    • 未使用泛型代码如下:

        //String类型数组
        public void changePosition(String[] arr, int index1, int index2) {
            String temp = arr[index1];
            arr[index1] = arr[index2];
            arr[index2] = temp;
        }
        
        //int类型数组
        public void changePosition(int[] arr, int index1, int index2) {
            int temp = arr[index1];
            arr[index1] = arr[index2]; 
            arr[index2] = temp; 
        }
      
        Integer[] arr1 = new Integer[] { 1, 2, 3, 4, 5 };
        changePosition(arr1, 1, 3); 
        System.out.println(Arrays.toString(arr1));
      
        String[] arr2 = new String[] { "aaa", "bbb", "ccc", "ddd" };
        changePosition(arr2, 0, 2);
        System.out.println(Arrays.toString(arr2));
      
    • 使用泛型代码如下:

        // 使用泛型 编写交换数组通用方法,类型可以String 可以 int --- 通过类型
        public <T> void changePosition(T[] arr, int index1, int index2) {
            T temp = arr[index1];
            arr[index1] = arr[index2];
            arr[index2] = temp;
        }
      
        Integer[] arr1 = new Integer[] { 1, 2, 3, 4, 5 };
        changePosition(arr1, 1, 3); 
        System.out.println(Arrays.toString(arr1));
      
        String[] arr2 = new String[] { "aaa", "bbb", "ccc", "ddd" };
        changePosition(arr2, 0, 2);
        System.out.println(Arrays.toString(arr2));
      
    • 两者输出相同,所以利用泛型可以编写通用的Java程序

6、自定义泛型——泛型类

  1. 如果一个类多处都要用到同一个泛型,这时可以吧泛型定义在类上(即类级别的泛型),语法如下:

     public class GenericDao<T>{
         private T field1;
         public void save(T obj){}
         public T getId(int id){}
     }
    

    注意:静态方法不能使用类定义的泛型,应该单独定义泛型。

  2. 示例:

    如果在1.5节中还需要一个需求:倒序数组,那么可以自定义一个泛型类:

     public class ArraysUtils<A> { // 类的泛型
         // 将数组倒序
         public void reverse(A[] arr) {
             /*
              * 只需要遍历数组前一半元素,和后一半元素 对应元素 交换位置
              */
             for (int i = 0; i < arr.length / 2; i++) {
                 // String first = arr[i];
                 // String second = arr[arr.length - 1 - i];
                 A temp = arr[i];
                 arr[i] = arr[arr.length - 1 - i];
                 arr[arr.length - 1 - i] = temp;
             }
         }
     
         public void changePosition(A[] arr, int index1, int index2) {
             A temp = arr[index1];
             arr[index1] = arr[index2];
             arr[index2] = temp;
         }
     }
    

对应泛型类型参数起名 T E K V ---- 泛型类型可以以任意大写字母命名,建议你使用有意义的字母
如:T Template E Element K key V value

7、泛型的高级应用——通配符(wildcard)

  1. 假设有一个方法,接受一个集合,并打印出集合中的所有元素,如下所示:

     // ? 代表任意类型
     public void print(List<?> list) { // 泛型类型 可以是任何类型 --- 泛型通配符
         for (Object string : list) {
             System.out.println(string);
         }
     }
     
     public void demo10() {
         // 打印数组中所有元素内容
         List<String> list = new LinkedList<String>();
    
         list.add("aaa");
         list.add("bbb");
         list.add("ccc");
         print(list);
    
         List<Integer> list2 = new LinkedList<Integer>();
    
         list2.add(111);
         list2.add(222);
         list2.add(333);
    
         print(list2);
     }
    
  2. 只用通配符的情况下很少,通常还需要通过指定上下边界,限制通配符类型范围。
    用法:

    • 指定上边界:

        List<? extends Number> list = new ArrayList<Integer>(); //继承自Number,即指定了泛型的上边界为Number,且包括Number
      
    • 指定下边界:

        List<? super String> list = new ArrayList<Object>(); //是String的父类,即指定了泛型的下边界为String,且包括String
      
    • 上下边界不能同时使用 :
      List<? extends Object super Integer> list = new ArrayList<Object>(); //错误!没有这么写的

  3. 上下边界的应用:

    • 范例一:

    Set中有方法:addAll(Collection<? extends E> c) //将目标集合c的内容添加到当前set ,? extends E 目标集合是E的子类型

    即有如下代码,可以运行成功:

         Set<Number> set = new HashSet<Number>();
         List<Integer> list = new ArrayList<Integer>();
         set.addAll(list); // list 中 Integer 自动转换为 Number
    
    • 范例二:

    TreeSet有构造方法:TreeSet(Comparator<? super E> comparator) //传入E的父类型的比较器

    即有如下代码,可以运行成功:

         Set<Apple> set = new TreeSet<Apple>(); // 默认需要苹果比较器排序 
         
         class FruitComparator implements Comparator<Fruit> {} //水果的比较器
         Set<Apple> set = new TreeSet<Apple>(new FruitComparator()); // 需要Apple比较器 ,传入 Fruit比较器    ,依据构造方法,可行    
    
  4. 错误范例:

     public void add(List<? extends Number> list){
         list.add(100);  //会报错!使用通配符后,不要使用与类型相关的方法。
     }
    

8、泛型通配符的扩展阅读

  1. 关于泛型还可深入研究,在《Effective Java 2th Edition》有相关介绍,感兴趣的同学可以阅读一下。

  2. 最后还介绍一下关于泛型通配符的上下边界问题,什么时候用上边界,什么时候用下边界?
    PECS:producer extends consumer super

    1. 频繁往外读取内容的,适合用上界Extends。

    2. 经常往里插入的,适合用下界Super。

  3. 例如:

     // compile error
     //    List <? extends Fruit> appList2 = new ArrayList();
     //    appList2.add(new Fruit());
     //    appList2.add(new Apple());
     //    appList2.add(new RedApple());
    
     // no error
     List <? super Fruit> appList = new ArrayList();
     appList.add(new Fruit());
     appList.add(new Apple());
     appList.add(new RedApple());
    

http://stackoverflow.com/questions/2723397/what-is-pecs-producer-extends-consumer-super

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容

  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,572评论 18 399
  • (一)Java部分 1、列举出JAVA中6个比较常用的包【天威诚信面试题】 【参考答案】 java.lang;ja...
    独云阅读 7,066评论 0 62
  • 文章作者:Tyan博客:noahsnail.com 1. 什么是泛型 Java泛型(Generics)是JDK 5...
    SnailTyan阅读 771评论 0 3
  • Java 语言支持的类型分为两类:基本类型和引用类型。整型(byte 1, short 2, int 4, lon...
    xiaogmail阅读 1,343评论 0 10
  • 大概直男癌男朋友就是这样 九点半不到 我说我要睡了 他说 好 晚安么么哒 然后继续看他的明日之子了
    MissM_eec7阅读 79评论 0 0