Java集合框架提供了一套性能优良、使用方便的接口和类,位于java.util包中。
Stream是jdk8开始新增的一套API(java.util.stream.*),可以用于操作集合或者数组的数据。
0 数组:Array
0.1 初始化
int[] arr = new int[] {25, 35, 45, 55};
int[] arr = new int[10];
int[] arr = {1, 2, 3, 4, 5};
int[][] arr2d = {{1, 2}, {3, 4}};
0.2 遍历
/* 传统for循环 */
for (int i = 0; i < arr.length; i++) { // 长度属性length
System.out.println(arr[i]);
}
/* forEach循环 */
for (int x : arr) {
System.out.println(x);
}
1 单列集合:Collection
- List系列集合
- ArrayList、LinkedList:有序、可重复、有索引
- Set系列集合
- HashSet:无序、不重复、无索引
- LinkedHashSet:有先后顺序、不重复、无索引
- TreeSet:按大小默认升序排序、不重复、无索引
1.1 Collection接口
单列集合的顶层接口Collection<E>
。
Collection<String> colle = new ArrayList<>();
1.1.1 通用方法
方法名 | 说明 |
---|---|
public boolean add(E e) | 把给定的对象添加到当前集合中 |
public boolean size() | 返回集合中元素的个数 |
public void clear() | 清空集合中所有元素 |
public boolean isEmpty() | 判断当前集合是否为空 |
public boolean remove(E e) | 把给定的对象在当前集合中删除 |
public boolean contains(Object obj) | 判断当前集合中是否包含给定的对象 |
public Object[] toArray() | 把集合中的元素存储到数组中 |
1.1.2 遍历
(1) 迭代器
迭代器是用来遍历集合的专用方式(数组没有迭代器),在Java中迭代器的代表是Iterator。
方法名 | 说明 |
---|---|
Iterator<E> iterator() | 返回集合中的迭代器对象,该迭代器对象默认指向当前集合的第一个元素 |
Iterator迭代器中的常用方法
方法名 | 说明 |
---|---|
boolean hasNext() | 判断当前位置是否有元素 |
E next() | 返回当前位置的元素,并同时将迭代器对象指向下一个元素 |
Collection本身无索引,故无法用正常的fori
遍历,但可以使用增强型for
遍历。
/* 迭代器遍历 */
Iterator<String> it = colle.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
/* 增强版for遍历(实际底层使用的也是迭代器) */
for (String s : colle) {
System.out.println(s);
}
(2) forEach()
调用forEach()
方法,并使用Lambda表达式简化书写
colle.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
// 使用Lambda表达式简化
colle.forEach(s -> {
System.out.println(s);
});
// 进一步简化
colle.forEach(System.out::println);
1.2 List系列集合
List系列集合添加的元素有序、可重复、有索引。因为有索引,故可用fori
遍历。
List<String> list = new ArrayList<>();
List<String> linkedList = new LinkedList<>();
方法名 | 说明 |
---|---|
void add(int index, E element) | 在此集合中的指定位置插入指定元素 |
E remove(int index) | 删除指定索引处的元素并返回它 |
E set(int index, E element) | 修改指定索引处的元素并返回它 |
E get(int index) | 返回指定索引处的元素 |
1.2.1 ArrayList
基于数组实现,支持随机存取(顺序表),查找快、增删慢。
利用无参构造器创建的ArrayList,会在底层创建一个默认长度为0的数组。添加第一个元素时,底层会创建一个新的长度为10的数组。存满时,会扩容1.5倍。如果一次添加多个元素,1.5倍还放不下,则新创建数组的长度以实际为准。
/* 定义 */
ArrayList<Integer> list = new ArrayList<>(); // 元素必须为引用数据类型
ArrayList<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3)); // 初始化时赋值
List<Integer> list = Arrays.stream(new int[] {1, 2, 3}.boxed().collect(Collectors.toList()));
/* 遍历 */
// 传统for循环
for (int i = 0; i < list.size(); i++) { // 集合通用方法size
System.out.println(list.get(i));
}
// forEach循环
for (int x : list) {
System.out.println(x);
}
// 迭代器
for (Iterator<Integer> it = list.iterator(); it.hasNext();) {
System.out.println(it.next());
}
// Stream流和Lambda表达式
list.stream().forEach(x -> System.out.println(x));
/* List转换为数组 */
// 使用toArray方法
Integer[] a = new Integer[v.size()];
list.toArray(a);
// 使用流
int[] a = list.stream().mapToInt(Integer::valueOf).toArray(); // mapToInt返回基本类型的流
/* 常用方法 */
// 添加元素
boolean add(E e) // 在尾部追加元素
boolean add(int index, E element) // 在指定下标处添加元素
// 添加另一个集合对象的元素
boolean addAll(Collection<? extends E> c) // 在尾部追加元素
boolean addAll(int index, Collection<? extends E> c) // 从指定下标开始添加元素
list.addAll(Arrays.asList(1, 2));
// 删除元素
boolean remove(Object o) // 删除第一个与指定元素相同的元素,如果存在的话
E remove(int index) // 删除指定下标处的元素
// 获取指定下标处的元素
E get(int index)
// 设置指定下标处的元素
E set(int index, E element)
// 返回指定下标区间的子序列,左闭右开
List<E> subList(int fromIndex, int toIndex)
// 判空
boolean isEmpty()
// 判断是否含有指定元素
boolean contains(Object o)
// 返回指定元素的索引,不存在则返回-1
int indexOf(Object o)
// 排序
default void sort(Comparator<? super E> c)
list.sort(Comparator.naturalOrder()); // 升序(非递减)排序
list.sort(Comparator.reverseOrder()); // 降序(非递增)排序
// 转换为数组
Object[] toArray(); // 返回数组
<T> T[] toArray(T[] a); // 返回数组,并将所有元素存入数组a(若空间不足则直接重分配空间)
1.2.2 LinkedList
/* 定义 */
LinkedList<Integer> list = new LinkedList<>(Arrays.asList(1, 2, 3));
基于双链表实现(早期为循环双链表(参考C++STL中的list),新版本为提升时空性能已不再循环),查找慢、增删快,但对首尾元素的增删改查皆是极快的。因此新增了很多首位操作的特有方法:
方法名 | 说明 |
---|---|
public void addFirst(E e) | 头插指定的元素 |
public void addLast(E e) | 尾插指定的元素 |
public E getFirst() | 返回列表中的第一个元素 |
public E getLast() | 返回列表中的最后一个元素 |
public E removeFirst() | 删除并返回列表中的第一个元素 |
public E removeLast() | 删除并返回列表中的最后一个元素 |
1.3 Set系列集合
- HashSet:无序、不重复、无索引
- LinkedHashSet:有先后顺序、不重复、无索引
- TreeSet:按大小默认升序排序、不重复、无索引
Set<Integer> set = new HashSet<>();
Set<Integer> linkedHashSet = new LinkedHashSet<>();
Set<Integer> treeSet = new TreeSet<>();
Java中每个对象都有一个哈希值,可调用public int hashCode()
获取。
若希望Set集合认为2个内容一样的对象是重复的,则需重写对象的hashCode()
和equals()
方法。(类似C++类重载关系运算符"==")
1.3.1 HashSet
基于哈希表实现,元素无序、不重复、无索引,增删改查都较好。
JDK8之前,哈希表=数组+链表,每条链表头插新元素。
JDK8开始,哈希表=数组+链表+红黑树,每条链表尾插新元素,且当链表长度>8、数组长度≥64时自动将链表转成红黑树。
1.3.2 LinkedHashSet
与HashSet类似,基于哈希表实现,但每个元素额外追加了双链表的机制,记录了其前后元素的位置,因此元素有先后顺序、不重复、无索引。(参考C++STL中的unordered_set)
1.3.3 TreeSet
基于红黑树实现元素按大小默认升序排序、不重复、无索引。(参考C++STL中的set)
- 对于数值类型(Integer、Double),默认按数值本身的大小进行升序排序。
- 对于字符串类型,默认按字典序排序。
自定义排序规则
- 让自定义类型实现Comparable接口,重写
compareTo(Object o)
方法来指定比较规则。(参考C++类重载小于号,顺序相同)
public class Student implements Comparable<Student> {
private int age;
@Override
public int compareTo(Student o) {
// this > o => 返回正整数
// this < o => 返回负整数
// this == o => 返回0
return this.getAge() - o.getAge();
}
}
- 调用TreeSet集合有参构造器
public TreeSet(Comparator<? super E> comparator)
,设置Comparator对象(比较器对象,制定比较规则)。(相减顺序关系同上)
Set<Student> students = new TreeSet<>(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
// 比较o1和o2成员,同上
return o1.getAge() - o2.getAge();
}
});
// 用Lambda表达式简化
Set<Student> students = new TreeSet<>((o1, o2) -> {
return o1.getAge() - o2.getAge()
});
2 工具类Collections
提供了一系列静态方法用来操作集合
方法名称 | 说明 |
---|---|
public static <T> boolean addAll(Collection<? super T> c, T... elements) | 给集合批量添加元素 |
public static void shuffle(List<?> List) | 打断List集合中的元素顺序 |
public static <T> void sort(List<T> list) | 对List集合中的元素进行升序排序 |
public static <T> void sort(List<T> list, Comparator<? super T> c) | 对List集合中的元素按指定的规则进行排序 |
对泛型的理解
<? super T>
:表示?匹配的类型都是T的父类,包括T本身。<? extends R>
:表示?匹配的类型都是类型R的子类,包括R本身。
3 双列集合:Map
- 根据键决定集合的特点
- HashMap:无序、不重复、无索引
- LinkedHashMap:有先后顺序、不重复、无索引
- TreeMap:按大小默认升序排序、不重复、无索引
3.1 Map接口
Map系列集合Map<K, V>
的每个元素key=value
称为一个键值对/Entry对象,所有键不允许重复
Map<String, Double> map = new HashMap<>();
Map<String, Double> linkedHashMap = new LinkedHashMap<>();
Map<String, Double> treeMap = new TreeMap<>();
3.1.1 通用方法
方法名 | 说明 |
---|---|
public V put(K key, V value) | 添加元素 |
public int size() | 获取集合的大小 |
public void clear() | 清空集合 |
public boolean isEmpty() | 判断集合是否为空 |
public V get(Object key) | 根据键获取对应值 |
public V remove(Object key) | 根据键删除整个元素,并返回对应的值 |
public boolean containsKey(Object key) | 判断是否包含某个键 |
public boolean containsValue(Object value) | 判定是否包含某个值 |
public Set<K> keySet() | 获取全部键的集合 |
public Collection<V> values() | 获取Map集合的全部值 |
3.1.2 遍历
(1) 键找值
先获取Map集合全部的键,再通过遍历键来找值
方法名 | 说明 |
---|---|
public Set<K> keySet() | 获取全部键的集合 |
public V get(Object key) | 根据键获取对应值 |
Set<String> keys = map.keySet();
for (String key : keys) {
double value = map.get(key);
System.out.println(key + " -> " + value);
}
(2) 键值对
把键值对看成一个整体进行遍历
方法名 | 说明 |
---|---|
Set<Map.Entry<K, V>> entrySet() | 获取索引键值对集合 |
类Map.Entry<K, V>
提供的方法
方法名 | 说明 |
---|---|
K getKey() | 获取键 |
V getValue() | 获取值 |
Set<Map.Entry<String, Double>> entries = map.entrySet();
for (Map.Entry<String, Double> entry : entries) {
String key = entry.getKey();
double value = entry.getValue();
System.out.println(key + " -> " + value);
}
(3) forEach()
map.forEach(new BiConsumer<String, Double>() {
@Override
public void accept(String k, Double v) {
System.out.println(k + " -> " + v);
}
});
// 使用Lambda表达式简写
map.forEach((k, v) -> {
System.out.println(k + " -> " + v);
});
3.2 HashMap
与HashSet相同,基于哈希表实现(实际Set系列集合底层均为基于对应的Map实现,集合元素只有键没有值而已,下同),元素无序、不重复。
3.3 LinkedHashMap
与LinkedHashSet相同,基于哈希表实现,每个元素额外追加了双链表的机制,记录了其前后元素的位置,因此元素有先后顺序、不重复。(参考C++STL中的unordered_map)
3.4 TreeMap
与TreeSet相同,基于红黑树实现元素按大小默认升序排序、不重复,自定义排序方法也完全一致。(参考C++STL中的map)
4 Stream流
Stream是jdk8开始新增的一套API(java.util.stream.*),可以用于操作集合或者数组的数据。Stream流大量结合了Lambda的语法风格,支持链式编程,提供了一种更加强大,更加简单的方式操作集合或者数组中的数据,代码更简洁,可读性更好。
4.1 获取Stream流
4.1.1 获取集合的Stream流
Collection提供的方法 | 说明 |
---|---|
default Stream<E> stream() | 获取当前集合对象的stream流 |
/* 获取Collection系列集合的Stream流 */
List<String> names = new ArrayList<>();
Stream<String> stream = names.stream();
/* 获取Map系列集合的Stream流 */
Map<String, Double> map = new HashMap<>();
// 利用键的Set获取键流
Set<String> keys = map.keySet();
Stream<String> ks = keys.stream();
// 利用值的集合获取值流
Collection<Double> values = map.values();
Stream<Double> vs = values.stream();
// 利用键值对的Set获取键值对流
Set<Map.Entry<String, Double>> entries = map.entrySet();
Stream<Map.Entry<String, Double>> kvs = entries.stream();
4.1.2 获取数组的Stream流
Arrays类提供的方法 | 说明 |
---|---|
public static <T> Stream<T> stream(T[] array) | 获取当前数组的stream流 |
Stream类提供的方法 | 说明 |
---|---|
public static <T> Stream<T> of(T... values) | 获取当前接收数据的stream流 |
// 获取数组的Stream流
String[] names = {"set", "unordered_set", "multiset"};
String<String> stream = Arrays.stream(names); // 用Arrays类提供的方法
String<String> stream2 = Stream.of(names); // 用Stream类提供的方法
4.2 中间方法
中间方法:调用完成后会返回新的Stream流,可以继续使用(支持链式编程)
Stream流常用中间方法 | 说明 |
---|---|
Stream<T> filter(Predicate<? super T> predicate) | 用于对流中的数据进行过滤 |
Stream<T> sorted() | 对元素进行升序排序 |
Stream<T> sorted(Comparator<? super T> comparator | 按照指定规则排序 |
Stream<T> limit(long maxSize) | 获取前几个元素 |
Stream<T> skip(long n) | 跳过前几个元素 |
Stream<T> distinct() | 去除流中重复的元素(对于自定义对象需重写hashCode() 和equals() 方法) |
<R> Stream<R> map(Function<? super T, ? extends R> mapper) | 对元素进行加工(“将e1变成e2”),并返回对应的新流 |
static <T> Stream<T> concat(Stream a, Stream b) | 合并a和b两个流为一个流 |
4.2 终结方法
终结方法:调用完成后不会返回新Stream,无法继续使用流了
Stream流常用终结方法 | 说明 |
---|---|
void forEach(Consumer action) | 对此流运算后的元素执行遍历 |
long count() | 统计此流运算后的元素个数 |
Optional<T> max(Comparator<? super T> comparator) | 获取此流运算后的最大值元素 |
Optional<T> min(Comparator<? super T> comparator) | 获取此流运算后的最小值元素 |
收集Stream流:把Stream流操作后的结果转回到集合或者数组中去返回,Stream流只能收集一次
用于收集Stream流的终结方法 | 说明 |
---|---|
R collect(Collector collector) | 把流处理后的结果收集到一个指定的集合中去 |
Object[] toArray() | 把流处理后的结果收集到一个数组中去 |
综合运用!
// 例1:过滤出所有大于等于60的数据,去重后降序排序,跳过前3个输出
List<Double> scores = new ArrayList<>();
Collection.addAll(scores, 92.4, 52.5, 1.2, 61.1, 75.0, 88.8, 34.9, 69.3, 71.1);
scores.stream().filter(s -> s >= 60).distinct().sorted((o1, o2) -> o2 - o1)
.skip(3).forEach(System.out::println);
// 例2:过滤出年龄(值)大于等于15且小于等于20的学生(键),按年龄降序排序输出
Map<String, Double> map = new HashMap<>();
map.put("teresa", 17);
map.put("akira", 1234);
map.put("undefined", 20);
map.put("kina", 21);
map.put("yohane", 16);
map.entrySet().stream().filter(e -> 15 <= e.getValue() && e.getValue() <= 20)
.sorted((o1, o2) -> o2.getValue() - o1.getValue())
.forEach(e -> System.out.println(e.getKey() + "->" + e.getValue()));
// 例3:获取所有名字长度小于等于6的学生的年龄,返回其最大值
Double maxAge = map.entrySet().stream().filter(e -> e.getKey().length() <= 6).map(e -> e.getValue())
.max((o1, o2) -> Double.compare(o1, o2)).get();
// 例4:合并两个流,再遍历输出其前3个
Stream<String> st1 = Stream.of("kira", "owns");
Stream<String> st2 = Stream.of("kira2nd", "again", "again", "sassy");
Stream<String> allSt = Stream.concat(st1, st2);
allSt.limit(3).forEach(System.out::println);
// 例5:把流st1、st2、scores中的结果分别放到List、Map、数组中
List<String> list = st1.collect(Collectors.toList());
Map<String, Integer> map = st2.distinct().collect(Collectors.toMap(s -> s, s -> s.length())); // 去重后分别设置键与值,此处为"字符串: 其长度"
Object[] arr = scores.toArray(); // 假如是转换成自定义类数组,则需在方法中设置转换后的元素(与toMap()设置键与值同理)