Java中的集合、枚举、泛型【下】

http://blog.csdn.net/silentbalanceyh/article/details/4586627

(本章主要讲解Java里面会遇到的所有集合类以及相关用法,还有JDK1.5里面出来的一些关于集合和算法的新内容,主要是方便我们在开发过程中适当地注意选择,而且本章的内容相对前边章节比较少,但是代码量比较大,但是大部分内容都是个人的一些总结。当然这一个章节会涉及到JDK本身提供的一些数据结构的相关内容,以及最基本几个数据结构的相关性能分析。本来是打算先写Java的IO相关的内容,刚好最近有个项目需要经常性使用到Java里面的东西,所以先提供这一个章节给大家。很多人来Email说Java的基础数据类型一个章节的内容太少了,这点我必须说一句抱歉,因为基础数据类型是我第一次写,后来才想到使用后边这样的方式来写BLOG的内容,基础章节的东西我后边会提供专程的章节来讲解控制流程、以及各种关键字的清单。该系列教程的主要目的是为了整理一份个人使用的开发文档,如果有人转载,请来Email告知,谢谢!在整个过程里面如果发现有笔误的地方希望来Email提点:silentbalanceyh@126.com)

本章目录
1.基本概念
2.常用集合——列表、队列、栈
3.常用集合——Set集合、哈希表
4.泛型、枚举

3.常用集合——Set集合、哈希表
  i.AbstractSet<E>(1.2)类和AbstractMap<K,V>(1.2)相关:
  Set和Map部分需要进行比较方式讲解可能更能够理解里面每一种集合的用法
  1)HashSet<E>(1.2)、HashMap<K,V>(1.2)和HashTable<K,V>(1.0)
  HashSet<E>(1.2)类:
public class HashSet<E> extends AbstractSet<E> implements Set<E>,Cloneable,Serializable
  该类实现了接口Set,由哈希表支持[实际是一个HashMap实例],它不保证集合的迭代顺序,特别是不保证顺序恒久不变,但是允许null元素。对该集合迭代使用的时间和HashSet实例的大小(元素的数量)和底层的HashMap实例(桶的数量)的“容量”的和成比例,如果迭代性在程序设计的时候很重要,不要将初始化容量设置得太高。该实现不是同步的,而且其遍历方法等同于数组,都是快速失败的,若需要对该集合进行同步方法就需要调用下边的方法:
Set s = Collections.synchronizedSet(new HashSet(...));
  HashMap<K,V>(1.2)类:
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>,Cloneable,Serializable
  该类是基于哈希表的Map接口实现,此实现提供了所有可选的映射操作,并且允许使用null值和null键。(除了非同步和允许使用null之外,HashMap类和Hashtable类大致相同。),同样的该类不保证映射的顺序,特别是它不保证该顺序恒久不变。HashMap的实例有两个影响性能的参数:初始容量和加载因子
  容量是哈希表中的桶的数量,初始容量就是哈希表在创建的时候的容量。
  加载因子是哈希表在其容量自动增加之前可以达到多满的一种尺度。
  当哈希表中的条目数超出了加载因子和当前容量的乘积的时候,需要针对哈希表结构进行重建操作,而哈希表内部具有大约两倍的桶数目。
  Hashtable<K,V>(1.0)类:
public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>,Cloneable,Serializable
  该类是HashMap的前身,主要特点和HashMap<K,V>差不多,但是有一点点区别就是:键和值都不允许使用null,而且该类的实现是线程同步的。
  先看几个简单的例子,再来考虑它们相互之间的一些区别和联系
  ——[1]HashSet<E>的基本例子——
package org.susan.java.collection;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
 *一个使用HashSet的简单例子
 **/
public class HashSetDemo {
    public static void main(String args[]){
        HashSet<String> set = new HashSet<String>();
        set.add("A");
        set.add("B");
        set.add("C");
        set.add("B");
        set.add("D");
        printSet(set);
        System.out.println();
        String[] strArray = {"E","F","G"};
        set.addAll(Arrays.asList(strArray));
        printSet(set);
    }
    private static void printSet(Set<String> set){
        Iterator<String> iterator = set.iterator();
        while(iterator.hasNext()){
            System.out.print("[" + iterator.next() + "],");
        }
    }
}
  根据上边这段代码可以得到下边的输出:【*:因为集合本身是无序的,而且迭代不保证顺序,有可能输出的顺序不一样,但是集合元素的内容应该是相同的。】
[D],[A],[B],[C],
[D],[E],[F],[G],[A],[B],[C],
  ——[2]从HashSet<E>里面删除某个元素——
package org.susan.java.collection;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
 *HashSet的删除方法remove(Object obj)
 **/
public class HashSetDemo {
    public static void main(String args[]){
        HashSet<Integer> set = new HashSet<Integer>();
        set.add(new Integer(12));
        set.add(new Integer(33));
        set.add(55);
        set.remove("B");
        set.remove(12);
        printSet(set);
    }
    private static void printSet(Set<Integer> set){
        Iterator<Integer> iterator = set.iterator();
        while(iterator.hasNext()){
            System.out.print("[" + iterator.next() + "],");
        }
    }
}
  先看上边这段代码的结果:
[33],[55],
  【*:与ArrayList的结果有区别的是,该remove方法只有一个参数Object,而且没有重载形式,因为HashSet本身是无序的,所以和ArrayList不一样的是,不支持索引,所以在删除的时候,进行了一次自动拆解箱的操作。注意代码set.remove(12),这个代码传入的是12的原始类型,但是在这个过程里面转换成为了Integer的类型。如果这一段是ArrayList类型而不是HashSet类型,就会抛出异常,因为ArrayList有一个带int类型的重载方法,是删除某个索引位置的元素,而且HashSet的remove方法如果删除失败的话不会抛出异常,因为是返回的一个特殊值false,这一点可以直接在代码中修改set.remove("B")为System.out.println(set.remove("B"))来验证】
  ——[3]HashMap<K,V>的不同遍历——
package org.susan.java.collection;

import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;
import java.util.Map.Entry;
/**
 *使用HashMap的基本例子
 **/
public class HashMapDemo {
    public static void main(String args[]){
        HashMap<Integer, String> map = new HashMap<Integer, String>();
        map.put(1, "One");
        map.put(2, "Two");
        map.put(3, "Three");
        map.put(4, "Four");
        //键遍历
        System.out.println("------Key Iterator------");
        Set<Integer> keys = map.keySet();
        Iterator<Integer> iterator = keys.iterator();
        while(iterator.hasNext()){
            Integer key = iterator.next();
            System.out.print("[Key:" + key + "--");
            System.out.println("Value:" + map.get(key) + "]");
        }
        // 值遍历
        System.out.println("------Value Iterator------");
        Collection<String> values = map.values();
        Iterator<String> vIterator = values.iterator();
        while(vIterator.hasNext()){
            String value = vIterator.next();
            System.out.println("[Value:" + value + "]");
        }
        //映射关系遍历
        System.out.println("------Entry Iterator------");
        Set<Entry<Integer, String>> set = map.entrySet();
        Iterator<Entry<Integer, String>> sIterator = set.iterator();
        while(sIterator.hasNext()){
            Entry<Integer, String> entry = sIterator.next();
            System.out.print("[Key:" + entry.getKey() + "--");
            System.out.println("Value:" + entry.getValue() + "]");
        }
    }
}
  这里提供了三种不同的方式遍历,有几点需要说明:
  • 通过键遍历的时候,键使用的返回是Set<E>,因为键是一个不重复的,所以为了保证键的不重复性,设计的时候使用的Set<E>
  • 通过值遍历的时候,值使用的时候返回是Collection<E>,因为该集合是可以重复的,而且不需要保证值的不重复性
  • 通过关系遍历的时候,使用的格式需要注意Entry是Map的内部类Map.Entry,而且每一个项的类型需要注意
  • 只能通过Map的键去获取值,不能通过Map的值去获取键
  下边是这段程序的输出:
------Key Iterator------
[Key:1--Value:One]
[Key:2--Value:Two]
[Key:3--Value:Three]
[Key:4--Value:Four]
------Value Iterator------
[Value:One]
[Value:Two]
[Value:Three]
[Value:Four]
------Entry Iterator------
[Key:1--Value:One]
[Key:2--Value:Two]
[Key:3--Value:Three]
[Key:4--Value:Four]
  ——[4]Hashtable<K,V>的简单用法——
package org.susan.java.collection;

import java.util.Enumeration;
import java.util.Hashtable;

public class HashtableDemo {
    public static void main(String args[]){
        Hashtable<Integer, String> tables = new Hashtable<Integer, String>();
        tables.put(1, "One");
        tables.put(2, "Two");
        tables.put(3, "Three");
        Enumeration<Integer> iterator = null;
        for(iterator = tables.keys();iterator.hasMoreElements();){
            Integer key = iterator.nextElement();
            System.out.print("[Key:" + key + "--");
            System.out.println("Value:" + tables.get(key) + "]");
        }
    }
}
  上边代码的程序输出:
[Key:3--Value:Three]
[Key:2--Value:Two]
[Key:1--Value:One]
  【*:从上边的代码段可以看出,HashMap和Hashtable没有太多的区别,所以这段代码仅仅提供Hashtable的键遍历方式就可以了,至于遍历是使用for还是使用while,这个可以根据本身的情况自行选择。而且需要注意一点是HashMap在遍历的时候返回的迭代器是Iterator,而Hashtable返回的迭代器是Enumeration。】
  上边的代码段都是1.5过后带泛型的代码段,下边提供一段老版本(1.4或者1.4以下的版本)的写法:
package org.susan.java.collection;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;

public class OleMap {
    public static void main(String args[]){
        HashMap map = new HashMap();
        map.put(1, "One");
        map.put(2, "Two");
        Iterator iterator = map.entrySet().iterator();
        while(iterator.hasNext())
        {
            Entry entry = (Entry)iterator.next();
            System.out.print("[Key:" + entry.getKey() + "--");
            System.out.println("Value:" + entry.getValue()+ "]");
        }
    }
}
  这段代码的输出这里不列出来了,老版本的写法没有泛型概念,就会出现一个类型的强制转换过程,在理解的时候可能简单很多,不过这种写法用1.5编译的时候会存在很多类型检查的警告
  上边已经基本接触了Hashtable和HashMap,总结一下这两个的特点和相互之间的区别,因为HashSet本身不是一个映射表的概念,就可以当作一个普通的Set集合的实现来使用,所以HashSet加入比较是没有太大的实际意义:
  • 返回的迭代器不一样:Hashtable返回的迭代器是Enumeration,而HashMap返回的迭代器是Iterator
  • HashMap不是同步的,而Hashtable是线程同步的,意思就是说前者是线程不安全的,系统开销比较小;后者是线程安全的,开销相对大点点
  • HashTable不允许null键和null值,而HashMap是允许null键和null值
  • HashTable有一个contains(Object value)方法和containsValue(Object value)功能是一模一样
  最后再介绍一下上边代码没有涉及到的常用方法:
HashMap<K,V>类:
boolean containsKey(Object key):如果该映射里面包含了指定的一个键的映射关系返回为true,反之返回为false
boolean containsValue(Object value):如果该映射里面包含了指定的一个或者多个值的映射关系,返回为true,反之返回为false
void putAll(Map<extends K,extends V> m):不单独复制某个映射关系,而是直接把一个映射关系复制到某个映射里面,类似集合中的addAll方法
Hashtable<K,V>类:
boolean contains(Object value):如果该映射里面包含了指定的一个或者多个值的映射关系,返回为true,反之返回为false
boolean containsKey(Object key):如果该映射里面包含了指定的一个键的映射关系返回为true,反之返回为false
boolean containsValue(Object value):如果该映射里面包含了指定的一个或者多个值的映射关系,返回为true,反之返回为false
Enumeration<V> elements():返回哈希表中的值的枚举
Collection<V> values():返回哈希表中的值的集合
Enumeration<K> keys():返回哈希表中的键的枚举
Set<K> keySet():返回哈希表中的键的集合
protected void rehash():重组该hash表。
  这里注意区分返回为键枚举以及键集合的方法和返回为值枚举和值集合的方法【elements() && values();keys() && keySet()】
  2)TreeSet<E>(1.2)类TreeMap(K,V>(1.2)类:
  上边介绍的HashSet和HashMap都是无序的,接下来介绍两个有序的映射:
  TreeSet<E>(1.2)类:
public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>,Cloneable,Serializable
  基于TreeMap的NavigableSet实现,使用元素的自然顺序对元素进行排序,或者根据创建的Comparator进行排序。
  ——[$]TreeSet简单例子——
package org.susan.java.collection;

import java.util.Iterator;
import java.util.TreeSet;
/**
 *TreeSet集合的一个例子
 **/
public class TreeSetDemo {
    public static void main(String args[]){
        TreeSet<Integer> tree = new TreeSet<Integer>();
        tree.add(22);
        tree.add(11);
        tree.add(33);
        tree.add(44);
        System.out.println(tree);
        Iterator<Integer> iterator = tree.iterator();
        while(iterator.hasNext()){
            System.out.print(iterator.next() + ",");
        }
        System.out.println();
        iterator = tree.descendingIterator();
        while(iterator.hasNext()){
            System.out.print(iterator.next() + ",");
        }
    }
}
  上边这段代码演示了TreeSet的基本用法,其实TreeSet有一个很大的特点,不论用什么样子的方式将元素添加到这个集合里面,这个集合里面的元素总是通过某个比较器进行了排序操作的,可以看下边的输出:
[11, 22, 33, 44]
11,22,33,44,
44,33,22,11,
  从上边的结果可以知道,TreeSet本身就是已经排好序的集合,所以正序遍历结果和本身内部的结果一样的顺序,虽然添加的时候是22,11,33,44。至于TreeSet的方法,大部分和Set差不多,这里不做详细介绍,有兴趣的可以去查阅以下API。
  ——[$]TreeMap简单例子——
  介于红黑树(Red-Block tree)的NavigableMap实现,该映射根据键的自然排序进行排序,或者根据键的Comparator进行排序
package org.susan.java.collection;

import java.util.Iterator;
import java.util.TreeMap;
import java.util.Map.Entry;
/**
 *TreeMap的简单例子
 **/
public class TreeMapDemo {
    public static void main(String args[]){
        TreeMap<Integer, String> treeMap = new TreeMap<Integer, String>();
        treeMap.put(2, "Two");
        treeMap.put(3, "Three");
        treeMap.put(1, "One");
        treeMap.put(4, "Four");
        Iterator<Entry<Integer, String>> iterator
            = treeMap.entrySet().iterator();
        while(iterator.hasNext()){
            Entry<Integer, String> entry = iterator.next();
            System.out.print("[Key:" + entry.getKey() + "--");
            System.out.println("Value:" + entry.getValue()+ "]");
        }
        System.out.println("First Entry:" + treeMap.firstEntry());
        System.out.println("First Key:" + treeMap.firstKey());
        System.out.println("Last Entry:" + treeMap.lastEntry());
        System.out.println("Last Key:" + treeMap.lastKey());
        System.out.println("Floor:" + treeMap.floorEntry(3));
        System.out.println("Ceiling:" + treeMap.ceilingEntry(2));
    }
}
  直接看这段代码的输出:
[Key:1--Value:One]
[Key:2--Value:Two]
[Key:3--Value:Three]
[Key:4--Value:Four]
First Entry:1=One
First Key:1
Last Entry:4=Four
Last Key:4
Floor:3=Three
Ceiling:2=Two
  针对这两个类,也许有些读者看了会去翻阅API,主要做以下的说明,也方便读者查阅相关的API函数:
  这两个类里面多出来的方法大部分来自接口SortedMap、SortedSetNavigableMap以及NavigableSet,有关这四个接口可以查阅第一部分的内容,这里不再重复。
  3)LinkedHashSet<E>类(1.4)以及LinkedHashMap<K,V>类(1.4):
  LinkedHashSet<E>(1.4)类的定义:
public class LinkedHashSet<E> extends HashSet<E> implements Set<E>,Cloneable,Serializable
  具有可预知迭代顺序的Set接口的哈希表和链表列表的实现,为一个组合实现,该实现和HashSet的区别在于,此列表维护的一个运行于所有条目的双重链表列表。此列表定义了迭代顺序,即按照将元素插入到集合中的顺序进行迭代,注:插入顺序不受在Set中重新插入的元素的影响。【*:该实现也不是同步的,也就是不是线程安全的】
  有时候需要针对某些集合进行简单的排序,不能像HashSet一样杂乱无章的排序,又不引起与TreeSet关联的成本增加,这种情况下使用该集合是一种不错的方式。
  LinkedHashMap<K,V>类的定义【这个就不做重复解释了,读者应该可以一目了然】
  ——[$]简单的LinkedHashSet和LinkedHashMap的综合例子——
package org.susan.java.collection;

import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map.Entry;

public class LinkedHashSetDemo {
    public static void main(String args[]){
        LinkedHashSet<Integer> hashSet = new LinkedHashSet<Integer>();
        LinkedHashMap<Integer, String> hashMap = new LinkedHashMap<Integer, String>();
        hashSet.add(13);
        hashSet.add(24);
        hashSet.add(22);
        hashSet.add(44);
        hashSet.add(45);
        hashSet.add(44);
        forint i = 0; i < hashSet.size();i++ ){
            hashMap.put((i+1), hashSet.toArray()[i].toString());
        }
        System.out.println(hashMap);
        Iterator<Entry<Integer, String>> iterator = hashMap.entrySet().iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }
}
  这段代码直接创建一个LinkedHashSet集,然后把LinkedHashSet里面的每一项作为LinkedHashMap的每一项的值,最终输出,读者可以仔细分析该概念代码段:
{1=13, 2=24, 3=22, 4=44, 5=45}
1=13
2=24
3=22
4=44
5=45
  ii.Arrays,ArrayDeque(1.6)
  1)ArrayDeque(1.6)类定义:
public class ArrayDeque<E> extends AbstractCollection<E> implements Deque<E>,Cloneable,Serializable
  Deque接口的大小可以改变数组的实现,数组双端队列是没有容量限制的;它们可以根据需要增加以支持使用。这个类不是线程安全的;在没有外部同步时,它们不支持多个线程的并发访问。而且该接口禁止null元素,而且此类在作用于堆栈的时候比Stack快,在作用于队列的时候比LinkedList也快很多。该类实现了Collection<E>接口和Iterator<E>接口的所有可选方法。
  【*:扩展AbstractCollection和实现Deque接口,允许动态双端队列,可以用迭代器,并且提供了先进后出的方法,可以用于模拟stack。而Stack是Vector的一个子类,它实现了标准的后进先出堆栈。】
  ——[$]提供一个概念说明的例子——
package org.susan.java.collection;

import java.util.ArrayDeque;
import java.util.Iterator;
/**
 *ArrayDeque的使用
 **/
public class CollectionDequeDemo {
    public static void main(String args[]){
        ArrayDeque<String> queueArrayDeque = new ArrayDeque<String>();
        queueArrayDeque.add("A");
        queueArrayDeque.add("B");
        queueArrayDeque.offer("C");
        queueArrayDeque.offer("D");
        queueArrayDeque.add("E");
        queueArrayDeque.addFirst("0");
        queueArrayDeque.addLast("F");
        queueArrayDeque.offerFirst("-A");
        queueArrayDeque.offerLast("G");
        Iterator<String> iterator = queueArrayDeque.descendingIterator();
        while(iterator.hasNext()){
            System.out.print(iterator.next() + ",");
        }
        System.out.println();
        System.out.println(queueArrayDeque);
        System.out.println("Peek:" + queueArrayDeque.peek());
        System.out.println("Peek First:" + queueArrayDeque.peekFirst());
        System.out.println("Peek Last:" + queueArrayDeque.peekLast());
        queueArrayDeque.push("Push");
        System.out.println(queueArrayDeque);
        System.out.println("Remove First:" + queueArrayDeque.removeFirst());
        System.out.println("Remove Last:" + queueArrayDeque.removeLast());
        System.out.println(queueArrayDeque);
        System.out.println("Poll:" + queueArrayDeque.poll());
        System.out.println("Poll First:" + queueArrayDeque.pollFirst());
        System.out.println("Poll Last:" + queueArrayDeque.pollLast());
        System.out.println(queueArrayDeque);
    }
}
  先看输出:
G,F,E,D,C,B,A,0,-A,
[-A, 0, A, B, C, D, E, F, G]
Peek:-A
Peek First:-A
Peek Last:G
[Push, -A, 0, A, B, C, D, E, F, G]
Remove First:Push
Remove Last:G
[-A, 0, A, B, C, D, E, F]
Poll:-A
Poll First:0
Poll Last:F
[A, B, C, D, E]
  这里详细解释一下这段代码:
  • 首先是构建ArrayDeque,在添加里面的元素的时候,分别使用了add/addFirst/addLast/offer/offerFirst/offerLast方法,不要被迷惑的是输出的第一行是逆序的,因为使用了descendingIterator();
  • 构建好了过后进行逆序输出,可以看到进行这种方式的构建过后的列表为:[-A, 0, A, B, C, D, E, F, G],注意:push方法也可以添加,而且是添加到队首
  • 然后调用一系列Peek方法peek/peekFirst/peekLast,Peek方法仅仅取出需要的元素,不进行任何“破坏性”操作
  • 接着使用push方法将一个元素“Push”压入到ArrayDeque里面
  • 接着进行remove操作,先移除第一个元素“Push”,然后移除最后一个元素“G”,移除过后的列表为:[-A, 0, A, B, C, D, E, F]
  • 最后一轮是poll的系列操作poll/pollFirst/pollLast,该方法和Peek方法不同的是,不仅仅取出元素,而且移除该元素,所以最后剩下[A, B, C, D, E]
  • 遗漏了一个pop()方法,该方法等效于removeFirst()
  掌握集合部分总结:
  [1]集合接口主要分为CollectionSetListMap,其中Set和List是Collection的子接口
  [2]集合的迭代器主要有Enumeration和Iterator,这两种迭代器可以这样理解,一种是老版本常用的迭代方法,一种是现在通用的迭代器,迭代器在遍历的时候针对List类型不仅仅可以顺序遍历,而且可以逆序遍历
  [3]集合里面的大部分方法为add、remove、get、set,这些方法与List相关的是以索引为基础,以Set相关的以元素为主,以Map相关的可以以键为主、实体为主或者值为主
  [4]特殊的数据结构可能存在方法push、peek、poll和pop等方法
  [5]针对双端队列还存在head前缀tail前缀的方法,而且获取某些集合的子集合的时候可以使用sub前缀方法
  [6]所有的边界规则都差不多:索引以0为起始,一般下边界是包括边界值,上边界不包括边界值,有些方法可以提供参数来决定是否包含
  [7]还存在有些类型提供了下列前缀方法:ceil/floor/lower/higher,以及后缀方法:First/Last等,这些可以根据方法名进行理解
  [8]大部分的集合遍历方式都可以通过while、for、和JDK 1.5里面的增强for语句进行遍历
  [9]注意迭代器的next()和hasNext()方法,这两种方法的意义以及在每一次迭代的时候迭代器位置的移动
  [10]进行运算操作的时候,一般使用Collections和Arrays,一个是针对Collection接口,一个是针对数组[]类型,而且在相互转换的时候,大部分格式为:toArray()转成数组或者asList()转换成Collection
  [11]每一种集合的排序规则不一样,有些是可排序的,有些是不可排序的,而且是否允许null的问题需要详细记忆
  [12]哪些集合是线程安全的、那些集合是非线程安全的,也需要详细记忆
  这里没有提及到的集合,一般都是作为编程过程的特殊用途的,后边的章节我会写道的,包括使用java.util.concurrent包里面的内容进行并发编程,这里未涉及
  以上代码的下载地址:svn checkout http://susanjavastudy.googlecode.com/svn/trunk/ susanjavastudy-read-only【使用SVN可下载,包名可以根据代码里面的包查找,工程IDE为Eclipse,里面还有些部分的代码是类和对象一个章节的,所有代码都是用完整的格式以及代码输出都写在文章里面主要是方便初学者,有一定基础的读者不需要看代码输出应该就可以了解里面的内容】

4.泛型、枚举
  i.枚举:
  在JDK 1.5里面出现了一个新的关键字就是enum,使用这个关键字创建的对象集合称为枚举。在编程过程中可以将每个指定的值看作是哪个类的实例,这样就提供了指定的证书集合无法提供的编译时类型安全。
  1)构造函数、方法和变量
  ——[$]先看一个Enum简单的例子——
package org.susan.java.enumeration;

public class SizeEnum {
    enum Size{
        Small,
        Medium,
        Large
    }
    public static void main(String args[]){
        for(Size s: Size.values()){
            helper(s);
        }
    }
    private static void helper(Size s){
        System.out.println("Size value:" + s);
    }
}
  上边这段代码的输出为:
Size value:Small
Size value:Medium
Size value:Large
  上边是使用enum关键字的一个最典型的例子,在使用enum关键字创建新的枚举类型的时候,实际上是在创建java.lang.Enum类的子类,其中,枚举类型符合通用模式Class Enum<E extends Enum<E>>,E表示枚举的名称。枚举类型的每一个值都对应protected Enum(String name,int ordinal)构造函数中,这里的每一个值都被转换称为了一个字符串,上边的代码enum Size { Small, Medium, Large }等同于下边的代码段:
new Enum<Size>("Small",0);
new Enum<Size>("Medium",1);
new Enum<Size>("Large",2);
  不必将构造函数的使用限制为简介Enum构造函数的使用,在使用enum关键字的时候,将创建Enum的子类。也就是说,在定制枚举的时候使用enum关键字,而不使用new关键字,如果需要自己定制构造函数可以使用下边的方式:
  ——[$]自定义构造函数——
package org.susan.java.enumeration;

enum Apple{
    A(10),B(9),C(12),D(15),E(8);
    private int price;
    Apple(int p){
        this.price = p;
    }
    int getPrice(){
        return this.price;
    }
}

public class AppleEnumDemo {
    public static void main(String args[]){
        Apple apple = Apple.B;
        System.out.println(apple.getPrice());
        System.out.println(apple);
        for( Apple a: Apple.values()){
            System.out.println(a + " costs:" + a.getPrice() + " cents.");
        }
    }
}
  上边就是枚举里面重新定义构造函数的一个简单例子,输出为:
9
B
A costs:10 cents.
B costs:9 cents.
C costs:12 cents.
D costs:15 cents.
E costs:8 cents.
  注意enum里面的一些写法,普通的写法没有什么区别,主要是枚举量的区别,枚举在使用的时候和普通类的使用区别不太大,但是注意代码Apple apple = Apple.B,这是枚举进行初始化的方式,可以这样理解。
  2)预定义方法、特定常量的类主体
  预定义的方法:
  因为用户在定义枚举类型的时候,用户定义的枚举类型是Enum类型的子类型,下边是所有Enum类的方法清单:
  • public int compareTo(e)
  • public boolean equals(Object o)
  • public final Class<E> getDeclaringClass()
  • public int hashCode()
  • public String name()
  • public int ordinal()
  • public String toString()
  • public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name)
  很多方法看起来都似曾相似,而其他方法都是特性于Enum类型的,有些方法是典型的Object的方法,name()方法和ordinal()返回的是构造函数的参数,toString()返回名称。
  [1]getDeclaringClass()方法类似于Object的getClass()方法,但是该方法的返回值和Object的getClass()的返回值不一样
  [2]valueOf()方法是静态方法,该方法允许从类型的名称中创建枚举的值
  特定常量的类主体:
  该特性是Java中的enum关键字支持的特性,不过使用的时候会有一定的限制,该特性提供一个简单的例子:
  ——[$]特定常量的类主体——
package org.susan.java.enumeration;
/**
 *特定常量的类主体
 **/
public class SimpleEnum {
    enum Size{
        Small{
            public double getPricingFactor(){
                return 0.8;
            }
        },
        Medium,
        Large,
        ExtraLarge{
            public double getPricingFactor(){
                return 1.2;
            }
        };
        public double getPricingFactor(){
            return 1.0;
        }
    }
    public static void main(String args[]){
        for(Size size: Size.values()){
            double d = size.getPricingFactor();
            System.out.println(size + " is :" + d);
        }
    }
}
  下边是这段代码的输出:
Small is :0.8
Medium is :1.0
Large is :1.0
ExtraLarge is :1.2
  【*:可以这样讲,Enum本身是可以定制它的每一个环节的,但是在定制过程需要一定的技巧和限制,我们也可以自定义一些关于枚举的内容,枚举有时候和类是差不多的用法】
  3)EnumSet<E>(1.5)和EnumMap<K,V>(1.5)集合
  EnumSet<E>(1.5)类:
public abstract class EnumSet<E extends Enum<E>extends AbstractSet<E> implements Cloneable,Serializable
  这是为枚举类型设计的专用的Set实现,这里面所有的值都来自单个枚举类型,该枚举类型在创建Set的时候显示或者隐式地指定,枚举Set在内部表示为位向量,此表示形式紧凑而且高效,该类的空间和时间的性能也很好,其批量操作也是很快的。Iterator方法返回的迭代器按自然顺序遍历这些元素(顺序应该为枚举常量的顺序)。注意和其他的Set不一样的在于该类的Iterator迭代器不是快速失败的,返回是弱一致的:不会抛出ConcurrentModificationException,也不一定显示在迭代进行时发生任何Set修改的效果。
  该类不允许插入null元素,插入null会抛NullPointerException,但是测试是否出现null或者移除null不会抛异常。而且和其他集合一样,这个类也是线程不同步的,而且在处理同步的时候和原来的集合不一样,需要使用下边代码:
    Set<MyEnum> s = Collections.synchronizedSet(EnumSet.noneOf(MyEnum.class));
  【*:所有基本操作都是在固定的时间内执行,虽然不保证,但是这种方式的效率很可能比HashSet快,如果参数也是一个枚举Set,可能批量操作起来更加快。】
  ——[$]一个EnumSet的例子——
package org.susan.java.enumeration;

import java.util.Arrays;
import java.util.EnumSet;
import java.util.Set;
/**
 *EnumSet的例子
 **/
