集合

一个Java对象可以在内部持有若干个其他Java对象,并对外提供访问接口,我们把这种Java对象称为集合。显然,数组也是一种集合

数组存在限制:

  • 数组初始化后大小不可变
  • 数组只能按索引顺序存取

因此需要各种不同类型的集合类来处理不同数据,例:

  • 可变大小的顺序链表
  • 保证无重复元素的集合
  • ……

Collection

Java自带的java.util包提供了集合类:Collection,

它是除了Map外所有其他集合类的根接口。

主要提供了三种类型的集合:

  • List:一种有序列表的集合
  • Set:一种保证没有重复元素的集合
  • Map:一种通过键值(key-value)查找的映射表集合

特点:

  • 实现了接口和实现类相分离
  • 支持泛型

Java访问集合总是通过统一的方式:迭代器,好处就是无需知道集合内部元素是按什么方式存储

List

list是一种有序列表,和数组几乎完全相同

ArrayList把添加和删除的操作封装起来,我们操作List类似于操作数组,不用关心内部元素如何移动。

主要接口方法:

  • 在末尾添加一个元素:boolean add(E e)
  • 在指定索引位置添加一个元素:boolean add(int index, E e)
  • 删除指定索引的元素:E remove(int index)
  • 删除某个元素:boolean remove(Object e)
  • 获取指定索引的元素:E get(int index)
  • 获取链表大小:int size()

特点:

  • 允许元素重复
  • 允许添加null

创建List

List<String> list = new ArrayList<>();//使用ArrayList
List<Integer> list = List.of(1, 2, 5);//使用List接口提供的of()方法,不接受null值

遍历List

for循环结合get(int)可行,但不推荐。

应使用迭代器Iterator来访问List

import java.util.Iterator;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> list = List.of("apple", "pear", "banana");
        for (Iterator<String> it = list.iterator(); it.hasNext(); ) {
            String s = it.next();
            System.out.println(s);
        }
    }
}
//由于for each循环本身就可以帮助我们使用Iterator遍历,改写如下
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> list = List.of("apple", "pear", "banana");
        for (String s : list) {
            System.out.println(s);
        }
    }
}

List 和 Array转换

  • 第一种调用toArray()方法直接返回一个Object[]数组(不推荐,会丢失类型信息)
  • 第二种调toArray(T[])传入一个类型相同的Array,List内部自动复制元素到Array中

编写equals()方法

List提供 boolean contains(Object o)方法判断List是否包含某个指定元素

此外,int indexOf(Object o)方法可以返回某个元素索引

注意:因为contains()方法是通过equals()进行判断是否相等。

所以,List放入的实例,必须正确重写equals()。

如何正确编写equals()?

  • 自反性
  • 对称性
  • 传递性
  • 一致性
  • 对null比较,只返回false

1.先确定实例"相等"的逻辑

2.用instanceof判断传入的待比较的Object是不是当前类型

3.对引用类型用Objects.equals()比较,对基本类型用==比较

Map

key-value映射表的数据结构,不保证顺序

Map是一个接口,最常用实现类是HashMap。

Map中不存在重复的key,value可以重复

  • 遍历key,可以使用for-each循环Map实例的keySet()方法返回Set集合,包含不重复的key集合 ,遍历key和value,可以使用entrySet()集合,包含每一个key-value映射
  • 在Map内部,对key作比较是通过equals()方法,所以作为key的对象必须正确覆写equals()方法
  • 如果不同的key映射的hashCode()相同,那么在HashMap的数组中会存储List,包含两个Entry。查找时,会找到这个List,然后遍历List,找到Entry,它的key字段为"a",然后返回对应实例

EnumMap

作为key的对象是enum类型,就可以使用EnumMap。

非常紧凑的数组存储value,并且根据enum类型的key直接定位到内部数组的索引,效率最高

TreeMap

内部会对Key进行排序,SortedMap是接口,实现类是TreeMap

SortedMap保证遍历时以Key的顺序进行排序。String 默认按字母排序。

