简说JAVA集合类

原创 2016年08月29日 15:32:25

Java集合类是一组可变数量的数据项,全部集合在一起,又叫做容器类,即可以像容器一样存放一系列基本数据类型或者对象

一、集合类概念引入

对数据储存,可以使用数组、封装类、数据库。其中数组可以储存一定长度、同种数据类型的数据,特别注意的长度是不可以变的,所以在定义的时候如果知道要使用的具体长度,就可以定义刚好的长度,但是不知道具体长度,就只能定义一个很大的数值,可能造成资源的浪费。
如上所述,数组存在着这两个缺点:

  • 数据类型必须相等
  • 数组的长度必须事先确定且不能动态修改

JAVA引入了集合类,集合类可以添加不同的数据类型,还可以动态的增加或减少数据。
下面是集合类的一个例子:

import java.util.*; 
public class Test {
    public static void main(String[] args) {
        int t1 = 1;          //t1为整型
        double t2 = 0.9;    //t2为双精度浮点型
        String t3 = "test";  //t3为字符串

        //ArrayList是集合类中的一种
        ArrayList a1=new ArrayList();
        a1.add(t1);   //将t1加入集合类对象中
        a1.add(t2);   //将t2加入集合类对象中
        a1.add(t3);   //将t3加入集合类对象中  

        System.out.println(a1.size()); //查看集合类对象中的元素数目
        a1.remove(t1);                 //将t1移除
        System.out.println(a1.size());
    }
}

测试结果为:
测试结果1
上面的提示是因为没有使用泛型,不影响结果。

二、集合框架

JAVA中有多个集合类,各自有不同的数据储存方式,集合类形成一个体系,称为集合框架
具体的用图来表示:
集合框架
图中黑色的是接口,深灰色的是抽象类,浅灰色的是实现类。

对图整理、简化,剩下常用的一些类,如图:
简化集合框架图
其中黄色的为接口,其他为实现类,红色的为比较常用的实现类。
从简图中可以看出,集合类有两大接口,一个是Collection,一个是Map,其他的类都是实现了它们的继承接口。

三、Collection和Map的对比

首先要注意的一点是Map不是Collection的子类,这点容易搞混,在网上找资料的时候看到有些人说所有集合类都继承Collection,但是我查找API发现Map是没有继承Collection的:
Map

Collection和Map最大的区别在于储存方式的不同。
Collection是储存“值(value)”,而Map是储存“键(key)—值(value)对”:
Map和Collection

Map中的“键(key)”就相当于表格的标题,可以通过键来索引数据,但是键不能够重复,一个键最多能绑定一个值(value)。

null也可以可以作为键,这样的键只有一个;但可以有一个或多个键所对应的值为null。
可以用get(Object key)方法获得key对应的value,但是API里是这样描述这个方法的:

更确切地讲,如果此映射包含满足 (key==null ? k==null : key.equals(k)) 的键 k 到值 v 的映射关系,则此方法返回 v;否则返回 null。(最多只能有一个这样的映射关系)。
如果此映射允许 null 值,则返回 null值并不一定 表示该映射不包含该键的映射关系;也可能该映射将该键显示地映射到 null。使用containsKey 操作可区分这两种情况

即当get(Object key)方法返回null值时,一可以表示Map中没有该键,也可以表示该键所对应的值为null。
为了避免混淆,可预先用containsKey(Object key)方法来判断:

如果此映射包含指定键的映射关系,则返回 true。更确切地讲,当且仅当此映射包含针对满足 (key==null ? k==null :key.equals(k)) 的键 k 的映射关系时,返回 true。(最多只能有一个这样的映射关系)。

四、Connection接口及其继承

1、List和Set接口

List和Set是继承Collection的两个接口,以下是它们之间的异同:

  • List是有序的collection(也称为序列)。此接口的用户可以对列表中每个元素的插入位置进行精确地控制。类似于数组,用户可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素,因为可以用整数索引,故List允许有重复的元素。
  • Set是一个不包含重复元素的 collection。更确切地讲,set 不包含满足 e1.equals(e2) 的元素对 e1 和e2,并且最多包含一个 null 元素。 Set不带顺序,所以是不能像数组一样使用下标来索引的。

结构可用下图来理解:

List和Set

2、List的实现类Vector、ArrayList、LinkedList实例

这三个类都对List进行了实现,它们的储存机制和List相同,和数组相似排列储存,可以按照下标进行访问。

它们的使用大致相同,故这里以Vector为例:

import java.util.*;

public class Test{
    public static void main(String[] args){

        //可以这样定义,只能用接口中的方法
        List ls = new Vector();

        //当然这样也是可以的
        //Vector v = new Vector();

        String str = "This is a test";
        int i = 1;
        double d = 0.9;

        //添加元素
        ls.add(str);
        ls.add(i);
        ls.add(d);

        //查看有多少元素
        int size = ls.size();
        System.out.println("添加元素后共有"+size+"个元素");

        //判断是否有指定的元素
        boolean b = ls.contains("This is a test");
        System.out.println(b);
        //判断第一次出现该元素的位置,存在则返回索引,不存在则返回-1
        int indexOfi = ls.indexOf(0.9);
        System.out.println(indexOfi);

        //获取指定索引处的元素
        /*第36行是不行的,因为get(int index)方法获得的是Object类型
         *会出现报错:
         *Test.java:36: 错误: 不兼容的类型: Object无法转换为String
         *String s = ls.get(0);
         */

        //应该强制转换Object为Sring
        String s = (String)ls.get(0);
        System.out.println(s);

        //移除指定索引处的元素,这里移除第三个元素d
        ls.remove(1);
        size = ls.size();
        System.out.println("移除元素后共有"+size+"个元素");
    }
}

运行结果为:
测试结果

3、ArrayList、Vector和LinkedList的区别

(1)ArrayList和Vector:

①ArrayList和Vector的使用基本相同,但是Vector是线程安全的,而ArrayList却是非线程安全的,因为 Vector的方法都是同步的(Synchronized),,而ArrayList的方法不是,由于线程的同步必然要影响性能,因此,ArrayList的性能比Vector好,按网上说的,ArrayList是Vector的裸奔版本,许多人都选用ArrayList使用,这时候要自己在必要的时候加上Synchronized字段。

②当Vector或ArrayList中的元素超过它的初始大小时,Vector会将它的容量翻倍,而ArrayList只增加50%的大小,这样,ArrayList就有利于节约内存空间。

③由Vector创建的Iterator,虽然和ArrayList创建的Iterator是同一接口,但是,因为Vector是同步的,当一个Iterator被创建而且正在被使用,另一个线程改变了Vector的状态(例如,添加或删除了一些元素),这时调用Iterator的方法时将抛出ConcurrentModificationException,因此必须捕获该异常。

④一般情况下,使用使用ArrayList较多,Vector被认为不推荐使用的。

(2)与LinkedList的对比:

①ArrayList和Vector使用了数组的实现,可以认为ArrayList或者Vector封装了对内部数组的操作,比如向数组中添加,删除,插入新的元素或者数据的扩展和重定向。

②LinkedList使用了循环双向链表数据结构。与基于数组ArrayList相比,这是两种截然不同的实现技术,这也决定了它们将适用于完全不同的工作场景。而且LinkedList也是不同步的,非线程安全。

由上可知在ArrayList或者Vector的前面或中间插入数据时,必须将其后的所有数据相应的后移,这样必然要花费较多时间,所以,当你的操作是在一列数据的前面或中间添加或删除数据,并且按照顺序访问其中的元素时,就应该使用LinkedList了,即修改数据时LinkedList较快;
而访问链表中的某个元素时,就必须从链表的一端开始沿着连接方向一个一个元素地去查找,直到找到所需的元素为止, 所以,当你的操作是在一列数据的后面添加数据而不是在前面或中间,并且需要随机地访问其中的元素时,使用ArrayList或者Vector会提供比较好的性能,即查找数据时ArrayList和Vector较快

而且LinkedList较这两者多了listIterator()方法,可以产生ListIterator迭代器,多了许多方法,不仅可以向后遍历,也可以向前遍历。
想看源码对比和链表的结构什么的,可以看看ArrayList和LinkedList的区别

4、对元素进行遍历输出的方法

可以用for循环和迭代器进行遍历输出:

import java.util.*;

public class Test{
    public static void main(String[] args){
        List ls = new Vector();

        ls.add("str1");
        ls.add("str2");
        ls.add("str3");
        //ls.add(111)   这里只添加字符串,方便后面用遍历

        //用for循环遍历
        for(int i=0; i<ls.size(); i++){
            //上面不添加整型111就是方便这里Object数据转换
            System.out.println((String)ls.get(i));
        }

        System.out.println("**********************");

        //用迭代器iterator迭代输出,iterator是一个接口
        Iterator it = ls.iterator();
        while(it.hasNext()){   //如果仍有元素可以迭代,则进行
            //获取下一个元素
            System.out.println((String)it.next());
        }
    }
}

测试结果为:
测试结果

这里用到了Iterator接口,也就是迭代器

5、迭代器简介

迭代器(iterator)是一种对象,它能够用来遍历标准模板库容器中的部分或全部元素,每个迭代器对象代表容器中的确定的地址。迭代器修改了常规指针的接口。

迭代器提供一些基本操作符:*、++、==、!=、=。这些操作和C/C++“操作array元素”时的指针接口一致。不同之处在于,迭代器是个所谓的复杂的指针,具有遍历复杂数据结构的能力。其下层运行机制取决于其所遍历的数据结构。因此,每一种容器型都必须提供自己的迭代器。事实上每一种容器都将其迭代器以嵌套的方式定义于内部。因此各种迭代器的接口相同,类型却不同。

这直接导出了泛型程序设计的概念:所有操作行为都使用相同接口,虽然它们的类型不同。

从API可以看出,其实Collection接口是继承了Iterator接口的,但要注意的是Map并没有继承Iterator。
这里写图片描述

Collection也有相应的方法返回迭代器:
这里写图片描述

有了迭代器,就可以使用迭代器对集合类进行遍历,其中的方法有:

  • 使用next()获得序列中的下一个元素;
  • 使用hasNext()检查序列中是否还有元素;
  • 使用remove()将迭代器新返回的元素删除。

Iterator是Java迭代器最简单的实现,为List设计的ListIterator具有更多的功能,它可以从两个方向遍历List,也可以从List中插入和删除元素。

6、泛型简介

由迭代器的使用产生了泛型的概念,由上面的例子可以看出,由get()或者iterator().next()得到的元素的类型都为Object,需要进行强制转换,假如List里加入的元素都是不同类型的,那么遍历或者使用时候就要反复的换类型,是不太现实的,而且转换不仅显得混乱,更可能导致类型转换异常ClassCastException,运行时异常往往让人难以检测到。
保证列表中的元素为一个特定的数据类型,这样就可以取消类型转换,减少发生错误的机会这里可以用泛型的概念,看下面一个例子:

import java.util.*;
public class Test{
    public static void main(String[] args){
        ArrayList<String> al = new ArrayList<String>();

        al.add("元素1");
        al.add("元素2");
        al.add("元素3");
        //al.add(1);   编译不通过

        String str1 = al.get(1);
        System.out.println(str1);

        Iterator<String> it = al.iterator();
        String str2 = it.next();
        System.out.println(str2);
    }
}

这里写图片描述
这里<>尖括号内的就是泛型。

需要注意的是,泛型只能使用引用数据类型,不能使用基本数据类型,如int是会出错的,需要使用Integer。
这里写图片描述

7、Set接口及其继承

这里按照上面的图解可以看出Set的这种储存方式类似于数学中的“集合”——集合中的元素具有无序性、单一性、确定性。根据这个特点,Set 可以被用来过滤在其他集合中存放的元素,得到一个没有包含重复的集合。

要储存就要确定不相等,那么Set集合下的元素怎么确定单一性呢?
我们看API里Set对add(E e)的解释:

如果 set 中尚未存在指定的元素,则添加此元素(可选操作)。更确切地讲,如果此 set 没有包含满足 (e==null ? e2==null : e.equals(e2)) 的元素 e2,则向该 set 中添加指定的元素 e。如果此 set 已经包含该元素,则该调用不改变此 set 并返回 false。

这里有如果存在元素e2满足(e==null ? e2==null : e.equals(e2)),则不添加元素e,也就是说:
判断e是否为null,如果为null,若存在元素也为null则不添加,否则添加;如果e不为null,则用equals()方法和其他元素进行对比,如果一样true则不添加,false则添加。
这里用到了equals()方法,我们知道在引用对象String也就是字符串的中使用过,== 比的是地址,equals()比较的是字符串内容,但是这里需要注意的是,每个类的equals()方法是不一样的。比如对于Object类,由API可以看到equals()方法的含义:
这里写图片描述

可以看出,Object类的equals()方法其实和==号是一样的。
任何类都继承Object类,类使用equals()方法时可以重写Object的equals方法来说明类之间如何进行比较,String类就重写了equals()方法,当类没有重写equals()方法时,Object中的equals方法默认比较两个类的地址,所以Set判断添加的元素是否相等还需要看特定的元素如何实现equals()方法;除了equals(),还有一个哈希码值的对比,但一般equals()里已经对哈希码进行了对比,两者之间存在关系,所以一般对比equals()就够了,关于哈希码和equals()的理解,可以看看这两篇博客:
第一篇比较短,也容易理解:总结一下java中我认识的哈希码以及equals和==的区别
第二篇比较长,说的详细点:equals()和hashCode()区别?

