1、概述
Java中的集合就像一个容器,专门用来存储Java对象(实际上是对象的引用,但习惯上称为对象),这些对象可以是任意的数据类型,并且长度可变。其中,这些集合都位于java.util包中。集合按照其储存结构可以分为两大类,
Collection:单列集合的根接口,用于储存符合一系列符合某种规则的元素。
Map:双列集合的根接口,用于储存具有键(Key)、值(Value)映射关系的元素。
2、Collection接口
方法声明 | 功能描述 |
boolean add(Object o) | 向集合中添加一个元素 |
boolean addAll(Collection c) | 将指定集合c中的所有元素添加到该集合中 |
void clear() | 删除该集合中所有元素 |
boolean remove(Object o) | 删除该集合中指定的元素 |
boolean removeAll(Collection c) | 删除该集合中包含指定集合c中的所有元素 |
boolean isEmpty() | 判断该集合是否为空 |
boolean contains(Object o) | 判断该集合中是否包含某个元素 |
boolean containsAll(Collection c) | 判断该集合中是否包含指定集合c中的所有元素 |
Iterator iterator() | 返回在该集合的元素上进行迭代的迭代器(Iterator),用于遍历该集合所有元素 |
int size() | 获取该集合元素个数 |
Stream<E> stream() | 将集合源转换为有序元素的流对象(JDK8新增) |
3、List接口
List作为Collection集合的子接口,不但继承了Collection接口中的全部方法,而且还增加了一些操作集合的特有方法。如下
3.1、List接口简介
方法声明 | 功能描述 |
void add(int index,Object element) | 将元素element插入在List集合的指定索引位置 |
boolean addAll(int index,Collection c) | 将元素c包含的所有元素插入到List集合的指定索引位置 |
Object get(int index) | 返回集合索引index处的元素 |
Object remove(int index) | 删除index索引出的元素 |
Object set(int index,Object element) | 将索引index处元素替换成element元素,并将替换后的元素返回 |
int indexOf(Object o) | 返回对象o在List集合中首次出现的位置索引 |
int lastIndexOf(Object c) | 返回对象o在List集合中最后一次出现的位置索引 |
List subList(int fromIndex,int toIndex) | 返回从索引fromIndex(包括)到toIndex(不包括)处所有元素集合组成的子集和 |
Object[] toArray(0 | 将集合元素转换为数组 |
default void sort(Comparator<?super E> c) | 根据指定的比较器规则对集合元素排序(JDK8新增) |
3.2、ArrayList集合
ArrayList是List接口的一个实现类,它是程序中最常见的一种集合。在ArrayList内部封装了一个长度可变的数组对象,当存入的元素超过数组长度时,ArrayList会在内存中分配一个更大的数组来储存这些元素,因此可以将ArrayList集合看作一个长度可变的数组。
正是由于ArrayList内部的数据储存结构是数组形式,在增加或删除指定位置的元素时,会创建新的数组,效率比较低,因此不适合较大的增删操作,但是,这种数组结构允许程序通过索引方式来访问元素,因此使用ArrayList集合在遍历和查找元素时显得非常高效。
ArrayList集合大部分方法都是从接口Collection和List继承过来的;例子
package demo; import java.util.ArrayList; public class Example01 { public static void main(String[] args){ ArrayList List = new ArrayList();//创建ArrayList数组 List.add("stu1"); List.add("stu2"); List.add("stu3"); List.add("stu4"); System.out.println("集合的长度:"+List.size()); System.out.println("第二个元素是:"+List.get(1)); } }
结果:
3.3、LinkedList集合
ArrayList集合在查询元素时速度很快,但在增删元素时效率较低,为了克服这种局限性,可以使用List接口的另一个实现类LinkedList。该集合内部包含有两个Node类型的first和last属性维护一个双向循环链表,链表中的每一个元素都使用引用的方式来记住它的前一个元素和后一个元素,从而可以将所有元素彼此连接起来。当插入一个新元素时,只需要修改元素之间的这种引用关系即可,删除一个节点也是如此。正因为这样的存储结构,所以LinkedList集合对于元素的增删操作表现出很高的效率。LInkedList集合如下图:
LinkedList集合除了从接口Collection和List中继承并实现了集合操作方法外,还专门针对元素的增删操作定义了一些特有的方法:
方法声明 | 功能描述 |
---|---|
void add(int index,E element) | 在此列表中指定的位置插入指定的元素 |
void addFirst(Object o) | 将指定元素插入集合的开头 |
void addLast(Dbject o) | 将指定元素添加到集合的结尾 |
Object getFirst() | 返回集合的第一个元素 |
Object getLast() | 返回集合的最后一个元素 |
Object removeFirst() | 移除并返回集合的 第一个元素 |
Object removeLast() | 移除并返回集合的最后一个元素 |
boolean offer(Object o) | 将指定元素添加到集合的结尾 |
boolean offerFirst(Object o) | 将指定元素添加到集合的开头 |
boolean offerLast(Object o) | 将指定元素添加到集合的结尾 |
Object peek() | 获取集合的第一个元素 |
Object peekFirst() | 获取集合的第一个元素 |
Object peekLast() | 获取集合的最后一个元素 |
Object poll() | 移除并返回集合的第一个元素 |
Object pollFirst() | 移除并返回集合的第一个元素 |
Object pollLast() | 移除并返回集合的最后一个元素 |
void push(Object o) | 将指定元素添加到集合的开头 |
Object pop() | 移除并返回集合的第一个元素 |
接下来用一个例子来说明表中部分方法的使用:
package demo;
import java.util.LinkedList;
public class Example02 {
public static void main(String[] args){
LinkedList link = new LinkedList();//创建LinkedList集合
link.add("stu1");//添加元素
link.add("stu2");
link.addFirst("stu0");//将指定元素添加到集合开头
System.out.println(link);//输出集合中的元素
link.offer("offer");//向集合尾部追加元素
link.push("push");
System.out.println(link);
Object object = link.peek();//获取集合第一个元素
System.out.println(object);//输出集合中的元素
link.removeFirst();//删除集合第一个元素
link.pollLast();//删除集合最后一个元素
System.out.println(link);
}
}
结果:
由此可见,使用LinkedList对元素进行增删操作是非常便捷的。
4、Collection遍历集合
本节将以前面学习的List集合为例,来对Collection集合的几种遍历方式进行详细讲解。
4.1、Iterator遍历集合
Iterator接口是Java集合框架中的一员,但它与Collection、Map接口有所不同,Collection接口与Map接口主要用于储存元素,而Iterator主要用于迭代访问(即遍历)Collection中的元素,因此Iterator对象也被称为迭代器。
Iterator迭代器例子:
package demo;
import java.util.ArrayList;
import java.util.Iterator;
public class Example03 {
public static void main(String[] args){
ArrayList List = new ArrayList();//创建ArrayList集合
List.add("data_1");//向集合中添加字符串
List.add("data_2");
List.add("data_3");
Iterator iterator = List.iterator();//获取Iterator对象
while(iterator.hasNext()){//判断集和中是否存在下一个元素
Object obj = iterator.next();//取出ArrayList集合中的元素
System.out.println(obj);
}
}
}
结果:
Iterator遍历集合的过程中。首先通过调用ArrayList集合的iterator()方法获得迭代器对象,然后使用hasNext()方法判断集合中是否存在下一个元素,如果存在,则调用next()方法将元素取出,否则说明已经到达了集合末尾,停止遍历元素。注意:在用next()方法遍历元素时,一定要保证获取的元素存在,否则,会抛出NoSuchElementException异常。
图例:
在调用Iterator的next()方法之前,迭代器的索引位于第一个元素之前,不指向任何元素,当第一次调用迭代器的next()方法后,迭代器的索引会向后移动一位,指向第一个元素并将该元素返回,当再次调用next()方法时,迭代器的索引会指向第二个元素并将该元素返回,依此类推,直到hasNext()方法返回false, 表示到达了集合的末尾,终止对元素的遍历。
4.2、foreach遍历集合
虽然Iterator可以用来遍历集合中的元素,但是书法上比较繁琐,因此从JDK5引入了foreach循环。foreach循坏是一种更简洁的for循环,也称增强for循环。
语法格式:for(容器元素类型 临时变量 :容器存量){
//执行语句
}
与for循环相比,foreach循环不需要获得容器的长度,也不需要根据索引访问容器中的元素,但它会自动遍历容器中的每个元素。如:
package demo;
import java.util.ArrayList;
public class Example04 {
public static void main(String[] args){
ArrayList List = new ArrayList();
List.add("data_1");
List.add("data_2");
List.add("data_3");
List.add("data_4");
for(Object obj : List){
System.out.println(obj);
}
}
}
结果:
从上面可以看出,foreach循环遍历集合的语法非常简洁,没有循环条件,也没有迭代语句,所有的这些工作都交给JVM去执行了。foreach循环的次数是由容器中元素的个数决定的。
注意1:虽然 foreach循环遍历集合的语法写起来非常简洁,但是在使用时也存在一定的局限性,当使用foreach循环遍历数组时,只能访问集合中的元素,不能对其中的元素进行修改,接下来以一个String类型的数组为例来进行演示:
package demo;
public class Example05 {
static String[] strs = {"aaa","bbb","ccc"};
public static void main(String[] args){
for(String str : strs){//foreach循环遍历数组
str="ddd";
}
System.out.println("foreach循环遍历后的数组:"+strs[0]+","+strs[1]+","+strs[2]);
for(int i=0;i<strs.length;i++){//for循环遍历数组
strs[i] = "ddd";
}
System.out.println("普通for循环遍历后的数组:"+strs[0]+","+strs[1]+","+strs[2]);
}
}
结果:
foreach循环并不能修改数组中的元素,原因是代码“str=ddd”只是将临时变量str指向了一个新的字符串,这和数组中的元素没有一点关系,而在普通for循环中,是可以通过索引的方式来引用数组中的元素并将其值进行修改的。
注意2:
在使用Iterator迭代器对集合中的元素进行迭代时,如果调用了集合对象的remove()方法去删除元素,会出现异常;例如:
结果:
这个异常时迭代器对象抛出的,出现这个异常的原因是集合中删除了元素会导致迭代器预期的迭代次数发生改变,导致迭代器的结果不准确。
解决方法:
1、
if("Annie".equals(object)){//判断该集合中的元素是否为Annie
List.remove(object);//删除该集合中的元素
break;
}
在18行代码下面增加一个break语句;break跳出循环后,由于没有继续使用迭代器对集合中的元素进行迭代,因此,集合中删除元素对程序没有任何影响。删除该元素,只需找到该元素后跳出循环不再迭代即可;至于后面多少元素我们并不关心。
2、
if("Annie".equals(object)){//判断该集合中的元素是否为Annie
it.remove();//删除该集合中的元素
}
迭代器本身的删除方法;18行代码换成it.remove()即可;
结果:
4.3、JDK8的forEach遍历集合
JDK8中,根据Lambda表达式特性增加一个forEach(Consumer action)方法来遍历集合,该方法所需要的参数是一个函数式接口。
package demo;
import java.util.ArrayList;
public class Example07 {
public static void main(String[] args){
ArrayList List = new ArrayList();
List.add("data_1");
List.add("data_2");
List.add("data_3");
System.out.println(List);
//使用JDK8增加的forEach(Consumer action)方法遍历集合
List.forEach(obj->System.out.println("迭代集合元素:"+obj);
}
}
另外,JDK8中,还针对Iterator迭代器对象提供了一个forEachRemaining(Consumer action)方法来遍历集合,该方法同样需要一个函数式接口。
package demo;
import java.util.ArrayList;
import java.util.Iterator;
public class Example08 {
public static void main(String[] args){
ArrayList List = new ArrayList();
List.add("data_1");
List.add("data_2");
List.add("data_3");
System.out.println(List);
Iterator it = List.iterator();//将集合转换为Iterator迭代器对象
//使用JDK8增加的forEachRemaining(Consumer action)方法遍历集合
it.forEachRemaining(obj->System.out.println("迭代集合元素:"+obj);
}
}
两种程序的结果完全相同,都可以正确遍历出集合元素 。
5、Set集合
5.1、Set接口简介
Set接口与List接口一样,同样继承自Collection接口,它与Collection接口中的方法基本一致,并没有Collection接口进行功能上的扩充,只是比Collection接口更加严格;与List接口不同的是,Set接口中的元素无序,并且都会以某种规则保证存入的元素不出现重复。
Set接口的主要两个实现类:
HashSet是根据对象的哈希值来确定元素在集合中的储存位置,具有良好的存取和查找性能。
TreeSet是以二叉树的方式来存储元素,它可以实现对集合中的元素进行排序。
5.2、HashSet集合
HashSet是Set接口的一个实现类,它所存储的元素是不可重复的,并且元素都是无序的。当向HashSet集合中添加一个元素时,首先会调用该元素的hashCode()方法来确定元素的存储位置.然后再调用元素对象的equals(方法来确保该位置没有重复元素。Set 集合与List集合存取元素的方式都一样。
package demo;
import java.util.HashSet;
public class Example09 {
public static void main(String[] args){
HashSet set = new HashSet();
set.add("Jack");
set.add("Eve");
set.add("Rose");
set.add("Rose");//向该Set集合中添加重复元素
set.forEach(o->System.out.println(o));//遍历输出Set集合中的元素
}
}
HashSet确保存入数据不重复,流程图如下: