java中的几个集合类

    技术2022-05-20  42

    今天在网上搜索了一下,发现一篇关于java集合的博文,里面整理得非常好, 特意copy过来和大家分享一下

    本讲内容:集合 collection

    讲集合collection之前,我们先分清三个概念:

    colection 集合,用来表示任何一种数据结构 Collection 集合接口,指的是 java.util.Collection接口,是 Set、List 和 Queue 接口的超类接口 Collections 集合工具类,指的是 java.util.Collections 类。

     

    SCJP考试要求了解的接口有:Collection , Set , SortedSet , List , Map , SortedMap , Queue , NavigableSet , NavigableMap, 还有一个 Iterator 接口也是必须了解的。

    SCJP考试要求了解的类有: HashMap , Hashtable ,TreeMap , LinkedHashMap , HashSet , LinkedHashSet ,TreeSet , ArrayList , Vector , LinkedList , PriorityQueuee , Collections , Arrays

    下面给出一个集合之间的关系图:

    上图中加粗线的ArrayList 和 HashMap 是我们重点讲解的对象。下面这张图看起来层级结构更清晰些。

    我们这里说的集合指的是小写的collection,集合有4种基本形式,其中前三种的父接口是Collection。

    List 关注事物的索引列表 Set 关注事物的唯一性 Queue 关注事物被处理时的顺序 Map 关注事物的映射和键值的唯一性

     

    一、Collection 接口

    Collection接口是 Set 、List 和 Queue 接口的父接口,提供了多数集合常用的方法声明,包括 add()、remove()、contains() 、size() 、iterator() 等。

    add(E e)将指定对象添加到集合中remove(Object o)将指定的对象从集合中移除,移除成功返回true,不成功返回falsecontains(Object o)查看该集合中是否包含指定的对象,包含返回true,不包含返回flasesize()返回集合中存放的对象的个数。返回值为intclear()移除该集合中的所有对象,清空该集合。iterator()返回一个包含所有对象的iterator对象,用来循环遍历toArray()返回一个包含所有对象的数组,类型是ObjecttoArray(T[] t)返回一个包含所有对象的指定类型的数组

     

     

    我们在这里只举一个把集合转成数组的例子,因为Collection本身是个接口所以,我们用它的实现类ArrayList做这个例子:

    view source print ? 01import java.util.ArrayList; 02import java.util.Collection; 03  04public class CollectionTest { 05  06    public static void main(String[] args) { 07  08        String a = "a",b="b",c="c"; 09        Collection list = new ArrayList(); 10        list.add(a); 11        list.add(b); 12        list.add(c); 13  14        String[] array =  list.toArray(new String[1]); 15  16        for(String s : array){ 17            System.out.println(s); 18        } 19    } 20}

    编译并运行程序,检查结果:

    二、几个比较重要的接口和类简介

    1、List接口

    List 关心的是索引,与其他集合相比,List特有的就是和索引相关的一些方法:get(int index) 、 add(int index,Object o) 、 indexOf(Object o) 。

    ArrayList 可以将它理解成一个可增长的数组,它提供快速迭代和快速随机访问的能力。

    LinkedList 中的元素之间是双链接的,当需要快速插入和删除时LinkedList成为List中的不二选择。

    Vector 是ArrayList的线程安全版本,性能比ArrayList要低,现在已经很少使用

    2、Set接口

    Set关心唯一性,它不允许重复。

    HashSet 当不希望集合中有重复值,并且不关心元素之间的顺序时可以使用此类。

    LinkedHashset 当不希望集合中有重复值,并且希望按照元素的插入顺序进行迭代遍历时可采用此类。

    TreeSet 当不希望集合中有重复值,并且希望按照元素的自然顺序进行排序时可以采用此类。(自然顺序意思是某种和插入顺序无关,而是和元素本身的内容和特质有关的排序方式,譬如“abc”排在“abd”前面。)

    3、Queue接口

    Queue用于保存将要执行的任务列表。

    LinkedList 同样实现了Queue接口,可以实现先进先出的队列。

    PriorityQueue 用来创建自然排序的优先级队列。番外篇中有个例子http://android.yaohuiji.com/archives/3454你可以看一下。

    4、Map接口

    Map关心的是唯一的标识符。他将唯一的键映射到某个元素。当然键和值都是对象。

    HashMap 当需要键值对表示,又不关心顺序时可采用HashMap。

    Hashtable 注意Hashtable中的t是小写的,它是HashMap的线程安全版本,现在已经很少使用。

    LinkedHashMap 当需要键值对,并且关心插入顺序时可采用它。

    TreeMap 当需要键值对,并关心元素的自然排序时可采用它。

    三、ArrayList的使用

    ArrayList是一个可变长的数组实现,读取效率很高,是最常用的集合类型。

    1、ArrayList的创建

    在Java5版本之前我们使用:

    view source print ? 1List list = new ArrayList();

    在Java5版本之后,我们使用带泛型的写法:

    view source print ? 1List<String> list = new ArrayList<String>();

    上面的代码定义了一个只允许保存字符串的列表,尖括号括住的类型就是参数类型,也成泛型。带泛型的写法给了我们一个类型安全的集合。关于泛型的知识可以参见这里。

    2、ArrayList的使用:

    view source print ? 01List<String> list = new ArrayList<String>(); 02list.add("nihao!"); 03list.add("hi!"); 04list.add("konikiwa!"); 05list.add("hola"); 06list.add("Bonjour"); 07System.out.println(list.size()); 08System.out.println(list.contains(21)); 09System.out.println(list.remove("hi!")); 10System.out.println(list.size());

    关于List接口中的方法和ArrayList中的方法,大家可以看看JDK中的帮助。

    3、基本数据类型的的自动装箱:

    我们知道集合中存放的是对象,而不能是基本数据类型,在Java5之后可以使用自动装箱功能,更方便的导入基本数据类型。

    view source print ? 1List<Integer> list = new ArrayList<Integer>(); 2list.add(new Integer(42)); 3list.add(43);

    4、ArrayList的排序:

    ArrayList本身不具备排序能力,但是我们可以使用Collections类的sort方法使其排序。我们看一个例子:

    view source print ? 01import java.util.ArrayList; 02import java.util.Collections; 03import java.util.List; 04  05public class Test { 06  07    public static void main(String[] args) { 08        List<String> list = new ArrayList<String>(); 09        list.add("nihao!"); 10        list.add("hi!"); 11        list.add("konikiwa!"); 12        list.add("hola"); 13        list.add("Bonjour"); 14  15        System.out.println("排序前:"+ list); 16  17        Collections.sort(list); 18  19        System.out.println("排序后:"+ list); 20    } 21  22}

    编译并运行程序查看结果:

    排序前:[nihao!, hi!, konikiwa!, hola, Bonjour]排序后:[Bonjour, hi!, hola, konikiwa!, nihao!]

    5、数组和List之间的转换

    从数组转换成list,可以使用Arrays类的asList()方法:

    view source print ? 01import java.util.ArrayList; 02import java.util.Collections; 03import java.util.List; 04  05public class Test { 06  07    public static void main(String[] args) { 08  09            String[] sa = {"one","two","three","four"}; 10            List list = Arrays.asList(sa); 11            System.out.println("list:"+list); 12            System.out.println("list.size()="+list.size()); 13    } 14  15}

    6、Iterator和for-each在for-each出现之前,我们想遍历ArrayList中的每个元素我们会使用Iterator接口:

    view source print ? 01import java.util.Arrays; 02import java.util.Iterator; 03import java.util.List; 04  05public class Test { 06  07    public static void main(String[] args) { 08  09        // Arrays类为我们提供了一种list的便捷创建方式 10        List<String> list = Arrays.asList("one", "two", "three", "four"); 11  12        // 转换成Iterator实例 13        Iterator<String> it = list.iterator(); 14  15        //遍历 16        while (it.hasNext()) { 17            System.out.println(it.next()); 18        } 19  20    } 21  22}

    在for-each出现之后,遍历变得简单一些:

    01import java.util.Arrays; 02import java.util.Iterator; 03import java.util.List; 04  05public class Test { 06  07    public static void main(String[] args) { 08  09        // Arrays类为我们提供了一种list的便捷创建方式 10        List<String> list = Arrays.asList("one", "two", "three", "four"); 11  12        for (String s : list) { 13            System.out.println(s); 14        } 15  16    } 17  18}

    本讲内容:Map HashMap

    前面课程中我们知道Map是个接口,它关心的是映射关系,它里面的元素是成对出现的,键和值都是对象且键必须保持唯一。这一点上看它和Collection是很不相同的。

    一、Map接口

    Map接口的常用方法如下表所示:

    put(K key, V value)向集合中添加指定的键值对putAll(Map <? extends K,? extends V> t)把一个Map中的所有键值对添加到该集合containsKey(Object key)如果包含该键,则返回truecontainsValue(Object value)如果包含该值,则返回trueget(Object key)根据键,返回相应的值对象keySet()将该集合中的所有键以Set集合形式返回values()将该集合中所有的值以Collection形式返回remove(Object key)如果存在指定的键,则移除该键值对,返回键所对应的值,如果不存在则返回nullclear()移除Map中的所有键值对,或者说就是清空集合isEmpty()查看Map中是否存在键值对size()查看集合中包含键值对的个数,返回int类型

     

    因为Map中的键必须是唯一的,所以虽然键可以是null,只能由一个键是null,而Map中的值可没有这种限制,值为null的情况经常出现,因此get(Object key)方法返回null,有两种情况一种是确实不存在该键值对,二是该键对应的值对象为null。为了确保某Map中确实有某个键,应该使用的方法是 containsKey(Object key) 。

    二、HashMap

    HashMap是最常用的Map集合,它的键值对在存储时要根据键的哈希码来确定值放在哪里。

    1、HashMap的基本使用:

     

    view source print ? 01import java.util.Collection; 02import java.util.HashMap; 03import java.util.Map; 04import java.util.Set; 05  06public class Test { 07  08    public static void main(String[] args) { 09  10        Map<Integer,String> map = new HashMap<Integer,String>(); 11  12        map.put(1, "白菜"); 13        map.put(2, "萝卜"); 14        map.put(3, "茄子"); 15        map.put(4, null); 16        map.put(null, null); 17        map.put(null, null); 18  19        System.out.println("map.size()="+map.size()); 20        System.out.println("map.containsKey(1)="+map.containsKey(2)); 21        System.out.println("map.containsKey(null)="+map.containsKey(null)); 22        System.out.println("map.get(null)="+map.get(null)); 23  24        System.out.println("map.get(2)="+map.get(2)); 25        map.put(null, "黄瓜"); 26        System.out.println("map.get(null)="+map.get(null)); 27  28        Set set = map.keySet(); 29        System.out.println("set="+set); 30  31        Collection<String> c = map.values(); 32  33        System.out.println("Collection="+c); 34  35    } 36  37}

    编译并运行程序,查看结果:

    view source print ? 1map.size()=5 2map.containsKey(1)=true 3map.containsKey(null)=true 4map.get(null)=null 5map.get(2)=萝卜 6map.get(null)=黄瓜 7set=[null, 1, 2, 3, 4] 8Collection=[黄瓜, 白菜, 萝卜, 茄子, null]

    2、HashMap 中作为键的对象必须重写Object的hashCode()方法和equals()方法

    下面看一个我花了1个小时构思的例子,熟悉龙枪的朋友看起来会比较亲切,设定了龙和龙的巢穴,然后把它们用Map集合对应起来,我们可以根据龙查看它巢穴中的宝藏数量,例子只是为了说明hashCode这个知识点,所以未必有太强的故事性和合理性,凑合看吧:

    view source print ? 01import java.util.HashMap; 02import java.util.Map; 03  04public class Test { 05  06    public static void main(String[] args) { 07  08        // 龙和它的巢穴映射表 09        Map<dragon , Nest> map = new HashMap<dragon , Nest>(); 10  11        // 在Map中放入四只克莱恩大陆上的龙 12        map.put(new Dragon("锐刃", 98), new Nest(98)); 13        map.put(new Dragon("明镜", 95), new Nest(95)); 14        map.put(new Dragon("碧雷", 176), new Nest(176)); 15        map.put(new Dragon("玛烈", 255), new Nest(255)); 16  17        // 查看宝藏 18        System.out.println("碧雷巢穴中有多少宝藏:" + map.get(new Dragon("碧雷", 176)).getTreasure()); 19    } 20  21} 22  23// 龙 24class Dragon { 25  26    Dragon(String name, int level) { 27        this.level = level; 28        this.name = name; 29    } 30  31    // 龙的名字 32    private String name; 33  34    // 龙的级别 35    private int level; 36  37    public int getLevel() { 38        return level; 39    } 40  41    public void setLevel(int level) { 42        this.level = level; 43    } 44  45    public String getName() { 46        return name; 47    } 48  49    public void setName(String name) { 50        this.name = name; 51    } 52  53} 54  55// 巢穴 56class Nest { 57  58    //我研究的龙之常数 59    final int DRAGON_M = 4162; 60  61    // 宝藏 62    private int treasure; 63  64    // 居住的龙的级别 65    private int level; 66  67    Nest(int level) { 68        this.level = level; 69        this.treasure = level * level * DRAGON_M; 70    } 71  72    int getTreasure() { 73        return treasure; 74    } 75  76    public int getLevel() { 77        return level; 78    } 79  80    public void setLevel(int level) { 81        this.level = level; 82        this.treasure = level * level * DRAGON_M; 83    } 84  85}

    编译并运行查看结果:

    view source print ? 1Exception in thread "main" java.lang.NullPointerException 2    at Test.main(Test.java:18)

    我们发现竟然报了错误,第18行出了空指针错误,也就是说get方法竟然没有拿到预期的巢穴对象。

    在这里我们就要研究一下为什么取不到了。我们这里先解释一下HashMap的工作方式。

    假设现在有个6张中奖彩票的存根,放在5个桶里(彩票首位只有1-5,首位是1的就放在一号桶,是2的就放在2号桶,依次类推),现在你拿了3张彩票来兑奖,一个号码是113,一个号码是213,一个号码是313。那么现在先兑第一张,取出一号桶里的存根发现存根号码和你的号码不符,所以你第一张没中奖。继续兑第二张,二号桶里就没存根所以就直接放弃了,把三号桶里的所有彩票存根都拿出来对应一番,最后发现有一个存根恰好是313,那么恭喜你中奖了。

    HashMap在确定一个键对象和另一个键对象是否是相同时用了同样的方法,每个桶就是一个键对象的散列码值,桶里放的就是散列码相同的彩票存根,如果散列码不同,那么肯定没有相关元素存在,如果散列码相同,那么还要用键的equals()方法去比较是否相同,如果相同才认为是相同的键。简单的说就是 hashCode()相同 && equals()==true 时才算两者相同。

    到了这里我们应该明白了,在没有重写一个对象的hashcode()和equals()方法之前,它们执行的是Object中对应的方法。而Object的hashcode()是用对象在内存中存放的位置计算出来的,每个对象实例都不相同。Object的equals()的实现更简单就是看两个对象是否==,也就是两个对象除非是同一个对象,否则根本不会相同。因此上面的例子虽然都是名字叫碧雷的龙,但是HashMap中却无法认可它们是相同的。

    因此我们只有重写Key对象的hashCode()和equals()方法,才能避免这种情形出现,好在Eclipse可以帮我们自动生成一个类的hashCode()和equals(),我们把上面的例子加上这两个方法再试试看:

    view source print ? 001import java.util.HashMap; 002import java.util.Map; 003  004public class Test { 005  006    public static void main(String[] args) { 007  008        // 龙和它的巢穴映射表 009        Map<dragon , Nest> map = new HashMap<dragon , Nest>(); 010  011        // 在Map中放入四只克莱恩大陆上的龙 012        map.put(new Dragon("锐刃", 98), new Nest(98)); 013        map.put(new Dragon("明镜", 95), new Nest(95)); 014        map.put(new Dragon("碧雷", 176), new Nest(176)); 015        map.put(new Dragon("玛烈", 255), new Nest(255)); 016  017        // 查看宝藏 018        System.out.println("碧雷巢穴中有多少宝藏:" + map.get(new Dragon("碧雷", 176)).getTreasure()); 019    } 020  021} 022  023// 龙 024class Dragon { 025  026    Dragon(String name, int level) { 027        this.level = level; 028        this.name = name; 029    } 030  031    // 龙的名字 032    private String name; 033  034    // 龙的级别 035    private int level; 036  037    public int getLevel() { 038        return level; 039    } 040  041    public void setLevel(int level) { 042        this.level = level; 043    } 044  045    public String getName() { 046        return name; 047    } 048  049    public void setName(String name) { 050        this.name = name; 051    } 052  053    @Override 054    public int hashCode() { 055        final int PRIME = 31; 056        int result = 1; 057        result = PRIME * result + level; 058        result = PRIME * result + ((name == null) ? 0 : name.hashCode()); 059        return result; 060    } 061  062    @Override 063    public boolean equals(Object obj) { 064        if (this == obj) 065            return true; 066        if (obj == null) 067            return false; 068        if (getClass() != obj.getClass()) 069            return false; 070        final Dragon other = (Dragon) obj; 071        if (level != other.level) 072            return false; 073        if (name == null) { 074            if (other.name != null) 075                return false; 076        } else if (!name.equals(other.name)) 077            return false; 078        return true; 079    } 080  081} 082  083// 巢穴 084class Nest { 085  086    //我研究的龙之常数 087    final int DRAGON_M = 4162; 088  089    // 宝藏 090    private int treasure; 091  092    // 居住的龙的级别 093    private int level; 094  095    Nest(int level) { 096        this.level = level; 097        this.treasure = level * level * DRAGON_M; 098    } 099  100    int getTreasure() { 101        return treasure; 102    } 103  104    public int getLevel() { 105        return level; 106    } 107  108    public void setLevel(int level) { 109        this.level = level; 110        this.treasure = level * level * DRAGON_M; 111    } 112  113}

    编译并运行查看结果:

    view source print ? 1碧雷巢穴中有多少宝藏:128922112

    这一次正常输出了,真不容易^_^

    好了本讲就到这里。


    最新回复(0)