------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
集合概述(Collection)
第一部分 List & Itertor
一、集合概述
集合从功能理解上来说,其实和数组有相同之处,同样是存储数据,同样是容器,但又有所不同。我们知道,数组也是一个容器,在建立一个数组的时候,就规定了这个数组的长度以及里面要存的数据类型,当然数据也是可以存储对象的,只要类型是对象所属类型即可。但集合相对于数组来说,它的长度不是固定的,是可变的长度,它里面只能存储对象,就是说如果基本数据类型想要存入集合中,那么基本数据类型就得封装成基本数据类型的对象,集合可以存储不同类型的对象。
集合也分不同的容器,对这些容器的共性进行向上抽取,形成一个体系,这就是集合框架。当着集合框架产生,我们就可以看顶层的父类,看其中功能,就可以了解整个体系锁具有的基本功能,这样我们参考父类,创建子类对象即可。整个体系顶层,就成为集合Collection。
Collection是一个接口,它有两个连个子接口List和Set,两个子接口下还有多个子接口:看图:
鉴于不同数据的存储方式都即数据结构都不同,所以集合框架中会有多个容器。
集合类的出现,是因为面向对象语言对事物的体现都是以对象的形式,所以为了方便对多个对象的操作,就对对象进行存储,集合就是存储对象最常用的一种方式。
数据和集合类同样是容器,但是又不同之处:
1、 数据虽然也可以存储对象,但是长度固定,集合长度是可变的
2、 数组中可以存储基本数据类型,集合只能存储对象
集合类特点,
1、 集合只能存储对象
2、 集合长度是可变的
3、 集合可以存储不同类型的对象
二、集合共性方法
首先建立一个List的子类对象,以ArrayList为例。
首先建立ArrayList对象,这样就建立了一个容器
ArrayList a = new ArrayList()
1、 添加元素,add(Object obj)
al.add("java01");
al.add("java02");
add方法接收的是Object类型,以便于接受任意类型的对象。
集合与数组一样时容器,同时一样的是存储方式,存储的都是对象的引用地址值。
2、 获取长度
int size =a.size() 返回的是int值
3、删除元素
a.remove("java01" );删除一个对象
a.clear(); 清空集合
4、判断元素
boolean a.contains("java01")判断是否包含某个元素
a. isEmpty()判断集合是否为空
4、 改变集合元素
集合1.retainAll (集合2):即集合1中只会保留与集合2中相同的元素。
集合1.removeAll (集合2):即集合1中会删除与集合2中相同的元素。
三、元素取出:迭代器
集合中的迭代器,其实就是集合的去除元素的方式。示例程序如下:
ArrayList al= new ArrayList();
al.add("java01");
al.add("java02");
Iterator it = al.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
集合al继承了父类的iterator方法,会放回一个iterator的子类对象,其中有
方法hasNext(),这是判断集合中是否有月元素,有的话返回true,没有的话返回false。
方法next(),这就逐个去除集合中的元素,返回迭代的下一个元素。
不同的容器,元素的取出方法就会不同,有几个容器,可能就会有几个方法,这些方法可能会有共同之处,比如都有取出动作,当然每个取出动作都不一样,但有共性内容。我们可以将这这取出动作,进行抽象的抽取,描述成一个新接口Iterator。这样,把取出方式定义在集合内部,这样的取出方式就可以直接访问集合中的元素,那么取出的具体方式就定义成了内部类。有共同的规则即接口,有具体实现的内部类,那么集合就通过对外提供的一个方法iterator(),让我们能够取出集合中的元素。
四、List
Collection中包含两个体系,上图可知:
1、 List 元素是有序的,元素可以重复,因为集合体系有索引。相当于数组。
2、 Set 元素中是无序的,元素不可以重复。
List集合的特有方法:
1、添加元素
add(“java 00”);
2、在指定位置添加元素
add( 1,”java 01”);
3、删除指定位置元素
remove ( 2 );
4、修改元素
set( 4 , “java ok”);
5、通过角标获取元素
get( 3);
6、获取对象位置
indexOf ( “java 01”);
7、获取子集合
subList ( 3,4 ) 包含头不包含尾。
关于ListIterator
ListIterator是List集合的特有方法,关于怎么用先看一下代码:
ArrayList al = new ArrayList();
al.add("java01");
al.add("java02");
al.add("java03");
Iterator it = al.iterator();
while(it.hasNext()){
Object obj = it.next();
if(obj.equals("java02"))
al.add("java009");
}
上面代码中,我建立一个List集合的对象,然后进行迭代器迭代,在迭代的过程中,对集合中元素进行操作,但是这时系统汇报一个异常ConcurrentModificationException,意思就是并发操作非法。简单来说,我们建立了一个List集合,这个集合对象可以操作其中的元素,同时,我们通过这个对象得到一迭代器Iterator,这个迭代器同时也可以操作这个集合中的元素,这样在迭代过程中,我们遍历元素用的是Iterator,增加元素用的是集合对象al。此时,两个对象操作同一个集合的元素,就会产生安全隐患,比如,集合对象减掉一个元素,而迭代器确认为这个元素是存在的,这样就冲突了。
这时,我们想只用迭代器进行增删改查,但是迭代器只有三个方法,hasNext,next,remove,这三个方法不能满足我们所需要的。所以就用到了ListIterator,这时一个Iterator的子接口,这个子接口中有对元素的判断、取出、删除、添加、修改等操作,这时子接口就很实用了,满足我们的要求。
通过List中方法listIterator,获取这个接口的对象。具体程序如下:
ArrayListal = new ArrayList();
al.add("java01");
al.add("java02");
al.add("java03");
al.add("java04");
Listiterator it = al.listIterator();
while(it.hasNext()){
Objectobj = it.next();
if(obj.equals("java02"))
it.add("java009");
方法时这样的,注意的是ListIterator中,hasNext(正向遍历)和hasPrevious(逆向遍历)两个方法中指针的走向。不管两个方法哪个先用,指针都是在0角标为,这就意味着hasNext此时返回值为true,就是说指针后面有元素;而此时,hasPrevoius返回为false,就是说指针前面没有元素。只有当使用hasNext后,指针后移,然后再使用hasPrevious返回的结果才为true。当hasNext遍历完后,在使用hasPrevious就会逆向遍历了。
List集合具体对象的特点:
有一中的关系如可知,List包含以下三个具体的对象,各自有特点:
1、 ArrayList
底层数据结构是数组结构。特点是:查询更改很快,增删稍慢。线程不同步。
初始长度为10,需要改变的话,每次增加50%
2、 LinkedList
底层数据结构是链表结构(就是A-B-C-D-F-G-H,知道前者才知道后者)。特点是,增删很快,查询稍慢。线程不同步。
3、 Vector
底层数据结构是数组结构,线程同步。早于ArrayList,西安被ArrayList替代了。无论增删或查询都很慢。
初始长度为10,需要改变的话,每次增加100%
1)关于Vector
Vector中,凡是带elements都是它的特有方法。
他有一个特别的方法,elements(),它会返回一个Enumeration(枚举)的子类对象。
枚举就是Vector特有的取出方式,其实枚举和迭代器很像,但是因为枚举的名称和方法名都过长,所以逐渐被枚举取代了。
2)关于LinkedList
LinkedList特有方法:
addFirst();在链表头部加入一个元素
addLast();在链表尾部加入一个元素
getFirst();获取头部元素,但不删除
getLast();获取尾部元素,但不删除
获取元素,但不删除元素,如果集合中没有元素,会有NoSuchElementException
removeFirst();移除头部元素,同时删除元素
remoceLast();移除尾部元素,同时删除元素
获取元素,但是删除元素,如果集合中没有元素,会有NoSuchElementException
JDK1.6中出现的替代的方法
offerFirst();
offerLast();
peekFirst();
peekLast();
获取元素,但是不删除元素,如果集合中没有元素,返回null
pollFirst();
pollLast();
获取元素,但是元素被删除,如果集合中没有元素,返回null
在List编程时应注意的:
List集合判断元素是否相同,依据的是元素自身所有的equals方法,
判断元素,contains和remove方法在使用时,就是依据equals。比如contains,在判断是否包含的时候,会在底层调用元素自身所有的equals方法进行判断比较,而元素尤其是自定义元素都是继承自Object中的equals,比较的是对象的地址值,所以,即便是两个元素内容相同,也会判断为不同,因为地址值就不一样。所以,我们在自定义元素的时候,一定要复写equals方法。看一个自定义元素的内部方法:
class Person{
-----------------------省略部分代码---------------------
public boolean equals(Object obj){
if(!(objinstanceof Person))
returnfalse;
Person p = (Person)obj;
return this.name.equals(p.name) &&this.age==p.age;
}
}
注意equlas方法的复写
第二部分 Set
Collection又一子集合
一、 HashSet
Set集合,元素是无序的(存入和取出的顺序不一定一致),没有重复元素。Set集合的功能和Collection是一致的。
HashSet是Set下的子集合,其功能和List中的功能基本一致。HashSet:底层数据结构是哈希表。
了解HashSet,先要了解HashSet的内存机制。因为其元素是无序的,且没有重复元素。它在内存中这样存入元素的:
首先会判断每个元素的哈希值,如果哈希值不同,那么它就会根据哈希表,把地址值放入相应的位置上,这样即使先存入的元素可能也不会在前面,所以其取出和存入的顺序不一定一致。如果哈希值相同,那么它就会判断元素的内容是否相同,如果相同,那么就不会存入集合中,如果不同,就会在这个相同的哈希值下面顺延,存入这个元素。这样就保证了了元素的唯一性。
简单来说,HashSet如何保证元素的唯一性:
是通过元素的两个方法,hashCode和equals来完成。如果元素的hashCode值相同,才会判断equals是否为true。如果hashCode不相同,不会调用equals。
其实在思想上来说,和List中,我们想要去除相同元素的想法是一样的。在HashSet中,我们想要定义自己的元素,就要复写hashCode和equals方法,这样才会执行上面描述的判断。示例程序如下:
classPerson{
------------------省略部分代码---------------
public int hashCode (){
retrun name.hashCode( ) + age*11
}
publicboolean equals(Object obj){
if(!(obj instanceof Person))
return false;
Person p = (Person)obj;
return this.name.equals(p.name) &&this.age==p.age;
}
}
比较List和HashSet,
不同点:前者是在判断是否相同元素时,会用equals方法;后者是在添加的时候,就动用hashCode和equals方法,而且在判断元素是否相同时,先会调用hashCode方法,不同同则不调用equals方法,相同就调用equals方法。
相同点:都比较依赖元素自身的方法
二、TreeSet
TreeSet可以对元素进行自然排序。
当我们在给TreeSet中添加元素的时,会按照自然顺序进行排序。但如果我们存入自定义的元素,就会失败。这里就要解释下为什么:很简单,Tree要求存入的元素具备比较性,怎么样具备比较性,其实就是实现Comparable,而Comparable中只有一个方法,就是compareTo(Object obj),那么就会用到元素自身所有的compareTo方法了。注意返回值为int,大于就是正数,小于就是负数,等于就是零。
我们在自定义元素的时候,只要实现Comparable接口,实现其compareTo方法即可,在方法中定义判断条件。看示例程序:
class Person implements Comparable{
-------------------------省略部分代码---------------
publicint compareTo(Object obj){
if (obj instanceof Person)
throw newRuntimeException("class is wrong");
Person p = (Person)obj;
if(this.age>p.age)
return 1;
if(this.age==p.age)
return this.name.compareTo(p.name);
else
return -1;
}
上面就是具体的方法示例,但是要注意一点,当我们自定义元素的时候,可能会有多个判断条件,就是说,在TreeSet集合中,想要按我们自己要求的顺序打印,就要分清主次条件,当主要条件相同时,一定要判断次要条件。
在返回最初,我们想要了解的底层数据结构。
TreeSet底层数据结构,是一种二叉树结构。语言描述比较繁琐,直接上图:
已上图来描述,当最先存进去的是3,这是在二叉树顶部,然后存进2,它会先跟3比较,比3小,那么就放到3的左边。再存入1,又先跟3比,比3小,再跟3的左边比,也比2小,那么就放到2的左边。再存入7,先跟3比,比3大,放到3的右边。再存入5,先跟3比,比3大,再跟7比,比7小,放到7的左边。再存入8,比3和7都打,放到7的右边。一次类推。这就是二叉树结构。
当我们程序中进行判断时,TreeSet集合主要是看元素中,compareTo返回的值是正数、负数还是零。TreeSet集合取元素的时候,默认是从最左边开始取。因为这样,我们就可以控制二叉树的排序,比如我们把元素compareTo的返回值固定为1,那么元素就会向左排序,那么取出的时候就有了输入时候的顺序。
TreeSet排序的第一种方式:让元素自身具备比较性,元素需要实现Comparable接口,覆盖compareTo方法,这种方式成为元素的自然排序,或者叫做默认排序。
TreeSet排序的第二种方式:当自身元素不具备比较性时,或者具备的比较性不是所需要的,这时就让集合自身具备比较性。方式就是,定义一个比较器compartor,将比较器对象作为参数传递给TreeSet集合的构造函数。那我们就自定义一个比较器,实现Comparator,复写其中的compare方法,然后把比较器对象传入TreeSet集合的构造函数即可。
实现程序如下:
class MyCopare implementsComparator{
public int compare(Object o1,Object o2){
Person p1 =(Perosn)o1;
Person p2 =(Perosn)o2;
int num =p1.getName().compareTo(p2.getName());
if (num==0){
returnnew Integer(p1.getAge()).compareTo(new Integer(p2.getAge()));
}
return num;
}
}
既然TreeSet有两种排序方式,如果当两种排序方式都存在是,以比较器为主。
Collection总结:
1、 Collection是一个容器,它的特点是,只能存储对象,对象类型可变,长度可变。而数组可以存储对象,也可以存储基本数据类型,但只能存一种类型,长度固定。
2、 Collection作为父类,看其中的方法,建立子类对象即可使用其中的方法,那么这里就有共有的方法在整个Collection都可使用,进行增删改查判断的操作。
3、 我们要操作集合中元素,尤其是遍历元素,就用到迭代器Iterator,获取方法时当前集合的iterator方法,即可获得迭代器。使用迭代器中的hasNext和next即可遍历集合。值得注意的是,想在遍历过程中操作集合,那么在List集合中就用listIterator方法返回一个ListIterator对象,使用这个对象中的方法即可(这个对象中的方法和集合共性方法相似)。同时理解迭代器原理,其实是每个集合的内部类,Iterator是引用。
4、 Collection下有两个大的子类,分别为List和Set。
List:有序集合,元素可以重复,有索引。
Set:无序集合,元素不可以重复
5、 List中子集合:
ArrayList:
底层数据结构是数组结构。特点是:查询更改很快,增删稍慢。线程不同步。
初始长度为10,需要改变的话,每次增加50%
LinkedList:
底层数据结构是链表结构(就是A-B-C-D-F-G-H,知道前者才知道后者)。特点是,增删很快,查询稍慢。线程不同步。
Vector:
底层数据结构是数组结构,线程同步。早于ArrayList,西安被ArrayList替代了。无论增删或查询都很慢。
初始长度为10,需要改变的话,每次增加100%
重点是:List集合在进行元素的判断的时候,要复写equals方法。
6、 Set中的子集合:
HashSet:底层数据结构是哈希表。HashSet中元素无序,是因为它是根据元素地址值来存入内存中的,而地址值就是哈希值,每个元素的哈希值不一样,所以存入顺序和输出顺序会不一致。
为了保证集合元素的唯一性,再加入元素时,HashSet会先判断hashCode的值是否相同,如果不同则存入,如果相同在equals方法判断,不同存入、相同排除。所以如果我们自定义元素,最好复写hashCode和equals方法。
TreeSet:底层数据结构是一种二叉树结构。
为了保证元素的唯一性,TreeSet集合也会对元素进行比较,按自然顺序排序。但这里要求元素具有可比较性,就里就涉及到两种排序方式。
一、让元素自身具备比较性,元素需要实现Comparable接口,覆盖compareTo方法,这种方式成为元素的自然排序,或者叫做默认排序。
二、当自身元素不具备比较性时,或者具备的比较性不是所需要的,这时就让集合自身具备比较性。方式就是,定义一个比较器compartor,将比较器对象作为参数传递给TreeSet集合的构造函数。那我们就自定义一个比较器,实现Comparator,复写其中的compare方法,然后把比较器对象传入TreeSet集合的构造函数即可。
注意compareTo和compare方法返回的都是int型值。