使用TreeMap时,放入的Key必须实现Comparable接口,作为Value的对象无要求

如果作为Key的class没有实现Comparable接口,

那么必须在创建TreeMap时同时指定一个自定义排序算法。

Map<String,Integer> map = new TreeMap<>()

Properties

编写应用程序时,经常需要读写配置文件。例如,用户的设置:

# 上次最后打开的文件:
last_open_file = /data/hello.txt
# 自动保存文件的时间间隔
auto_save_interval = 60

配置文件的特点,key-value一般是String-String类型,

因此完全可以用Map<String,String>来表示

因为配置文件非常常用,所以Java提供Properties来表示一组配置

内部本质是Hashtable

读取配置文件

用Properties读取配置文件非常简单。默认配置文件以.properties为扩展名

典型配置文件:

# setting.properties

last_open_file=/data/hello.txt
auto_save_interval=60

可以从文件系统读取这个.properties文件

一共三步:

  • 创建Properties实例;
  • 调用load()读取文件
  • 调用getProperty()获取配置。
String f = "setting.properties";
Properties props = new Properties();
props.load(new java.io.FileInputStream(f));

String filepath = props.getProperty("last_open_file");
String interval = props.getProperty("auto_save_interval", "120");

load(InputStream)方法接收一个InputStream实例,表示一个字节流,既可以是文件流,也可以是从jar包中读取的资源流

写入配置文件

通过setProperty()修改Properties实例,可以把配置写入文件,写入配置文件使用store()方法

Set

Map用于存储key-value的映射,对于充当key的对象,是不能重复的,并且不但需要正确覆写equals(),还要覆写hashCode()方法。

如果我们只需要存储不重复的key,并不需要存储映射的value,那就可以使用Set。

Set用于存储不重复的元素集合,主要方法:

  • 将元素加入Set< E>: boolean add(E e)
  • 将元素从Set删除:boolean remove(Object e)
  • 判断是否包含元素:boolean contains(Object e)

最常用Set实现类是HashSet,实际上HashSet仅仅是对HashMap的一个简单包装

Set接口不保证有序,而SortedSet接口则保证元素有序

  • HashSet是无序的,它实现了Set接口,并没有实现SortedSet接口
  • TreeSet是有序的,它实现了SortedSet接口

Queue

队列,先进先出的有序表。它和List区别在于,List可以在任意位置添加和删除元素,而Queue只有两个操作:

  • 把元素添加到队列末尾
  • 从队列头部取出元素

方法:

  • int size()
  • boolean add(E) / boolean offer(E)
  • E remove() / E poll()
  • E element() / E peak()

LinkedList既实现了List接口,又实现了Queue接口。

尽量避免把null添加到队列。

PriorityQueue

优先队列,出队顺序与元素优先级有关。

调用remove()或poll()方法,返回的总是优先级最高的元素。

必须给每个元素定义优先级

因此放入PriorityQueue的元素必须实现Comparable接口。

Deque

双端队列,允许两头都进,两头都出。

Deque接口实际扩展自Queue。

所以Deque可以调用Queue的方法,offer()/poll()

但是推荐直接明确调用方法

Stack

栈,先进后出的数据结构,只有入栈和出栈的操作。

  • push(E) ,压栈
  • pop(); 弹出
  • peek(); 取栈顶元素但不弹出

Java中用Deque实现Stack功能:

  • 压栈:push(E) \ addFirst(E);
  • 弹出:pop() \ removeFirst();
  • 取栈顶元素但不弹出:peek() \ peekFirst().

作用:

  • JVM在处理Java方法调用的时候就会通过栈这种数据结构维护方法调用的层次

Collections

是JDK提供的工具类,更方便操作各种集合

创建集合

直接使用 .of()方法

排序

排序会修改List元素的位置,因此必须传入可变List

洗牌

随机打乱List内部元素的顺序

不可变集合

可以将可变集合封装成不可变集合:.unmodifiableList(), ……