一、集合框架概述
1.引入集合框架
申明一个50长度的数组来存储数据的缺陷:
- 数组长度固定不变,不能很好地适应元素数量动态变化的情况。若要存储大于50个元素,则数组长度不足;若只存储20长度的数据,则造成内存空间浪费。
- 虽然可通过
数组名.length
获取数组的长度,却无法直接获取数组中真实存储的狗狗个数。 - 数组采用在内存中分配连续空间的存储方式,根据下标可以快速获取对应的信息,但是根据信息查找的时候效率低下,需要多次比较。在进行频繁插入、删除操作时同样效率低下。
2.Java集合框架包含的内容
Java集合框架为我们提供了一套性能优良、使用方便的接口和类,它们都位于java.util
包中。
集合框架包括三大块内容:对外接口、接口实现和对集合运算的算法
。
Java集合框架简图:
- 接口:表示集合的抽象数据类型,如:
Collection, List, Set, Map, Iterator
。 - 实现:集合框架中接口的具体实现,如:
ArrayList, LinkedList, HashMap, TreeMap, HashSet, TreeSet
。 - 算法:在实现了的接口对象身上完成某种有用的计算方法,例如:查询、排序等。Java 提供了进行集合操作的工具类
Collections (注意不是Collection)
,为我们提供了对集合进行排序等多种算法实现。使用时查看 JDK 帮助文档。
上图中可以看出Java 集合框架中的两大类接口:Collection 和 Map
。Collection
又有两个子接口:List 和 Set
。通常说Java集合框架共有三大类接口:List, Set 和 Map
。共同点:都是集合接口,都可以用来存储很多对象。区别如下:
-
Collection
接口存储一组不唯一(允许重复)、无序的对象。 -
Set
接口继承Collection
接口,存储一组唯一(不允许重复)、无序的对象。 -
List
接口继承Collection
接口,存储一组不唯一(允许重复)、有序(以元素插入的次序来放置元素,不会重新排列)的对象。 -
Map
接口存储一组成对的键-值
对象,提供key-value
映射。key
不要求有序,不允许重复。value
同样不要求有序,但允许重复。 -
Iterator
接口是负责定义访问和遍历元素的接口。
List
可以理解为数组,元素内容可以重复并且有序。 Set
可以理解为数学中的集合,数据不重复并且无序。
Map
可以理解为数学中的集合,只是其中每个元素都由key-value
两个对象组成。
二、List接口
ArrayList 对数组进行了封装,实现了长度可变的数组。ArrayList 存储数据的方式和数组相同,都是在内存中分配连续的空间,如下图。它的优点在于遍历元素和随机访问元素的效率比较高。
LinkedList 采用链表存储方式,如下图所示,优点在于插入、删除元素时效率比较高。他提供了额外的addFirst(), addLast(), removeFirst(), removeLast()
等方法,这些方法使得 LinkedList 可被用作堆栈(stack)或者队列(queue)。
1.ArrayList集合类
ArrayList 接口中定义的各种常用方法如下表:
方法名称 | 说明 |
| 在列表末尾顺序添加元素,起始索引位置从0开始 |
| 在指定的索引位置添加元素,原索引位置及其后面的元素依次后移 注意: |
| 返回列表中的元素个数 |
| 返回指定索引位置处的元素 |
| 判断列表中是否存在指定元素 |
| 从列表中删除元素 |
| 从列表中删除指定位置元素 注意: |
示例如下:
import java.util.ArrayList;
import java.util.List;
/**
* @author 天道酬勤
*/
public class ArrayListDemo {
public static void main(String[] args) {
// 创建4个学生对象,Student类的属性:int age, String stuName
Student s1 = new Student("s1", 18);
Student s2 = new Student("s2", 17);
Student s3 = new Student("s3", 16);
Student s4 = new Student("s4", 15);
// 创建ArrayList集合对象并把4个对象放入其中
List<Student> students = new ArrayList<>();
students.add(s1);
students.add(s2);
students.add(s3);
// 添加s4到下标为2的指定位置
students.add(3, s4);
// 输出集合中Student对象数量
System.out.println("集合中共有" +
students.size() + "个Student对象");
// 通过遍历集合显示各条信息
System.out.println("初始Student信息:");
for (int i = 0; i < students.size(); i++) {
Student s = students.get(i);
System.out.println(s.getStuName() +
"\t" + s.getAge());
}
// 删除下标为0的Student对象(第一条信息)
students.remove(0);
// 删除指定的Student对象
students.remove(s4);
// 显示删除后的列表信息
System.out.println("删除后集合中共有" +
students.size() + "个Student对象");
for (int i = 0; i < students.size(); i++) {
Student s = students.get(i);
System.out.println(s.getStuName() +
"\t" + s.getAge());
}
// 判断集合中是否包含指定的信息
if (students.contains(s3)) {
System.out.println("s3 is in ArrayList");
} else {
System.out.println("s3 is not in ArrayList");
}
}
}
运行结果如下图所示:
3.LinkedList集合类
LinkedList 除了上表中列出的各种方法外还包括一些特殊的方法,如下表所示:
方法名称 | 说明 |
| 在列表的首部添加元素 |
| 在列表的末尾添加元素 |
| 返回列表中的第一个元素 |
| 返回列表中的最后一个元素 |
| 删除并返回列表中的第一个元素 |
| 删除并返回列表中的最后一个元素 |
示例代码如下:
import java.util.LinkedList;
/**
* @author 天道酬勤
*/
public class LinkedListDemo {
public static void main(String[] args) {
// 创建4个学生对象,Student类的属性:int age, String stuName
Student s1 = new Student("s1", 18);
Student s2 = new Student("s2", 17);
Student s3 = new Student("s3", 16);
Student s4 = new Student("s4", 15);
// 创建LinkedList集合对象并把多个Student对象放入其中
LinkedList<Student> students = new LinkedList<>();
students.add(s1);
students.add(s2);
students.addFirst(s3);
students.addLast(s4);
// 查看第一个Student对象的名字
System.out.println("第一个对象的stuName:" +
students.getFirst().getStuName());
// 查看最后一个Student对象的名字
System.out.println("最后一个对象的stuName:" +
students.getLast().getStuName());
// 删除第一个Student对象和最后一个Student对象
students.removeFirst();
students.removeLast();
// 显示删除后集合中的信息
System.out.println("删除后的LinkedList信息");
for (int i = 0; i < students.size(); i++) {
System.out.println(students.get(i).getStuName() +
"\t" + students.get(i).getAge());
}
}
}
三、Set接口
Set 接口可以存储一组唯一、无序
的对象。
常用实现类有HashSet
。
1.HashSet类动态存储数据
在数据量大的情况下,要查找某个数据,LinkedList 和 ArrayList 效率低下,前者由于数据结构决定效率底下,后者由于不知道下标,需要遍历一遍才可以。这时候我们需要HashSet
。它实现了Set 接口,是使用 Set 集合的最常用的一个实现类。 HashSet
特点如下:
- 集合内的元素是无序排列的
- HashSet 类是非线程安全的
- 允许集合元素值为NULL
下表列出了 HashSet 类的常用方法:
方法 | 说明 |
| 如果此 Set 中尚未包含指定元素,则添加指定元素 |
| 从此 Set 中移除所有元素 |
| 返回此 Set 中元素的数量(Set 的容量) |
| 如果此 Set 不包含任何元素,则返回true |
| 如果此 Set 包含指定元素,则返回true |
| 如果指定元素存在于此 Set 中,则将其移除 |
下面代码演示使用HashSet类常用方法存储并操作信息,并遍历集合,代码如下所示:
import java.util.HashSet;
import java.util.Set;
public class HashSetDemo {
public static void main(String[] args) {
Set<String> hs = new HashSet<>();
for (int i = 0; i < 100; i++) {
hs.add(Integer.toString(i));
}
for (String s : hs) {
System.out.println(s);
}
}
}
该结果的部分运行结果如下所示(所以Set一组无序唯一的数据):
如果添加多次相同数据,则实际只添加了一次该数据:
import java.util.HashSet;
import java.util.Set;
public class HashSetDemo {
public static void main(String[] args) {
Set<String> hs = new HashSet<>();
hs.add("1");
hs.add("1");
for (String s : hs) {
System.out.println(s);
}
}
}
运行结果如下所示:
注意
:
HashSet 不能使用(或者不推荐) 简单 for 循环遍历。
但是可以使用 Iterator 接口遍历,示例如下:
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class HashSetDemo {
public static void main(String[] args) {
Set<String> hs = new HashSet<>();
for (int i = 0; i < 100; i++) {
hs.add(Integer.toString(i));
}
Iterator<String> it = hs.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
}
}
运行结果与上面相同,无序唯一,在这里不演示了。
四、Map接口
Map 接口存储一组成对的键-值对象,提供key(键)到value (值)的映射。Map 中的 key 不要求有序,不允许重复。value 同样不要求有序,但允许重复。
最常用的Map 实现类是HashMap,它的存储方式是哈希表。哈希表也称为散列表,是根据关键码值(key value)而直接进行访问的数据结构。也就是说,他通过把关键码映射到表中一个位置来进行访问记录,以加快查找速度。存放记录的数组称为散列表。使用这种方式存储数据的优点是查询指定元素效率高
。
1.HashMap集合类
建立key-value对象,实现映射关系。如:CN->中华人民共和国,根据 “CN” 可以查找到 “中华人民共和国”,通过删除键可实现对应值的删除,存储和修改。
HashMap常用方法如下:
方法名称 | 说明 |
| 以”键-值对”的方式进行存储。 注意:键必须是唯一的,值可以重复。如果试图添加重复的键,那么最后加入的”键-值对”将替换原先的”键-值对”。 |
| 根据键返回和关联的值,若不存在指定的键,则返回NULL |
| 删除指定的键映射的”键-值对” |
| 返回元素个数 |
| 返回键的集合 |
| 返回值的集合 |
| 若存在指定的键映射的”键-值对”,则返回 |
| 若不存在键-值映射关系,则返回 |
| 从此映射中移除所有映射关系 |
代码示例如下所示:
import java.util.HashMap;
import java.util.Map;
public class HashMapDemo {
public static void main(String[] args) {
// 使用HashMap存储多组国家英文简称和中文全称的"键-值对"
Map<String, String> hmap = new HashMap<>();
hmap.put("CN", "中华人民共和国");
hmap.put("US", "美利坚合众国");
hmap.put("RU", "俄罗斯联邦");
hmap.put("FR", "法兰西共和国");
// 显示"CN"对应国家的中文名称
String country = hmap.get("CN");
System.out.println(country);
// 显示集合中元素的个数
System.out.println("Map中共有:" + hmap.size() + "组数据");
// 两次判断Map中是否存在"FR"值
System.out.println("Map中包含FR的key吗?"
+ hmap.containsKey("FR"));
hmap.remove("FR");
System.out.println("Map中包含FR的key吗?"
+ hmap.containsKey("FR"));
// 分别显示键集,值集和键-值对集
System.out.println(hmap.keySet());
System.out.println(hmap.values());
System.out.println(hmap);
// 清空HashMap并判断
hmap.clear();
if (hmap.isEmpty()) {
System.out.println("已经清空HashMap中的数据!");
}
}
}
运行结果如下图所示:
五、迭代器Iterator
所有集合接口都没有提供相应的遍历方法,而是把遍历交给迭代器 Iterator 完成。 Iterator 为集合而生,专门实现集合的遍历。它隐藏了各种集合实现类的内部细节,提供了遍历集合的统一编程接口。
Collection 接口的 Iterator()
方法返回一个 Iterator , 然后通过 Iterator 接口的两个方法即可方便的实现遍历。
-
boolean hasNext()
:判断是否存在另一个可访问的元素。 -
Object next()
:返回要访问的下一个元素。
1.使用Iterator遍历List集合类
用 Iterator 接口来遍历 List 中的元素(ArrayList 和 LinkedList):
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
public class IteratorList {
public static void main(String[] args) {
// ArrayList演示
List<String> ls = new ArrayList<>();
ls.add("1");
ls.add("2");
ls.add("3");
ls.add("4");
System.out.println("ArrayList中的所有数据:");
Iterator<String> it = ls.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
// LinkedList演示
List<String> linkl = new LinkedList<>();
linkl.add("7");
linkl.add("8");
linkl.add("9");
linkl.add("10");
System.out.println("\n");
System.out.println("LinkedList中所有数据:");
Iterator<String> linkl_it = linkl.iterator();
while (linkl_it.hasNext()) {
System.out.println(linkl_it.next());
}
}
}
运行结果如下:
2.使用Iterator遍历Map集合类
代码如下:
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class IteratorMap {
public static void main(String[] args) {
// HashMap
Map<String, String> map = new HashMap<>();
map.put("1", "1");
map.put("2", "2");
map.put("3", "3");
map.put("4", "4");
Set<String> keys = map.keySet();
Iterator<String> it = keys.iterator();
while (it.hasNext()) {
String key = it.next();
System.out.println(map.get(key));
}
}
}
运行结果如下:
3.使用Foreach循环遍历Map集合
代码如下:
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class ForEachMap {
public static void main(String[] args) {
// HashMap
Map<String, String> map = new HashMap<>();
map.put("1", "1");
map.put("2", "2");
map.put("3", "3");
map.put("4", "4");
Set<String> keys = map.keySet();
for (String key : keys) {
System.out.println(map.get(key));
}
}
}
运行结果如下:
3.使用Foreach循环遍历List集合
代码如下:
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public class ForEachList {
public static void main(String[] args) {
List<String> arrayList = new ArrayList<>();
arrayList.add("1");
arrayList.add("2");
arrayList.add("3");
arrayList.add("4");
System.out.println("遍历arrayList结果:");
for (String s : arrayList) {
System.out.println(s);
}
List<String> linkedList = new LinkedList<>();
linkedList.add("11");
linkedList.add("22");
linkedList.add("33");
linkedList.add("44");
System.out.println("遍历linkedList结果:");
for (String s : linkedList) {
System.out.println(s);
}
}
}
运行结果如下:
六、泛型集合
Collection 的 add(Object obj)
方法的参数就是 Object 类型,无论把什么对象放入 Collection 及其子接口或实现类中,都认为只是 Object 类型,在通过 get(int index)
方法取出集合中元素时必须进行强制类型转换,不仅繁琐且容易出现ClassCastException
异常。Map 中使用put(Object key, Object value)
和get(Object key)
方法存取对象时、使用 Iterator 的next()
方法获取元素时也存在同样的问题。
JDK 1.5 中通过引用泛型(Generic)有效的解决了这个问题。在 JDK 1.5 中已经改写了集合框架中的所有接口和类,增加了对泛型的支持。
使用泛型集合在创建集合对象时指定集合中元素的类型,从集合中取出元素时无需进行类型强制转换,而且如果把非指定类型对象放入集合,会出现编译错误。
该页全部代码均使用了泛型集合,样式为:
//从左到右List为集合类型,
//String为List中存放的元素的引用数据类型或者对象
//(不可使用基本数据类型)
//右边的String可以不写
List<String> listName = new ArrayList<String>();
//Map同理,右边的<String, String>中的两个String要么都写,要么一个写
Map<String, String> mapName = new HashMap<String, String>();