public class EnumSetDemo {
    enum Week{
        Monday,
        Tuesday,
        Wednesday,
        Thursday,
        Friday,
        Saturday,
        Sunday
    }
    public static void main(String args[]){
        EnumSet<Week> week = EnumSet.noneOf(Week.class);
        week.add(Week.Monday);
        week.add(Week.Tuesday);
        week.add(Week.Wednesday);
        week.add(Week.Thursday);
        week.add(Week.Friday);
        week.add(Week.Saturday);
        week.add(Week.Sunday);
        showEnum(week,"First Enum");
        EnumSet<Week> set = EnumSet.allOf(Week.class);
        showEnum(set, "EnumSet.allOf");
        EnumSet<Week> set2 = EnumSet.of(Week.Monday,Week.Friday,Week.Thursday);
        showEnum(set2, "EnumSet.of");
        EnumSet<Week> set3 = EnumSet.range(Week.Monday, Week.Wednesday);
        showEnum(set3, "EnumSet.range");
    }
    private static void showEnum(Set<Week> set,String title){
        System.out.println(title + ":");
        System.out.println(Arrays.toString(set.toArray()));
    }
}
  上边代码段的输出为:
First Enum:
[Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday]
EnumSet.allOf:
[Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday]
EnumSet.of:
[Monday, Thursday, Friday]
EnumSet.range:
[Monday, Tuesday, Wednesday]
  针对EnumSet进行简单说明一下:
  • 上边如果使用range方法的时候起点大于终点会抛出异常:Exception in thread "main" java.lang.IllegalArgumentException: Sunday > Wednesday
  • 下边是EnumSet的方法列表:
    static <extends Enum<E>> EunmSet<E> allOf(Class<E> elementType):创建一个包含了指定元素类型的所有元素的枚举Set
    EnumSet<E> clone():返回Set的副本
    static <extends Enum<E>> EunmSet<E> complementOf(EnumSet<E> s):创建一个其元素类型与指定Set相同的枚举Set,最初包含了指定Set中不包含此类型的所有元素
    static <extends Enum<E>> EunmSet<E> copyOf(Collection<E> c):创建一个从指定的Collection初始化的枚举Set
    static <extends Enum<E>> EunmSet<E> copyOf(EnumSet<E> s):创建一个其元素类型与指定枚举Set相同的枚举Set,最初包含相同元素(若有元素的话)
    static <extends Enum<E>> EunmSet<E> noneOf(Class<E> elementType):创建一个具有指定元素类型的空枚举Set
    static <extends Enum<E>> EunmSet<E> of(E e):创建一个最初包含了指定元素的set
    static <extends Enum<E>> EunmSet<E> of(first,E... rest):创建一个最初包含指定元素的枚举类型
    static <extends Enum<E>> EunmSet<E> range(E from,to):创建一个最初包含了两个指定端点所定义的范围内的所有元素的枚举Set
  EnumMap<K,V>(1.5)类:
public class EnumMap<extends Enum<K>,Vextends AbstractMap<K,V> implements Serializable,Cloneable
  与枚举类型专用的Map实现,枚举映射所有键都必须来自单个枚举类型,其他的操作和普通的Map差不多,注意该类的同步方法:
Map<EnumKey,V> m = Collections.synchronizedMap(new EnumMap<EnumKey,V>(...));
  该类这里提供一个简单的例子:
package org.susan.java.enumeration;

import java.util.EnumMap;
import java.util.Map;

public class EnumMapDemo {
    enum Size{
        Small,
        Medium,
        Large;
    }
    public static void main(String args[]){
        Map<Size, String> map = new EnumMap<Size,String>(Size.class);
        map.put(Size.Small"Small Value");
        map.put(Size.Medium"Medium Value");
        map.put(Size.Large"Large Value");
        for(Map.Entry<Size, String> entry: map.entrySet()){
            helper(entry);
        }
    }
    private static void helper(Map.Entry<Size, String> entry){
        System.out.println("Map entry:" + entry);
    }
}
  上边代码的输出为:
Map entry:Small=Small Value
Map entry:Medium=Medium Value
Map entry:Large=Large Value
  4)关于枚举的类型转换问题:
  既然枚举类型存在,有时候难免需要将枚举类型和其他数据类型进行相关的转换,最后提供一个类型转换的例子:
  ——[$]枚举和字符串——
package org.susan.java.enumeration;
/**
 *枚举和字符串的相互转换,直接使用枚举的字面量
 **/
public class EnumConvertor {
    enum Week{Monday,Tuesday,Wednesday,
        Thursday,Friday,Saturday,Sunday}
    public static Week getWeek(String strWeekName){
        Week week;
        week = Week.valueOf(strWeekName);
        return week;
    }
    public static void main(String args[]){
        Week week = getWeek("Monday");
        System.out.println(week.name());
        System.out.println(week);
    }
}
  这段代码的输出为:
Monday
Monday
  枚举转字符串一般很好转,但是反过来可能麻烦一点。如果上边这段代码传入的字符串不存就会抛出下边异常:
Exception in thread "main" java.lang.IllegalArgumentException: No enum const class org.susan.java.enumeration.EnumConvertor$Week.Mnday
  网上有人提供了这样一段代码可以参考一下可能更加方便使用:
package org.susan.java.enumeration;

public class CustomEnum {
    public enum Size{
        Small,
        Mediam,
        Big,
        Large;
        public static int toInt(Size size){
            return size.ordinal();
        }
        public static String toString(Size size){
            return size.name();
        }
        public static Size fromString(String str){
            return Size.valueOf(str);
        }
        public static Size fromInt(int num){
            switch (num) {
                case 0: { return Size.Small; }
                case 1: { return Size.Mediam; }
                case 2: { return Size.Big; }
                case 3: { return Size.Large; }
                default:return null;
            }
        }
    }
    public static void main(String args[]){
        System.out.println(Size.toInt(Size.Small));
        System.out.println(Size.toString(Size.Large));
        System.out.println(Size.fromInt(1));
        System.out.println(Size.fromString("Big"));
    }
}
  上边测试代码的输出为:
