认识集合类
集合表示一组对象,称为其元素。一些集合允许重复的元素,而另一些则不允许。一些集合是有序的,而其他则是无序的。
集合类其实就是为了更好地组织、管理和操作我们的数据而存在的,包括列表、集合、队列、映射等数据结构。从这一块开始,我们会从源码角度给大家讲解(数据结构很重要!),不仅仅是教会大家如何去使用。
集合类最顶层不是抽象类而是接口,因为接口代表的是某个功能,而抽象类是已经快要成形的类型,不同的集合类的底层实现是不相同的,同时一个集合类可能会同时具有两种及以上功能(既能做队列也能做列表),所以采用接口会更加合适,接口只需定义支持的功能即可。
数组与集合
相同之处:
- 它们都是容器,都能够容纳一组元素。
不同之处:
- 数组的大小是固定的,集合的大小是可变的。
- 数组可以存放基本数据类型,但集合只能存放对象。
- 数组存放的类型只能是一种,但集合可以有不同种类的元素。
集合根接口Collection
本接口中定义了全部的集合基本操作,我们可以在源码中看看。
我们再来看看List和Set以及Queue接口。集合类的使用
List列表
首先介绍ArrayList,它的底层是用数组实现的,内部维护的是一个可改变大小的数组,也就是我们之前所说的线性表!跟我们之前自己写的ArrayList相比,它更加的规范,同时继承自List接口。
先看看ArrayList的源码!基本操作
List<String> list = new ArrayList<>(); //默认长度的列表
List<String> listInit = new ArrayList<>(100); //初始长度为100的列表
向列表中添加元素:
List<String> list = new ArrayList<>();
list.add("lbwnb");
list.add("yyds");
list.contains("yyds"); //是否包含某个元素
System.out.println(list);
移除元素:
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("lbwnb");
list.add("yyds");
list.remove(0); //按下标移除元素
list.remove("yyds"); //移除指定元素
System.out.println(list);
}
也支持批量操作:
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.addAll(new ArrayList<>()); //在尾部批量添加元素
list.removeAll(new ArrayList<>()); //批量移除元素(只有给定集合中存在的元素才会被移除)
list.retainAll(new ArrayList<>()); //只保留某些元素
System.out.println(list);
}
我们再来看LinkedList,其实本质就是一个链表!我们来看看源码。
其实与我们之前编写的LinkedList不同之处在于,它内部使用的是一个双向链表:
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
当然,我们发现它还实现了Queue接口,所以LinkedList也能被当做一个队列或是栈来使用。
public static void main(String[] args) {
LinkedList<String> list = new LinkedList<>();
list.offer("A"); //入队
System.out.println(list.poll()); //出队
list.push("A");
list.push("B"); //进栈
list.push("C");
System.out.println(list.pop());
System.out.println(list.pop()); //出栈
System.out.println(list.pop());
}
利用代码块来快速添加内容
前面我们学习了匿名内部类,我们就可以利用代码块,来快速生成一个自带元素的List
List<String> list = new LinkedList<String>(){{ //初始化时添加
this.add("A");
this.add("B");
}};
如果是需要快速生成一个只读的List,后面我们会讲解Arrays工具类。
集合的排序
List<Integer> list = new LinkedList<Integer>(){ //Java9才支持匿名内部类使用钻石运算符
{
this.add(10);
this.add(2);
this.add(5);
this.add(8);
}
};
list.sort((a, b) -> { //排序已经由JDK实现,现在只需要填入自定义规则,完成Comparator接口实现
return a - b; //返回值小于0,表示a应该在b前面,返回值大于0,表示b应该在a后面,等于0则不进行交换
});
System.out.println(list);
迭代器
集合的遍历
所有的集合类,都支持foreach循环!
public static void main(String[] args) {
List<Integer> list = new LinkedList<Integer>(){ //Java9才支持匿名内部类使用钻石运算符
{
this.add(10);
this.add(2);
this.add(5);
this.add(8);
}
};
for (Integer integer : list) {
System.out.println(integer);
}
}
当然,也可以使用JDK1.8新增的forEach方法,它接受一个Consumer接口实现:
list.forEach(i -> {
System.out.println(i);
});
从JDK1.8开始,lambda表达式开始逐渐成为主流,我们需要去适应函数式编程的这种语法,包括批量替换,也是用到了函数式接口来完成的。
list.replaceAll((i) -> {
if(i == 2) return 3; //将所有的2替换为3
else return i; //不是2就不变
});
System.out.println(list);
Iterable和Iterator接口
我们之前学习数据结构时,已经得知,不同的线性表实现,在获取元素时的效率也不同,因此我们需要一种更好地方式来统一不同数据结构的遍历。
由于ArrayList对于随机访问的速度更快,而LinkedList对于顺序访问的速度更快,因此在上述的传统for循环遍历操作中,ArrayList的效率更胜一筹,因此我们要使得LinkedList遍历效率提升,就需要采用顺序访问的方式进行遍历,如果没有迭代器帮助我们统一标准,那么我们在应对多种集合类型的时候,就需要对应编写不同的遍历算法,很显然这样会降低我们的开发效率,而迭代器的出现就帮助我们解决了这个问题。
我们先来看看迭代器里面方法:
public interface Iterator<E> {
//...
}
每个集合类都有自己的迭代器,通过iterator()
方法来获取:
Iterator<Integer> iterator = list.iterator(); //生成一个新的迭代器
while (iterator.hasNext()){ //判断是否还有下一个元素
Integer i = iterator.next(); //获取下一个元素(获取一个少一个)
System.out.println(i);
}
迭代器生成后,默认指向第一个元素,每次调用next()
方法,都会将指针后移,当指针移动到最后一个元素之后,调用hasNext()
将会返回false
,迭代器是一次性的,用完即止,如果需要再次使用,需要调用iterator()
方法。
ListIterator<Integer> iterator = list.listIterator(); //List还有一个更好地迭代器实现ListIterator
ListIterator
是List中独有的迭代器,在原有迭代器基础上新增了一些额外的操作。