认识集合类

集合表示一组对象,称为其元素。一些集合允许重复的元素,而另一些则不允许。一些集合是有序的,而其他则是无序的。
集合类其实就是为了更好地组织、管理和操作我们的数据而存在的,包括列表、集合、队列、映射等数据结构。从这一块开始,我们会从源码角度给大家讲解(数据结构很重要!),不仅仅是教会大家如何去使用。
集合类最顶层不是抽象类而是接口,因为接口代表的是某个功能,而抽象类是已经快要成形的类型,不同的集合类的底层实现是不相同的,同时一个集合类可能会同时具有两种及以上功能(既能做队列也能做列表),所以采用接口会更加合适,接口只需定义支持的功能即可。
4-6 集合 - 图1

数组与集合

相同之处:

  1. 它们都是容器,都能够容纳一组元素。

不同之处:

  1. 数组的大小是固定的,集合的大小是可变的。
  2. 数组可以存放基本数据类型,但集合只能存放对象。
  3. 数组存放的类型只能是一种,但集合可以有不同种类的元素。

    集合根接口Collection

    本接口中定义了全部的集合基本操作,我们可以在源码中看看。
    我们再来看看List和Set以及Queue接口。

    集合类的使用

    List列表

    首先介绍ArrayList,它的底层是用数组实现的,内部维护的是一个可改变大小的数组,也就是我们之前所说的线性表!跟我们之前自己写的ArrayList相比,它更加的规范,同时继承自List接口。
    先看看ArrayList的源码!

    基本操作

  1. List<String> list = new ArrayList<>(); //默认长度的列表
  2. List<String> listInit = new ArrayList<>(100); //初始长度为100的列表

向列表中添加元素:

  1. List<String> list = new ArrayList<>();
  2. list.add("lbwnb");
  3. list.add("yyds");
  4. list.contains("yyds"); //是否包含某个元素
  5. System.out.println(list);

移除元素:

  1. public static void main(String[] args) {
  2. List<String> list = new ArrayList<>();
  3. list.add("lbwnb");
  4. list.add("yyds");
  5. list.remove(0); //按下标移除元素
  6. list.remove("yyds"); //移除指定元素
  7. System.out.println(list);
  8. }

也支持批量操作:

  1. public static void main(String[] args) {
  2. ArrayList<String> list = new ArrayList<>();
  3. list.addAll(new ArrayList<>()); //在尾部批量添加元素
  4. list.removeAll(new ArrayList<>()); //批量移除元素(只有给定集合中存在的元素才会被移除)
  5. list.retainAll(new ArrayList<>()); //只保留某些元素
  6. System.out.println(list);
  7. }

我们再来看LinkedList,其实本质就是一个链表!我们来看看源码。

其实与我们之前编写的LinkedList不同之处在于,它内部使用的是一个双向链表:

  1. private static class Node<E> {
  2. E item;
  3. Node<E> next;
  4. Node<E> prev;
  5. Node(Node<E> prev, E element, Node<E> next) {
  6. this.item = element;
  7. this.next = next;
  8. this.prev = prev;
  9. }
  10. }

当然,我们发现它还实现了Queue接口,所以LinkedList也能被当做一个队列或是栈来使用。

  1. public static void main(String[] args) {
  2. LinkedList<String> list = new LinkedList<>();
  3. list.offer("A"); //入队
  4. System.out.println(list.poll()); //出队
  5. list.push("A");
  6. list.push("B"); //进栈
  7. list.push("C");
  8. System.out.println(list.pop());
  9. System.out.println(list.pop()); //出栈
  10. System.out.println(list.pop());
  11. }

利用代码块来快速添加内容

前面我们学习了匿名内部类,我们就可以利用代码块,来快速生成一个自带元素的List

  1. List<String> list = new LinkedList<String>(){{ //初始化时添加
  2. this.add("A");
  3. this.add("B");
  4. }};

如果是需要快速生成一个只读的List,后面我们会讲解Arrays工具类。

集合的排序

  1. List<Integer> list = new LinkedList<Integer>(){ //Java9才支持匿名内部类使用钻石运算符
  2. {
  3. this.add(10);
  4. this.add(2);
  5. this.add(5);
  6. this.add(8);
  7. }
  8. };
  9. list.sort((a, b) -> { //排序已经由JDK实现,现在只需要填入自定义规则,完成Comparator接口实现
  10. return a - b; //返回值小于0,表示a应该在b前面,返回值大于0,表示b应该在a后面,等于0则不进行交换
  11. });
  12. System.out.println(list);

迭代器

集合的遍历

所有的集合类,都支持foreach循环!

  1. public static void main(String[] args) {
  2. List<Integer> list = new LinkedList<Integer>(){ //Java9才支持匿名内部类使用钻石运算符
  3. {
  4. this.add(10);
  5. this.add(2);
  6. this.add(5);
  7. this.add(8);
  8. }
  9. };
  10. for (Integer integer : list) {
  11. System.out.println(integer);
  12. }
  13. }

当然,也可以使用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中独有的迭代器,在原有迭代器基础上新增了一些额外的操作。