以下用HashSet为例,示范一下基本的方法和不能添加两个相同元素的情况:

import java.util.Set;
import java.util.HashSet;
import java.util.Iterator;
public class Test{

    public static void main(String[] args){
        Set<Student> hs1 = new HashSet<Student>();

        Student s1 = new Student("小明",10);
        boolean b11 = hs1.add(s1);   //添加元素s1

        //两个一样内容的实例
        Student s2 = new Student("小红",11);
        Student s3 = new Student("小红",11); //内容一样但是可以添加
        //添加元素
        boolean b12 = hs1.add(s2);
        boolean b13 = hs1.add(s3);

        System.out.println("b11="+b11+" ,b12="+b12+" ,b13="+b13);
        System.out.println("hs1当前的元素个数为"+hs1.size());

        Set<String> hs2 = new HashSet<String>();
        boolean b21 = hs2.add("text1");
        boolean b22 = hs2.add("text2");
        boolean b23 = hs2.add("text3");
        boolean b24 = hs2.add("text1"); //元素重复了
        System.out.println("b21="+b21+" ,b22="+b22+" ,b23="+b23+" ,b24:"+b24);
        System.out.println("hs2当前的元素个数为"+hs2.size());

        //移除元素,这里的参数是元素的值,因为没有下标
        hs2.remove("text3");
        //用迭代器遍历
        Iterator it = hs2.iterator();
        while(it.hasNext()){
            String str = (String)it.next();
            System.out.println(str);
        }
    }
}

class Student{
    String name;
    int age;

    public Student(String name,int age){
        this.name = name;
        this.age = age;
    }

    public String getMessage(){
        String rtn = "name:"+name+" ,age:"+age;
        return rtn;
    }
}

得到结果:
这里写图片描述
从结果可以看到,对于hs1,添加的Student对象s2和s3虽然内容是一样的,但是因为没有改变Object的equals()方法,所以比较的是地址,因为s2和s3是两个不同的对象,固然地址是不一样的,所以可以添加;
而对于hs2,添加了两个不同的String对象但是值都为”text1”,由于String改写了equals()方法,是根据字符串的内容来辨别是否相等的,所以这两个对象按这种比较方法是算相等的,不能重复添加。
Set接口的remove()方法参数是要移除的元素值,不像List一样是按照索引来移除的,这里要注意。

8、HashSet、TreeSet、LinkedHashSet区别

三者都有Set的共性,使用上也大致相同,也有各自的特点。

(1)HashSet使用的是相当复杂的方式来存储元素的,虽然空间使用的大,但其获取元素的速度较快;
(2)TreeSet会对元素进行自动排序,如果存放的对象不能排序则会报错,所以存放的对象必须指定排序规则。排序规则包括自然排序和客户排序:
- ①自然排序:TreeSet要添加哪个对象就在哪个对象类上面实现java.lang.Comparable接口,并且重写comparaTo()方法,返回0则表示是同一个对象,否则为不同对象。
- ②客户排序:建立一个第三方类并实现java.util.Comparator接口。并重写方法。定义集合形式为TreeSet ts = new TreeSet(new 第三方类());
其实个人觉得这两者差不多,自然排序是由要插入的元素决定的,而客户排序是用自己新建的一个类来决定的。

(3)LinkedHashSet按照插入顺序保存对象,同时还保存了HashSet的查询速度。
相关的测试可以看看:java笔记四:Set接口

五、Map接口以及其继承

1、Map实现类实例

如上所述,Map接口储存的是键值对,对元素的插入和查找和collection不一样,不过和set接口有些类似:Map获得元素的方法使用get(Object key),这里使用的是对键进行索引;Map的元素插入使用的是put(K key,V value)而不是add方法,参数有两个;如果想知道是否存在某个键或者某个值,可以分别使用containsKey(Object key)和containsValue(Object value)方法,存在则返回true。
以下是具体的例子:

import java.util.Map;
import java.util.HashMap;
public class Test{
    public static void main(String[] args){
        Map hm = new HashMap();

        //用put来添加键值对,put返回值为以前添加的值,没有值则返回null
        String k1value = (String)hm.put("k1","v1");
        System.out.println("以前添加k1的值为:"+k1value);
        hm.put("k2","v2");
        hm.put("k3","c3");
        //键和值都可以添加不同的类型
        hm.put(4,0.4);
        hm.put(5,0.5);
        //获得集合大小
        int size = hm.size();
        System.out.println("添加5个键值对后size为:"+size);

        //添加相同的键,则值会被覆盖
        String k3value = (String)hm.put("k3","v3");
        System.out.println("添加相同值前,键k3对应的值为:"+k3value);
        System.out.println("添加相同值后,键k3对应的值为:"+(String)hm.get("k3"));

        //移除键值对,参数为Object key,如果存在则返回以前关联的值,否则返回null
        double fiveValue = (double)hm.remove(5);
        System.out.println("移除的值为:"+fiveValue);
        System.out.println("移除后size为:"+hm.size());

    }
}