0
Large
Mediam
Big
  这也是一种办法,但是不一定很好,毕竟这只是一种策略,为了使得我们在操作过程可以直接针对Enum类型和Int还有String相互之间进行转换。真正项目开发过程的时候使用转换是有必要的,针对Hibernate这种专程的ORM框架,可以写一个专程的转换器进行类型转换然后映射到数据库里面去。
  最后总结一下枚举部分的内容:
  • 枚举不能有public的构造函数,这样做可以保证客户代码没有办法新建一个enum的类型
  • 所有的枚举值都是public、static、final的,注意这一点只是针对枚举值,但是我们可以和在普通类里面定义变量一样定义其他任何非枚举的变量,这些变量可以用任意的修饰符
  • Enum默认实现了java.lang.Comparable接口
  • Enum重写了toString()方法,因此可以直接使用Size.Large.toString()默认返回字符串“Large”
  • Enum提供了一个valueOf方法,这个方法和toString()方法是相对应的,调用valueOf("Large")将返回Size.Large,因此如果重写toString()方法的时候需要注意这一点,一般来说应该相对应的重写valueOf()方法
  • Enum还提供了values方法,这个方法使你能够方便地遍历所有的枚举值
  • Enum还有一个oridinal的方法,这个方法返回枚举值在枚举类中的顺序,这个顺序根据枚举值生命的顺序而定,所以第一句输出为0;
  ii.泛型
  1)基本概念:
  泛型(Generic Type或Generics)是对Java语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类。可以把类型参数看做是使用参数化类型时指定的类型的一个占位符,就像方法的形式参数是运行时传递的占位符一样,泛型的体现主要是在集合框架里面可以看到,JCF里面应该是1.5里面使用泛型最多的地方。Java语言引入泛型是一个较大的功能增强,不仅语言、类型系统和编译器有了大变化,以支持泛型,而且类库也进行了大翻修,所以许多重要的类,比如集合框架,都已经成为了泛型化的了,使用泛型的优点为:
  • 类型安全:
    泛型的主要目标是提高Java程序的类型安全。通过知道使用泛型定义的变量的类型限制,编译器可以在一个高得多的程度上验证类型假设。没有泛型,这些假设就只存在于程序员的脑海中。Java程序中的一种流行技术是定义这样的集合,即它的元素或键是功能类型的,比如“_列表”。通过在变量声明中捕获这一附加的类型信息,泛型允许编译器实施这些附加的约束,类型错误现在就可以在编译时被捕获了,而不是在运行时才来进行检测操作。
  • 消除强制类型转换:
    泛型的一个附带的好处是,消除源代码中的许多强制类型转换,这使得代码更加可读,而且减少了出错的机会。比较两段代码:
    不使用泛型的代码段:
    List li = new ArrayList();
    li.add(new Integer(3));
    Integer i = (Integer)li.get(0);
    使用泛型:
    List<Integer> li = new ArrayList<Integer>();
    li.add(new Integer(3));
    Integer i = li.get(0);
  • 潜在的性能收获:
    泛型为较大的优化带来可能。在泛型的初始实现中,编译器将类型转换插入到各种字节码中。但是更多类型信息可用于编译器这一事实,为未来版本的JVM也带来了优化可能。
  泛型本质上是提供类型的“类型参数【外露参数】”,它们也被成为参数化类型(Parameterized Type)参量多态(Parametric Polymorphism),用GJ(Generic Java)编写的程序看起来和普通的Java基本相同,只不过多了一些参数化的类型同时少了一些类型转换。实际上,GJ程序也是首先被转化秤一般的不带泛型的Java程序后再进行处理的,编译器自动完成从GenericJava到普通Java的翻译,具体转化过程分为以下几个阶段:
  • 将参数化类型中的类型参数“擦除”(erasure)掉;
  • 将类型变量用“上限(Upper bound)”取代,通常情况下这些上限是Object。这里的类型变量是指实例域,本地方法域,方法参数以方法返回值用来标记类型信息的“变量”。
  • 添加类型转换并插入“桥方法”(bridge method),以覆盖可以正常的工作
  转化后的程序和没有引入的程序程序员不得不手工完成转换的程序是非常一致的,下边针对GJ的特点做一个简要的总结:
  • 类型安全:泛型的一个主要目标是提高Java的类型安全,使用泛型可以使编译器知道变量的类型限制,进而可以在更高程度上验证类型假设。如果没有泛型,那么类型的安全性主要由程序员来宝物,这显然不如带有泛型的安全性高。
  • 消除强制类型转换:泛型可以消除源代码中的许多强制类型转换,这样使得代码更加可读,并且减少出错的机会
  • 向后兼容:支持泛型的Java编译器可以用来编译经过泛型扩充的Java程序(GJ程序),但是现有的没有使用泛型扩充的Java程序仍然可以用这些编译器来编译
  • 层次清晰,比较规范:无论被编译的源程序是否使用泛型扩充,编译生成的字节码均可以被虚拟机接受执行。就是说不论编译器输入的GJ程序还是一般的Java程序,经过编译后的字节码都遵循了Java虚拟机规范里面针对字节码的要求,也可以说:泛型对Java虚拟机是透明的
  • 性能收益:目前来讲GJ编写的代码和一般的Java代码在效率上是很接近的,但是泛型可以进一步优化
  API中的类型定义:
K——键、比如映射的键
V——值,比如_和_的内容,或者_中的值
E——异常类型
T——泛型
  ——[1]简单的泛型例子——
package org.susan.java.enumeration;

class GenClass<T>{
    T ob;
    GenClass(T o){
        this.ob = o;
    }
    T getOb(){
        return this.ob;
    }
    void showType(){
        System.out.println("Type of T is " + ob.getClass().getName());
    }
}

public class GenDemo {
    public static void main(String args[]){
        GenClass<Integer> iObject = new GenClass<Integer>(88);
        iObject.showType();
    }
}
  这段代码的输出为:
Type of T is java.lang.Integer
  【*:上边定义了一个带泛型的类,在使用的时候T可以替换为所有我们需要的类型,这种定义方式类似C++里面的模板定义。】
  2)深入理解泛型:
  【*:这里仅仅提供一般初学者和一些开发人员能够理解的泛型知识】
  [1]数据类型转换:
  在前边《Java的类和对象》章节里面已经说过了,Java里面存在类的向下转型向上转型,而且Java语言里面有时候比较容易因为类型的检查引发转型的问题,因为很多时候需要不断地向下转型,这种方式往往增加了JVM的一部分运行时的开销。实际上程序中每个向下转型针对ClassCastException都是潜在的危险,应当尽量避免它们,但是在写程序的过程,这种转型往往没有办法避免,即使特别优良的设计也是会存在的。其实JDK 1.4到JDK 1.5的升级过程泛型是一个大的跨度,这种跨度使得编写程序更加规范,其实在前边讲解集合的时候已经使用了很多泛型的编程格式了,提供的很多代码Demo都是泛型的。这里再提供一段简单的泛型使用代码:
  ——[$]使用泛型的List——
package org.susan.java.generic;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
 *泛型List的使用,结合了JDK 1.4和JDK 1.5的两种用法,但是在JDK 1.4编译器中下边的方式编不通
 **/
public class ListGenericDemo {
    public static void main(String args[]){
        List list = new ArrayList();
        List<String> genList = new ArrayList<String>();
        list.add("List 1");
        list.add("List 2");
        genList.add("Generic List 1");
        genList.add("Generic List 2");
        System.out.println("No Generic:");
        Iterator iterator = list.iterator();
        while(iterator.hasNext()){
            System.out.print("[" + (String)iterator.next() + "],");
        }
        System.out.println("/nGeneric:");
        Iterator<String> genIterator = genList.iterator();
        while(genIterator.hasNext()){
            System.out.print("[" + genIterator.next() + "],");
        }
    }
}
  上边这段代码的输出为:
No Generic:
[List 1],[List 2],
Generic:
[Generic List 1],[Generic List 2],
  这里使用了两种不同的方式【*:在最原始的定义的List参数里面,不使用泛型的时候都是直接使用Object类型,在传入String的时候会进行向下转型,一般情况不会出现转型失败的情况,但是使用了泛型过后,就上边的genList代码段里面,只能添加String对象,如果使用了其他类型的元素这里编译器就会直接报错,这种做法消除了JVM本身的类型转换。】
  [2]基本类型限制
  Tiger【Java 5.0的版本号】中的类型变量的限制之一就是:必须使用引用类型进行实例化,基本类型不起作用,就是说不能使用:List<int> list = new ArrayList<int>();这种定义方式。也就是说在泛型使用的过程里面,如果要针对基本类型进行泛型使用,必须要进行包装,就是Boxing操作,比如把int包装成为Integer。
  这里参考以下泛型的类型限制:JSR-14中:
  • 不应在静态成员中引用封闭类型参数
  • 不能用基本类型实例化泛型类型参数
  • 不能在数据类型转换或instanceof操作中使用“外露”类型参数
  • 不能在new操作符中使用“外露”的类型参数
  • 不能在类定义的implements或extends字句中使用“外露”类型参数
  这里简单透露一点JVM的原理:JVM本身不支持泛型,在编译器进行泛型代码的编译的时候,其实是使用了“擦除”功能,就是JVM在编译带泛型的代码的时候,实际上对带泛型的代码进行了类型检查,然后“擦除”泛型代码中的类型支持,转换为普通类型进行编译。这里有一个新概念成为“外露”类型——单独出现而不是位于某个类型中的类型参数如(List<T>中的T)针对T类型而言,T的上界就是Object。这一项技术的功能极其强大,我们可以使几乎所有泛型类型的精度增强,但是与JVM兼容。
  ——[$]静态成员中的封闭类型参数——
package org.susan.java.generic;

public class StaticListGenericDemo<T> {
    static void metho1(){
        //T t;
    }
    static class StaticClass{
        //StaticListGenericDemo<T> t;
    }
}
  这里被注释掉的代码是不能通过JVM编译的,因为编译器完全禁止在静态方法和静态内部类中引用封闭类型参数,下边几种情况这里就不做解释了,T在整个过程里面是不应该作为外露类型来使用。
  ——[$]提供一个instanceof操作中的“外露参数”——
package org.susan.java.generic;

import java.util.Hashtable;

interface Registry{
    public void register(Object o);
}

class C<T> implements Registry{
    int counter = 0;
    Hashtable<Integer,T> values;
    public C(){
        values = new Hashtable<Integer,T>();
    }
    public void register(Object o){
        values.put(new Integer(counter), (T)o);
        counter++;
    }
}
  【*:这段代码编译没有任何问题,但是在(T)o地方会有一个警告,虽然这些警告本身没有什么,事实上,它们会使得诊断代码变得极为困难。在以前的代码中,我们认为如果对实例 C<JFrame> 调用 register("test") ,会发出 ClassCastException 。但并非如此;计算将继续,就仿佛数据类型转换成功了一样,然后在进一步进行计算时发出错误,或者更糟:用遭破坏的数据完成计算,但不向外发出任何错误信号。同样,对“外露”类型参数的 instanceof 检查将在编译时产生“unchecked”警告,而且检查将不会如期在运行时进行。
  [3]泛型的构造函数:
  在定义泛型的构造函数的时候,要解决这一个问题,需要一定的操作:
  • 要求类型参数的所有实例化都包括不带参数的构造函数
  • 只要泛型类的运行时实例化没有包括所需要的构造函数,就抛异常
  • 修改语言的语法以包括更加详细的类型参数界限
  ——[$]定义一个带泛型构造函数——
package org.susan.java.generic;

class GenGons{
    private double val;
    <T extends Number> GenGons(T arg){
        val = arg.doubleValue();
    }
    void showVal(){
        System.out.println("Val: " + val);
    }
}

public class GenGonsDemo {
    public static void main(String args[]){
        GenGons genOne = new GenGons(100);
        GenGons genTwo = new GenGons(123.5F);
        genOne.showVal();
        genTwo.showVal();
    }
}
  这段程序输出为:
Val: 100.0
Val: 123.5
  [4]泛型中通配符的使用:
  通配符——使用一个?标识类型参数,是一种表示未知类型的约束方法。通配符并不包含在最初的泛型设计中,从形成JSR14到发布其最终版本之间的五年时间内完成了设计添加到泛型中。通配符在泛型的使用中具有重要的意义,它们为一个泛型类所指定的类型集合提供了一个有用的类型范围。对泛型的ArrayList而言,对于任意类型T,ArrayList<?>类型是ArrayList<T>的超类型,但是这些超类型在执行类型推断方面是不起作用的。通配符类型List<?>、原始List和具体List<Object>都不相同。如果说变量X具有List<?>类型,标识存在一些T类型,其中x是List<T>类型,x具有相同的结构,尽管我们不知道其元素的具体类型。这并不代表它具有任意内容,而是指我们并不了解内容的类型限制是什么 — 但我们知道存在 某种限制。另一方面,原始类型 List 是异构的,我们不能对其元素有任何类型限制,具体类型 List<Object> 表示我们明确地知道它能包含任何对象(当然,泛型的类型系统没有 “列表内容” 的概念,但可以从 List 之类的集合类型轻松地理解泛型)。通配符在类型系统中的作用部分来自其不会发生协变(covariant)这一特性。数组是协变的,因为 Integer 是 Number 的子类型,数组类型 Integer[] 是 Number[] 的子类型,因此在任何需要 Number[] 值的地方都可以提供一个 Integer[] 值。另一方面,泛型不是协变的, List<Integer> 不是 List<Number> 的子类型,试图在要求 List<Number> 的位置提供 List<Integer> 是一个类型错误。这不算很严重的问题,也不是所有人都认为的错误,但泛型和数组的不同行为的确引起了许多混乱。
  ——[$]通配符的使用——
package org.susan.java.generic;

class Status<T extends Number>{
    T[] nums;
    Status(T[] o){
        nums = o;
    }
    double average(){
        double sum = 0.0;
        forint i = 0; i < nums.length; i++)
            sum += nums[i].doubleValue();
        return sum / nums.length;
    }
    boolean sameAvg(Status<?> obj){
        if( average() == obj.average())
            return true;
        return false;
    }
}

public class WildcardDemo {
    public static void main(String args[]){
        Integer inums[] = {1,2,3,4,5};
        Status<Integer> iobj = new Status<Integer>(inums);
        System.out.println("iob average is "+ iobj.average());
        Double dnums[] = {1.1,2.2,3.3,4.4,5.5};
        Status<Double> dobj = new Status<Double>(dnums);
        System.out.println("dob average is "+ dobj.average());
        Float fnums[] = {1.1F,2.2F,3.3F,4.4F,5.5F};
        Status<Float> fobj = new Status<Float>(fnums);
        System.out.println("fob average is "+ fobj.average());
    }
}
  这段程序的输出为:
iob average is 3.0
dob average is 3.3
fob average is 3.300000023841858
  ——[$]返回泛型值的方法——
package org.susan.java.generic;

import java.io.Serializable;

class Base{}
class SubClass extends Base implements Serializable{}
class SubClassTwo extends Base implements Serializable{}

public class TypeInference {
    public static <extends Base> T Method(T t1,T t2){
        return null;
    }
    public static void main(String args[]){
        Base base = Method(new SubClass(), new SubClassTwo());
        Serializable run = Method(new SubClass(), new SubClassTwo());
    }
}
  注意上边返回值的写法
  ——[$]?通配符——
package org.susan.java.generic;

import java.util.ArrayList;
import java.util.List;
/**
 *问号通配符
 **/
public class QuestionDemo {
    private static void testMethod(List<? extends Number> list){}
    public static void main(String args[]){
        List<Object> oList = new ArrayList<Object>();
        List<Integer> iList = new ArrayList<Integer>();
        List<Number> nList = new ArrayList<Number>();
        //testMethod(oList); //这里会出现编译错误
        testMethod(iList);
        testMethod(nList);
    }
}
  从上边的的代码可以知道,?一般和extends以及super关键字进行使用其含义在于传入泛型的类型定义为extends后边的类型的子类,所以上边的注释掉的代码是没有办法通过编译的。
  【*:泛型真正在开发过程程序员需要掌握的是用法,上边讲到的四点都比较深入,而且都是讨论的与JVM处理泛型原理相关的内容,如果没有弄懂没有关系,下边讲“泛型类型捕获”的时候主要提及应用层的相关内容。】
  ——[$]定义一个泛型接口——
package org.susan.java.generic;

interface MinMan<T extends Comparable<T>>{
    T min();
    T max();
}
class MyDemo<T extends Comparable<T>> implements MinMan<T>{
    T[] tValues;
    MyDemo(T[] o){ tValues = o;}
    public T min(){
        T vT = tValues[0];
        forint i = 1; i < tValues.length; i++ )
            if(tValues[i].compareTo(vT) < 0)
                vT = tValues[i];
        return vT;
    }
    public T max(){
        T vT = tValues[0];
        forint i = 1; i < tValues.length; i++ )
            if(tValues[i].compareTo(vT) >  0)
                vT = tValues[i];
        return vT;
    }
}
public class GenericInterface {
    public static void main(String args[]){
        Integer inums[] = {3,6,13,11,45,22,33,21};
        MyDemo<Integer> iob = new MyDemo<Integer>(inums);
        System.out.println("iob Max:" + iob.max());
        System.out.println("iob Min:" + iob.min());
    }
}
  该输出为:
iob Max:45
iob Min:3
  ——[$]泛型的方法重载——
package org.susan.java.generic;
/**
 *一段错误的代码段
 **/
class MyGenDemo<T,V>{
    ob1;
    ob2;
    void set(o){
        this.ob1 = o;
    }
    void set(o){
        this.ob2 = o;
    }
}
class GenDemo<T>{
    T ob;
    GenDemo(){
        this.ob = new T();
    }
}
class Wrong<T>{
    static T ob;
    static T getOb(){
        return ob;
    }
}
  分析上边的代码段就可以发现很多问题:
  • 首先set方法并不能通过这种方式重载,原因很简单,虽然这里使用了通配符T、V,但是这两个“外露”类型在编译器里面为默认为相同的,因为这种情况下两个都属于占位符类型,按照这样的方式就会使得set方法的方法签名对JVM而言是一模一样
  • this.ob = new T()有问题,因为通配符是不能通过这种方式构造的,通配符目前还不能成为Java类型,所以通配符目前是不能实例化的,哪怕是一个类类型。
  • 最后一段代码问题也很严重:因为通配符属于非静态类型,所以不能使用在static的方法或者类定义里面,这就是上边代码段的代码。
  ——[$]泛型Java类——
package org.susan.java.generic;

class GenType<T>{
    ob;
    GenType(ob){
        this.ob = ob;
    }
    getOb(){
        return this.ob;
    }
}

class GenStr<T extends Number>{
    str;
    GenStr(T o){
        this.str = o;
    }
    getStr(){
        return this.str;
    }
}

public class GenTypeDemo {
    public static void main(String args[]){
        GenType<Integer> iObGenType = new GenType<Integer>(99);
        GenStr<Float> fOb = new GenStr<Float>(102.2F);
        System.out.println(iObGenType.getClass().getName());
        System.out.println(fOb.getClass().getName());
    }
}
  根据输出结果可以知道该类型就为我们定义的类型:
org.susan.java.generic.GenType
org.susan.java.generic.GenStr
  3)泛型“类型捕获”
  协变概念【extends和super】
  讲了这么多泛型的内容,相信读者对泛型有一点点了解了,接下来针对泛型里面比较费解的地方进行比较通俗的讲解,泛型的类型捕获是从编译器的级别来说的,当我们定义了泛型的时候,如果使用了? extends T这种格式,编译器遇到一个这样带有通配符的变量的时候,它如何来进行识别操作呢,它会觉得遇到T了过后,一定会有T定义的变量,而对这些T而言一定有一个Class<T>类型。但是它不知道T代表什么,但是JVM会为T定制一个占位符来代替T的类型。占位符被称为特殊通配符的捕获。这种情况下,编译器会为通配符提供一个名字,每个变量声明中出现的一个通配符都会活得JVM的一个捕获,,因此在泛型声明中如果用了public void method(Pointer<?,?> pointer)的话,JVM就会获取两个通配符名称,因为这个时候?也好,T也好,类型是未知的,任意未知的类型的参数在使用的时候相互之间是没有任何关系的。
  【*:泛型的通配符的使用其实在JDK 1.5无疑是最复杂的部分,而且Java编译器产生的一些令人困惑的错误以及很多消息都可能和通配符有关。】
  ? extends Number:这种语法的意思就是传入的泛型的“外露”类型必须是Number的子类,这里重复一下“外露类型”,其实在遇到泛型的时候往往会遇到两个类型,直接定义的类型,一般为Type<T>格式的,然后是外露类型T,这里T就代表了外露类型,这里需要区分这种情况下Type<T>和Type不属于同一个类型。这里需要了解的是泛型的“协变”。
  注意:泛型本身不是协变的
  比如有类型Integer和Number,因为Integer类是Number类的子类,也就是说Integer是一个Number,Integer[]也是一个Number[],按照这样的逻辑,我们也许会觉得泛型也是遵循了这样原理List<Integer>也会是List<Number>的子类,但是这种做法在泛型里面是错误的。这种做法在传入的时候会被编译器定义为类型错误。看一段程序:
  ——[$]协变的概念代码——
package org.susan.java.generic;

import java.util.ArrayList;
import java.util.List;
/**
 *关于泛型协变的概念代码
 **/
public class ChangeGenerice {
    private static void methodOne(List<Number> number){
        System.out.println("One:" + number);
    }
    private static void methodTwo(List<Integer> integer){
        System.out.println("Two:" + integer);
    }
    private static void methodThree(List<? extends Number> number){
        System.out.println("Three:" + number);
    }
    private static void methodFour(List<? super Number> integer){
        System.out.println("Four:" + integer);
    }
    public static void main(String args[]){
        List<Number> nList = new ArrayList<Number>();
        List<Integer> iList = new ArrayList<Integer>();
        List<Object> oList = new ArrayList<Object>();
        methodOne(nList);
        //这里编译报错,因为nList是List<Number>,但是方法接受参数是List<Integer>,它们不存在继承关系,这里也证明了协变的简单
        //methodTwo(nList); 
        methodThree(nList);
        methodFour(nList);
        
        //这里编译报错,iList是List<Integer>,不是List<Number>
        //methodOne(iList);
        methodTwo(iList);
        methodThree(iList);
        //这里编译报错,iList不满足条件List<? super Number>,因为List<Integer>中外露类型Integer不满足Integer super Number
        //methodFour(iList);

        //最后三个编译错误留给读者自己去分析
        //methodOne(oList);
        //methodTwo(oList);
        //methodThree(oList);
        methodFour(oList);
    }
}
  如果需要在1.5的JDK环境里面进行1.4的写法,如果不去掉类型检测可能会报警告:
  【*:上边这段概念代码很好说明了协变过程,同样也能够很好理解泛型里面的extends和super关键字的使用了。关于通配符的使用可能还需要写更多的代码,最好办法是直接去参考集合部分的源代码,是一个不错的学习途径。】
  针对Java泛型做一个简单的总结:
  • 泛型的“外露”类型可以用在类、接口和方法中,一般称为泛型类、泛型接口、泛型方法
  • 泛型的“外露”类型只可以是类类型(包括自定义类)不能简单类型
  • 泛型的“外露”类型可以使用extendssuper关键字,该关键字标识了“外露”类型的界限
  • 泛型不支持直接的类型协变,在协变过程注意泛型的“外露”类型满足的界限的条件
  • 泛型还可以使用通配符进行操作,这种情况可以用来作为类模版
  【*思考小节:泛型在JVM里面无疑为JDK 1.5里面一个重量级的新东西,从这里可以考虑JVM的执行原理。JVM通过使用通配符来进行泛型的编译,最终还是会成为普通类型来进行整体的编译操作,JVM在编译带泛型的代码的时候,会进行“擦除”操作,这种操作是伪装在JVM本身编译器的外部,需要知道一点JVM内部是不支持泛型的,从1.4到1.5的升级是没有进行编译器的重新改写。在通配符非?的使用过程里面,一般的通配符相当于是定义了一个类型,该类型是可以直接作为Java语言里面的类型来进行使用的,在定义这种类型定义里面可以直接使用,该类型在文章里面就是常常提出的“外露”类型。JVM在捕获类型的时候,会针对类型进行检测,检测过程同样也会检测“外露”类型,在捕获过程会一步一步进行严格的类型检测。至于更深入的原理,可以参考JDK的内部原理。】

5.总结:
  关于Java的集合、枚举、泛型的内容写到这里基本上就完成了所有内容的讲解了,可能泛型部分的内容还不是特别深入,只是写了应用部分,没有进行原理解析,后边有必要的话我会补充一份泛型的章节,因为好久没有写代码了,泛型这部分整理的时候可能有点乱,所以请读者见谅。不过基本上泛型遇到的一些常见的代码的Demo以及泛型里面常见的一些关键点都已经涉及到了,如果有什么笔误或者不详细的地方请读者来Email告知。集合部分基本上所有API都已经被遍历过一次,而且都已经详细说明了里面的每一种的用法和相对的内容。枚举部分所有内容在上边都有讲解。不过估计这次又要打散成几个章节了,貌似这个长度CSDN的BLOG一次性又发不下。谢谢!

展开阅读全文

没有更多推荐了,返回首页