Java中的集合类-详解
集合概述
JDK中提供了一些特殊的类,这些类可以储存任意类型的对象,并且长度可变,在Java中这些类统称为集合。
按照存储结构可以分为两大类,单列集合Collection和双列集合Map,这两种集合特点如下
- Collection:单列集合类的根接口,用于存储一些列符合某种规则的元素,有两个重要的子接口,分别是List和Set。List特点为有序、可重复。Set特点元素无序不可重复。List接口的主要实现类有ArrayList和LinkList,Set接口的主要实现类有HashSet和TreeSet。
- Map:双列集合的根接口,用于存储具有键值关系的元素,每个元素包含一对键值,可以通过指定的key找到对应的value,Map接口的主要实现类有HashMap和TreeMap。
下面用图来描述其继承体系:
Collection接口中的方法
方法声明 | 功能描述 |
---|---|
boolean add(Object o) | 向集合中添加一个元素 |
boolean addAll(Collection c) | 将指定的Collection中的所有元素添加到该集合中 |
void clear | 删除该集合中的所有元素 |
boolean remove(Object o) | 删除该集合中指定的元素 |
boolean removeAll(Collection c) | 删除该集合中的所有元素 |
boolean isEmpty() | 判断该集合是否为空 |
boolean contains(Object o) | 判断该集合是否包含某个元素 |
boolean containsAll(Collection c) | 判断该集合是否包含某个集合中的所有元素 |
Iterator iterator() | 返回在该集合的元素上进行迭代的迭代器,用于遍历该集合的所有元素 |
int size() | 获取该集合元素个数 |
List接口
List集合特点:线性存储,有序
不但继承了Collection接口中的全部方法,而且还增加了一些根据元素索引来操作集合的特有方法,如下表所示:
方法声明 | 功能描述 |
---|---|
void add(int index,Object element) | 将元素element插入在List集合的index处 |
void addAll(int index,Collection c) | 将集合c所包含的所有元素插入到List集合的index处 |
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 o) | 返回对象o在List集合中最后一次出现的位置索引 |
List subList(int fromIndex,int toIndex) | 返回从索引fromIndex(包括)到toIndex(不包括)处所有元素组成的子集合 |
ArrayList集合
ArrayList是List接口的一个实现类,是程序中一种常见的集合。下面通过一个案例学习ArrayList集合如何存取元素:
import java.util.ArrayList;
public class c6_1 {
public static void main(String[] args) {
ArrayList list=new ArrayList();//创建ArrayList集合
list.add("stu1");
list.add("stu1");//List集合元素有序可重复
list.add("stu2");
list.add("stu3");
System.out.println("集合长度"+list.size());
System.out.println("第一个元素是"+list.get(1));
}
}
菜鸟Java在线工具:https://c.runoob.com/compile/10
使用ArrayList集合查找元素很便捷
LinkedList集合
前面说了ArrayList在查询元素时速度很快,但在增删元素时效率较低。为了克服这种局限性,我们可以用另一个实现类LinkedList,该集合内部维护了一个双向循环链表,链表中每一个元素都使用引用的方式来记住它前后的元素,从而将所有元素彼此连接起来,对元素进行增删时,我们只需要修改这个引用关系即可,具有增删元素效率高的特点。
LinkedList中定义的方法:
方法声明 | 功能描述 |
---|---|
void add(int index,E element) | 指定位置插入指定元素 |
void addFirst(Object o) | 将指定元素插入列表开头 |
void addLast(Object o) | 将指定元素插入列表结尾 |
Object getFirst() | 返回列表第一个元素 |
Object getLast() | 返回列表最后一个元素 |
Object removeFirst() | 移除并返回列表的第一个元素 |
Object removeLast() | 移除并返回列表的最后一个元素 |
案例学习: |
import java.util.LinkedList;
public class c6_2 {
public static void main(String[] args) {
LinkedList link=new LinkedList();//创建集合
link.add("stu1");//添加元素
link.add("s2");
link.add("s3");
link.add("s4");
System.out.println(link.toString());
link.add(3, "add3");//指定位置插入元素,第一个索引位置为0
link.addFirst(1);//集合第一个位置插入元素
System.out.println(link);
System.out.println(link.getFirst());//取出集合中第一个元素
link.remove(3);//移除指定位置元素
link.removeFirst();//移除第一个元素
System.out.println(link);
}
}
输出如下:
[stu1, s2, s3, s4]
[1, stu1, s2, s3, add3, s4]
1
[stu1, s2, add3, s4]
Iterator接口
Iterator主要用于迭代访问(即遍历)Collection中的元素,因此Iterator对象也被成为迭代器。
参考案例:
import java.util.ArrayList;
import java.util.Iterator;
public class c6_3 {
public static void main(String[] args) {
ArrayList list=new ArrayList();
list.add("data1");
list.add("data2");
list.add("data3");
list.add("data4");
Iterator it=list.iterator();//获取iterator对象
while(it.hasNext()) {//判断是否存在下一个元素
Object obj=it.next();//取出集合中的元素
System.out.println(obj);
}
}
}
DK5.0新特性-foreach循环
虽然Iterator可以用来遍历集合中的元素,但写法上比较繁琐,为了简化书写,从JDK5.0开始提供了foreach循环,是一种更加简洁的for循环,也叫增强for循环,具体语法格式如下:
for(容器元素类型 临时变量:容器变量){执行语句}
案例如下:
import java.util.ArrayList;
public class c6_4 {
public static void main(String[] args) {
ArrayList list=new ArrayList();
list.add("data1");
list.add("data2");
list.add("data3");
for(Object obj:list) {
System.out.println(obj);
}
}
}
注1:foreach只能遍历集合或数组中的元素,并不能对其进行修改,在上述的基础上我们对其进行修改:
public static void main(String[] args) {
ArrayList list=new ArrayList();
list.add("data1");
list.add("data2");
list.add("data3");
for(Object obj:list) {
obj="tom";
}
System.out.println(list.get(0));////foreach循环只能遍历数组或者集合,不能对其中的元素进行修改
}
运行结果不是tom,而是data1
原因是临时变量obj指向了一个心字符串tom,这和集合中的元素没有一点关系
注2:使用Iterator迭代器对集合中的元素进行迭代时,如果调用集合对象的remove()删除元素之后,继续使用迭代器会出现异常,如下面案例所示:
import java.util.ArrayList;
import java.util.Iterator;
public class c6_6 {
public static void main(String[] args) {
ArrayList list=new ArrayList();
list.add("data1");
list.add("data2");
list.add("data3");
list.add("data4");
Iterator it=list.iterator();
while(it.hasNext()){
Object obj=it.next();
if("data2".equals(obj)) {
list.remove(obj);
}
}
System.out.println(list);
}
}
抛出异常如下:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at chapter6.c6_6.main(c6_6.java:15)
原因是删除了集合中删除了元素会导致迭代器预期的迭代次数发生改变导致迭代器结果不准
为了解决上述问题,我们可以使用以下两种方式
- 第一种方式
在如下位置添加break:
if("data2"equals(obj)){
list.remove(obj);
break;
}
- 第二种方式
使用迭代器本身的删除方法,如下:
if("data2".equals(obj)){
it.remove();
}
思考:
其实在该述案例中,不使用上述两种方式,如果添加的元素是三个,会正常运行,是不会抛出异常的,有读者知道是为什么吗?欢迎在评论区留言
Set接口
Set集合特点:元素无序,且以某种规则保证存入的元素不会重复
HashSet
HashSet集合添加一个对象时,会调用hashCode()方法来计算对象的哈希值,从而确定元素位置,如果哈希值相同,再调用对象的equals()方法来确保该位置没有重复元素
直接上案例:
import java.util.HashSet;
import java.util.Iterator;
public class c6_9 {
public static void main(String[] args) {
HashSet set =new HashSet();
set.add("data1");
set.add("data1");//重复元素会被舍弃,遍历时只会输出一个data1
set.add("data2");
set.add("data3");
set.add("data4");
Iterator it=set.iterator();
while(it.hasNext()) {
Object obj=it.next();
System.out.println(obj);
}
}
}
String类已经重写了hashCode()和equals()方法,如果将Student对象存入HashSet结果又如何呢,观看下面案例:
View Code
import java.util.HashSet;
public class c6_10 {
public static void main(String[] args) {
HashSet hs=new HashSet();
Student s1=new Student("1","Jack");
Student s2=new Student("2","Rose");
Student s3=new Student("2","Rose");
hs.add(s1);
hs.add(s2);
hs.add(s3);
System.out.println(hs);
}
}
class Student{
String id;
String name;
public Student(String id, String name) {
super();
this.id = id;
this.name = name;
}
@Override
public String toString() {
return id+":"+name;
}
//运行结果:[2:Rose, 1:Jack, 2:Rose]
//之所以会打印两个Rose,是因为Student类中没有对hashCode()和equals()方法进行重写
}
接下来我们对Student类中的hashCode()和equals()方法进行重写,案例如下:
View Code
import java.util.HashSet;
public class c6_11 {
public static void main(String[] args) {
HashSet hs=new HashSet();
Stu s1=new Stu("1","Jack");
Stu s2=new Stu("2","Rose");
Stu s3=new Stu("2","Rose");
hs.add(s1);
hs.add(s2);
hs.add(s3);
System.out.println(hs);
}
}
class Stu{
String id;
String name;
public Stu(String id, String name) {
super();
this.id = id;
this.name = name;
}
@Override
public String toString() {
return id+":"+name;
}
public int hashCode() {
return id.hashCode();
}
public boolean equals(Object obj) {
if(this==obj) {
return true;
}
Stu s=(Stu) obj;
boolean b=this.id.equals(s.id);
return b;
}
}
//重写了hashCode()和equals()方法,当HashSet调用add()方法添加s3对象时,发现它的哈希值与s2相同,且s2.equals(s3)返回true,HashSet集合两个对象相同,因此重复的对象就被去除了
输出:[1:Jack, 2:Rose]
Map
Map接口
简介:是一种双列集合, 每个元素包含一对键值,键值直接一一对应。
Map集合常用的方法表:
方法声明 | 功能描述 |
---|---|
void put(Object key,Object value) | 将指定的值与映射中的指定键关联 |
Object get(Object key) | 返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回null |
boolean containsKey(Object key) | 如果此映射包含指定键的映射关系,返回true |
boolean containsValue(Object value) | 如果此映射将一个或多个键映射到指定值,则返回true |
Set keySet | 返回此映射中包含的键的Set视图 |
Collection |
返回此映射关系中包含的值的Collectiono视图 |
Set<Map.Entry<K,V>>entrySet() | 返回此映射包含的映射关系的Set视图 |
HashMap
案例如下:
import java.util.HashMap;
import java.util.Map;
public class c6_15 {
public static void main(String[] args) {
Map map=new HashMap();
map.put("1", "Jack");
map.put("2", "Rose");
map.put("3", "Lucy");
System.out.println(map.get("1"));
System.out.println(map.get("2"));
System.out.println(map.get("3"));
}
}
输出:
Jack
Rose
Lucy
如果再添加map.put("3", "Marry");
,那么Lucy会被Marry覆盖(键相同,值覆盖)
遍历Map集合中所有的键值对,有两种方式,方式一:先遍历Map集合中的所有键,再根据键获取对应的值,案例:
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class c6_16 {
public static void main(String[] args) {
Map map=new HashMap();//创建Map集合
map.put("1", "Jack");//存储键和值
map.put("2", "Rose");
map.put("3", "Lucy");
Set ks=map.keySet();//获取键的集合
Iterator it=ks.iterator();//迭代键的集合
while(it.hasNext()) {
Object key=it.next();
Object value=map.get(key);//获取每个键所对应的值
System.out.println(key+":"+value);
}
}
}
遍历键值对,方式二:先获取集合中所有的映射关系, 然后从映射关系中取出键和值,案例:
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class c6_17 {
public static void main(String[] args) {
Map map=new HashMap();
map.put("1", "Jack");
map.put("2", "Rose");
map.put("3", "Lucy");
Set entrySet=map.entrySet();
Iterator it=entrySet.iterator();
while(it.hasNext()) {
Map.Entry entry=(Map.Entry) (it.next());//获取键值对映射
Object key=entry.getKey();//获取Entry中的键
Object value=entry.getValue();//获取Entry中的值
System.out.println(key+":"+value);
}
}
}
在Map中还提供了一个values()方法,通过这个方法可以直接获取Map中存储的所有值的Collection集合,案例:
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class c6_18 {
public static void main(String[] args) {
Map map=new HashMap();
map.put("5", "Jack");
map.put("2", "Rose");
map.put("3", "Lucy");
Collection values=map.values();
Iterator it=values.iterator();
while(it.hasNext()){
Object value=it.next();
System.out.println(value);
}
}
}
输出:
Rose
Lucy
Jack
根据观察,输出的值的顺序是根据键从小到大排序的,如果要按添加顺序进行输出那么可以使用LinkedHashMap
LinkedHashMap,是HashMap的子类,与LinkedList一样,它也使用双向链表来维护内部元素的关系,使Map元素迭代的顺序与存入的顺序一致,案例:
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
public class c6_19 {
public static void main(String[] args) {
Map map=new LinkedHashMap();
map.put("1", "Jack");
map.put("2", "Rose");
map.put("3", "Lucy");
Set keySet=map.keySet();
Iterator it=keySet.iterator();
while(it.hasNext()) {
Object key=it.next();
Object value=map.get(key);//获取每个键对应的值
System.out.println(key+":"+value);
}
}
}
Properties集合
Map接口中还有一个实现类Hashtable,它和hashMap十分相似,区别在于Hashtable线程是安全的,它有一个子类十分重要,那就是Properties
Properties主要用于存储字符串类型的键和值,在实际开发中,经常使用Properties集合来存取应用的配置项,案例:
import java.util.Enumeration;
import java.util.Properties;
public class c6_20 {
public static void main(String[] args) {
Properties p=new Properties(); //创建Properties对象
p.setProperty("Background-color", "red");
p.setProperty("Font-size", "14px");
p.setProperty("Language", "zh-hans");
Enumeration names=p.propertyNames();
while(names.hasMoreElements()) { //遍历循环所有的键
String key=(String)names.nextElement();
String value=p.getProperty(key); //获取键对应的值
System.out.println(key+"="+value);
}
}
}
JDK5.0新特性——泛型
集合可以存储任何类型的对象,但是存入对象后,会“忘记”对象的类型,这个对象的编译类型就会自动编程Object类型,在强制转换类型时就会出错,案例:
import java.util.ArrayList;
public class c6_22 {
public static void main(String[] args) {
ArrayList list=new ArrayList();
list.add("String");
list.add("Collection");
list.add(1);
for(Object obj:list) {
String str=(String) obj;
}
}
}
运行后,就会报错:Exception in thread "main" java.lang.ClassCastException
list集合中存入了3个元素,分别是2个字符串,1个证书,取出时我们都将其强制转换为String类型,但是Integer对象无法转换为String类型,因此会报出上诉错误。为了解决该问题,Java中引入了“参数化类型”这个概念,即泛型。它可以限定方法操作的数据类型,在定义集合类时,使用<参数化类型>
的方式指定该类中方法操作的数据类型,格式如下:
ArrayList<参数化类型> list=new ArrayList<参数化类型>();
那么上述案例相应代码改为:ArrayList<String> list=new ArrayList<String>();
修改过后的案例:
import java.lang.reflect.Array;
import java.util.ArrayList;
public class c6_22 {
public static void main(String[] args) {
ArrayList<String> list=new ArrayList<String>();
list.add("String");
list.add("Collection");
for(String str:list) {
System.out.println(str);
}
}
}
这样ArrayList
文章到此暂告一段落,博文还有一些不足,比如过长的代码应该弄一个折叠,影响读者的阅读体验,后续会再更新整理一下
参考
参考自《Java基础案例教程》一书
注:原创博文,禁止转载。