运行结果为:
这里写图片描述

2、HashMap和TreeMap的选择

以下是两者的区别:

  • HashMap:底层是哈希表数据结构,通过hashcode对其内容进行快速查找,元素排列顺序不固定,线程不同步。
  • TreeMap:底层是二叉树数据结构,所有的元素都保持着某种固定的顺序,同样线程不同步。

两者所有的具体实现一样,使用哪种实现取决于特定需要。在Map 中插入、删除和定位元素,HashMap 是最好的选择。但要按顺序遍历键,那么TreeMap 会更好。根据集合大小,先把元素添加到 HashMap,再把这种映射转换成一个用于有序键遍历的 TreeMap 可能更快。

六、Map和Collection对元素的遍历的区别

Collection接口继承了Iterator接口,可以用iterator()方法返回迭代器进行遍历,但是Map没有继承Iterator也没有iterator()方法,不能创建这样的迭代器,当然你可以用for循环来遍历,也可以像这样“曲线救国”:
一个是使用entrySet:
这里写图片描述
一个是使用keySet:
这里写图片描述
例子如下:

import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;
public class Test{
    public static void main(String[] args){
        //这里使用了使用泛型当然你可以不用
        Map<String,String> hm = new HashMap<String,String>();

        hm.put("k1","v1");
        hm.put("k2","v2");
        hm.put("k3","v3");
        hm.put("k4","v4");
        hm.put("k5","v5");

        //方法一:建立key的set集合,再用迭代器
        System.out.println("方法一:ketSet()***************************");
        Iterator<String> it1 = hm.keySet().iterator();
        while(it1.hasNext()){
            System.out.println(hm.get(it1.next()));
        }

        //方法二:使用包有entry的Iterator迭代器
        System.out.println("方法二:entrySet()*************************");
        //或者写为Iterator it2 = hm.entrySet().iterator();下面的用了泛型
        Iterator<Map.Entry<String,String>> it2 = hm.entrySet().iterator();

        while(it2.hasNext()){
            //如果用上面注释的,则这里写Map.Entry entry = (Map.Entry)it2.next();
            Map.Entry entry = it2.next();
            System.out.println(entry.getKey()+":"+entry.getValue());
        }

    }
}

运行结果:
这里写图片描述

因为Map没有Iterator迭代器,所以都先生成了Collection的子接口Set的实例,再进行迭代。
在这里,方法一使用了KeySet()方法,方法的返回值为键key的Set集合;
方法二使用entrySet()方法,方法的返回值为entry的Set集合,利用迭代器返回entry对象后,再使用entry对象的方法来读取key和value。
一般选用方法一,比较简单。

版权声明:原创作品,允许转载,转载时请务必以超链接形式标明文章原始出处 、作者博客地址(http://blog.csdn.net/z_haisome)和本声明。否则将追究法律责任。

相关文章推荐

Java中hashCode() equals() 与将对象放入集合或者说Map时要考虑的问题

Java中hashCode() equals() 与将对象放入集合或者说Map时要考虑的问题

2JAVA编程高级-集合类.pdf

  • 2014-07-29 09:17
  • 373KB
  • 下载

Java集合类(Collection)学习

  • 2014-06-10 10:33
  • 28KB
  • 下载

简说JAVA8引入函数式的问题。

JAVA8中加入lambda演算是一个令人兴奋的新特性

java集合类的效率测试

  • 2009-06-01 18:30
  • 16KB
  • 下载

java笔记--集合类

  • 2011-11-23 21:23
  • 217KB
  • 下载

从java读取Excel继续说大道至简

在上一篇博客《从复杂到简单,大道至简》中说道我们要把复杂的问题简单化,也就是要把问题细分,让大问题变成小问题,这样解决起来会相对容易,当我们把容易的小问题解决掉了,大问题自动就会迎刃而解。      ...

java集合类

  • 2012-06-22 10:48
  • 162KB
  • 下载

Java中的匿名内部类:由setOnClickListener说起

在初学Android的时候,总是看到这样一段代码: Button button = (Button) findViewById(R.id.button); button.setOnClickLis...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)