Collections(集合)
一、常用的集合相关接口与实现类
在Java中所有的集合类都源自Iterable接口,Colletion继承Iterable接口,Collection下有三个子接口,分别是List、Queue和Set。它们拥有不同的特性,List下的ArrayList和LinkedList是两个常用的实现类,用于顺序存储的列表。Set下的HashSet和TreeSet,是去重的集合实现类。Queue下的PriorityQueue,是队列这一种数据结构的实现类,可以按照一定的规则形成队列。它们的整个关系如下图所示:
二、集合相关接口与类的使用
2.1 Iterable接口和Iterator接口
实现该接口允许对象使用for-each循环,用于更方便地遍历元素。使用泛型类,可以粗略实现列表集合,如下代码展示:
import java.util.Iterator;
/**
* @author tqwstart
* @creat 2022-08-11 20:32
*/
public class GenericList<T> implements Iterable<T>{ //实现Iterable接口必须实现Iterator方法
private T[] items = (T[])new Object[10]; //Object数组充当集合容器
private int count;
public void add(T item){
items[count++]=item;
}
public T get(int index){
return items[index];
}
@Override
public Iterator<T> iterator() {
return null;
}
}
如果集合类不实现Iterable接口,则不能使用for-each循环,编译报错,如上图所示。而实现Iterable接口,需要实现Iterator()方法。实现Iterator()方法,实际上是实现Iterator接口,每次调用Iterator()方法,得到不同的迭代对象,代码如下:
public class GenericList<T> implements Iterable<T>{
private T[] items = (T[])new Object[10];
private int count;
public void add(T item){
items[count++]=item;
}
public T get(int index){
return items[index];
}
@Override
public Iterator<T> iterator() {//实现Iterator()方法
return new ListIterator(this);
}
private class ListIterator implements Iterator<T>{ //实现Iterator接口
private GenericList<T> list;
private int index;//迭代器从索引0开始
public ListIterator(GenericList<T> list ){
this.list=list;
}
@Override
public boolean hasNext() {
return (index<list.count);
}
@Override
public T next() {
return list.items[index++];
}
}
}
执行结果如下:
2.2 Collection接口
Collection继承了Iterable接口,说明它的所有集合都是可变的,并且可以使用for-each循环遍历。Collection接口的使用,如下所示,展示了比较完整的用法:
/**
* @author tqwstart
* @creat 2022-08-11 21:23
*/
public class CollectionsDemo {
public static void show(){
//展示Collection接口的方法使用
Collection<String> collection = new ArrayList<>();
//添加元素
collection.add("a");
collection.add("b");
collection.add("c");
//一次性添加多个元素,使用工具类Collections
Collections.addAll(collection,"d","e");
//查看集合元素个数
System.out.println(collection.size());
//移除元素
collection.remove("a");
//打印集合
System.out.println(collection);
//判断是否包含某个元素
boolean flagD = collection.contains("d");
//判断集合是否为空
boolean flagEmpty = collection.isEmpty();
System.out.println(flagD);
System.out.println(flagEmpty);
//for-each输出元素
for(String item:collection){
System.out.println(item);
}
//集合转换为数组
String[] array = collection.toArray(new String[0]);
System.out.println(array[0]);
//清空集合
collection.clear();
System.out.println(collection);
}
}
执行结果如下图所示:
注意:如果两个集合元素完全相同,要判断它们是否元素一样,应该使用equals()方法,而不是==号。
2.3 List接口
List接口是Collection接口的子接口,拥有与Collection接口许多方法。当我们考虑需要使用有序的集合或者需要使用索引来访问集合元素时,我们可以考虑使用List接口。它的ArrayList实现类,可以在指定索引添加元素,也可以获取指定索引的元素。使用代码如下:
public class ListDemo{
public static void show(){
List<Integer> list=new ArrayList<>();
list.add(1);
list.add(0);
Collections.addAll(list,2,3,4,5); //与Collection接口一致,这里也可以一次性添加多个元素。
list.set(1,1); //List接口下的特有方法,在指定索引添加元素。
System.out.println(list.get(0));//List接口下的特有方法,获取指定索引的元素。
List<Integer> subList = list.subList(0, 3); //获取子列表
System.out.println(subList);
list.remove(0); //List接口下的特有方法,删除指定索引的元素。
System.out.println(list);
}
}
//注意:值得注意的是,当List的实现类中元素的类别是Integer时,remove()方法有两种,存在方法重载,一个指的是索引,一个指的是对象。默认使用索引。
2.4 Comparable接口与Comparator接口
当集合中的元素需要排序时,通常会使用到Comparable接口,自定义类Customer,按姓名进行排序,代码如下所示:
public class Customer implements Comparable<Customer>{
private String name;
public Customer(String name){
this.name=name;
}
@Override
public String toString() {
return "Customer{" +
"name='" + name + '\'' +
'}';
}
@Override
public int compareTo(Customer other) { //实现Comparable接口的方法
return name.compareTo(other.name); //由于比较的字段是String,这里使用compareTo()方法
}
}
public class ListDemo{
public static void show(){
List<Customer> list=new ArrayList<>();
list.add(new Customer("a"));
list.add(new Customer("c"));
list.add(new Customer("b"));
Collections.sort(list);
System.out.println(list);
}
}
执行结果:
然而,上述元素的排序是不灵活的。当Customer类拥有更多的属性,比如email、address,想通过email来排序,是不能实现的。这种情况下,我们可以使用Comparator接口进行比较,创建一个emailComparator类实现Comparator接口,代码如下:
public class Customer implements Comparable<Customer>{
private String name;
private String email;
public Customer(String name, String email) {
this.name = name;
this.email = email;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Override
public String toString() {
return "Customer{" +
"name='" + name + '\'' +
", email='" + email + '\'' +
'}';
}
@Override
public int compareTo(Customer other) { //实现Comparable接口的方法
return name.compareTo(other.name); //由于比较的字段是String,这里使用compareTo()方法
}
}
public class emailComparator implements Comparator<Customer> {
@Override
public int compare(Customer o1, Customer o2) {
return o1.getEmail().compareTo(o2.getEmail());
}
}
public class ListDemo{
public static void show(){
List<Customer> list=new ArrayList<>();
list.add(new Customer("a","e3"));
list.add(new Customer("c","e1"));
list.add(new Customer("b","e2"));
Collections.sort(list,new emailComparator());
System.out.println(list);
}
}
执行结果:
2.5 Queue接口
在现实生活中,我们通常会遇上这样一种情景。打印机接收文件打印任务之后,打印顺序总是先传输的文件先打印。这就是队列的应用,在Java中,Queue接口下有两个常用的类,一个是ArrayDeque(双端队列),另一个是PriorityQueue(优先队列)。双端队列,可以在任何一端进出数据,从而进行双端操作。进入优先队列中的数据按照某种规则确定顺序,创建优先队列时传入Comparator比较器对象。在操作系统中,某些应用程序具有更高的优先级,它就有更多的机会得到CPU时间片。Queue接口下常用类的使用,如下所示:
public class QueueDemo {
public static void show(){
Queue<String> queue = new ArrayDeque<>();//创建双端队列需要将Queue改为Deque,既可以在队首,又可以在队尾操作。
//这里是创建普通队列。
queue.offer("a"); //元素入队,这里也可使用add()。
queue.offer("b");
queue.offer("c");
String s = queue.peek(); //获取队列首部元素,不出队。这里也可使用element()。
queue.poll();//元素出队。这里也可使用remove(),它们与程序中的方法功能一样,不同之处在于它们会抛异常。
System.out.println(s);
//c->b->a
System.out.println(queue); //打印队列元素
Deque<String> deque = new ArrayDeque<>();//演示双端队列的操作
deque.offerFirst("a");
deque.offerFirst("b");
deque.offerFirst("c"); //a->b->c 在队头添加元素,普通队列只能在队尾添加元素
String s1 = deque.peekLast();//获取队尾元素
System.out.println(s1);
deque.pollLast();//队尾元素出队列
System.out.println(deque);
PriorityQueue<String> pq = new PriorityQueue<>(cmp);
pq.add("a");
pq.add("c");
pq.add("b");
while(!pq.isEmpty())
{
System.out.print(pq.poll()+" ");//遍历输出队列元素
}
}
static Comparator<String> cmp = (o1, o2) -> o2.compareTo(o1); //构建比较器,创建优先队列时传入。
}
执行结果:
2.6 Set接口
Set集合中的特性是,它不存储重复的元素,如果想要集合中存储唯一的元素,Set集合是个不错的选择。Set集合可以保证元素不重复,但是无法保证元素的顺序。Set集合还可以完成集合操作,包括并集、交集、差集。Set使用的代码演示如下:
public class SetDemo {
public static void show(){
Set<String> set = new HashSet<>();
set.add("sky");
set.add("is");
set.add("blue");
set.add("blue");
System.out.println(set); //Set只存储唯一元素
//利用Set只存储唯一元素这一属性,我们可以用于去重操作。
Collection<String> collection =new ArrayList<>();
Collections.addAll(collection,"a","b","c","c");
Set<String> setD = new HashSet<>(collection);
System.out.println(setD);
Set<String> set1 = new HashSet<>(Arrays.asList("a","b","c")); //集合操作,包括并集、交集、差集
Set<String> set2 = new HashSet<>(Arrays.asList("d","b","c"));
set1.addAll(set2); //求集合的并集
System.out.println(set1);
set1.retainAll(set2);//求交集
System.out.println(set1);
set1.removeAll(set2);//求差集
System.out.println(set1);
}
}
执行结果:
2.7 Map接口
上文中提到了Collection集合中的接口和实现类的用法,除了它们之外。Map接口也是常用的,它以key-value形式存储元素。根据对象的某一属性查询对象,时间复杂度低,可以考虑使用。代码如下所示:
public class MapDemo {
public static void show(){
Collection<Customer> customers = new ArrayList<>();
Customer c1 =new Customer("a","e1");
Customer c2=new Customer("b","e2");
Customer c3=new Customer("c","e3");
Collections.addAll(customers,c1,c2,c3);
//如果想要寻找email为e2顾客的姓名,可以使用循环找。
for(Customer customer:customers){
if(customer.getEmail()=="e2")
System.out.println("found!"); //遍历的方法,时间复杂度O(n)。当n较大时,这个方法不可取。
}
//Map接口,存储key->value的映射,查找时间复杂度O(1)。
Map<String,Customer> map = new HashMap<>();
map.put(c1.getEmail(),c1); //map添加元素
map.put(c2.getEmail(),c2);
map.put(c3.getEmail(),c3);
Customer customerFound = map.get("e2"); //map获取元素
System.out.println(customerFound.getName());
for(String email:map.keySet()){ //获取所有键值
System.out.println(email);
}
for(Map.Entry entry:map.entrySet()){
System.out.println(entry); //获取map中的对象
}
for(Customer customer:map.values()){
System.out.println(customer);//获取map中的value
}
}
}
执行结果: