【Java基础】集合(Map、Stream流)

Map集合

需要存储一一对应的数据时,就可以考虑使用Map集合来做。

  • Map集合称为双列集合,格式:{key1=value1 , key2=value2 , key3=value3 , …}, 一次需要存一对数据做为一个元素。
  • Map集合的每个元素“key=value”称为一个键值对/键值对对象/Entry对象,Map集合也被叫做“键值对集合”。
  • Map集合的所有键是不允许重复的,但值可以重复,键和值是一一对应的,每一个键只能找到自己对应的值。

Map集合体系

Map<K,V>是接口,下面有很多实现类:

  1. HashMap<K , V>。
  2. LinkedHashMap<K , V>。(是1的子类。)
  3. TreeMap<K , V>。

特点:

  1. Map系列集合的特点都是由键决定的,值只是一个附属品,值是不做要求的。
  2. HashMap(由键决定特点): 无序、不重复、无索引; (用的最多)。
  3. LinkedHashMap (由键决定特点):由键决定的特点:有序、不重复、无索引。
  4. TreeMap (由键决定特点):按照(键的)大小默认升序排序、不重复、无索引。
1
2
//经典代码
Map<String, Integer> map = new HashMap<>(); //同样需要指定具体类。

Map常用方法

  1. public V put(K key,V value)。添加元素。
  2. public int size()。获取集合的大小。
  3. public void clear()。清空集合。
  4. public boolean isEmpty()。判断集合是否为空,为空返回true , 反之。
  5. public V get(Object key)。根据键获取对应值
  6. public V remove(Object key)。根据键删除整个元素。
  7. public boolean containsKey(Object key)。判断是否包含某个键。(是精确匹配。)
  8. public boolean containsValue(Object value)。判断是否包含某个值。(值的类型一定要注意。)
  9. public Set keySet()。获取全部键的集合。(会放到一个Set里面再返回。)
  10. public Collection values()。获取Map集合的全部值。(会放到一个Collection里面再返回,因为Set里面不允许有重复的值。)
  11. putAll()。把其他Map集合的数据倒入到自己集合中来。

Map遍历方式

  1. 键找值。先获取Map集合全部的键,再通过遍历键来找值。
  2. 键值对。把“键值对“看成一个整体进行遍历(难度较大)。
  3. Labmda。JDK 1.8开始之后的新技术(非常的简单)。

键找值

  1. public Set keySet()。获取所有键的集合。
  2. public V get(Object key)。根据键获取其对应的值。
1
2
3
4
5
6
7
8
9
10
// 1、获取Map集合的全部键
Set<String> keys = map.keySet();
// System.out.println(keys);
// [蜘蛛精, 牛魔王, 至尊宝, 紫霞]
// key
// 2、遍历全部的键,根据键获取其对应的值
for (String key : keys) {
// 根据键获取对应的值
double value = map.get(key);
System.out.println(key + "=====>" + value);

键值对

使用增强for遍历时,元素类型无法确定。所以Java提供另一个方法:

  • Set<Map.Entry<K, V>> entrySet()。获取所有“键值对”的集合。作为一个Set集合返回了,然后每个元素就是键值对类型,自然也就可以遍历了。

Map.Entry提供的方法:

  1. K getKey()。获取键。
  2. V getValue()。获取值。
1
2
3
4
5
6
// 1、调用Map集合提供entrySet方法,把Map集合转换成键值对类型的Set集合
Set<Map.Entry<String, Double>> entries = map.entrySet(); //ctrl+alt+v
for (Map.Entry<String, Double> entry : entries) {
String key = entry.getKey();
double value = entry.getValue();
System.out.println(key + "---->" + value);

Labmda(简单,推荐)

JDK 1.8开始。需要用到下面的方法:

  • default void forEach(BiConsumer<? super K, ? super V> action)。结合lambda遍历Map集合。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//遍历map集合,传递Lambda表达式
map.forEach(( k, v) -> {
System.out.println(k + "---->" + v);
});

//来看看原来的形式
//遍历map集合,传递匿名内部类(BiConsumer是一个接口)
map.forEach(new BiConsumer<String, Double>() {
@Override
public void accept(String k, Double v) {
System.out.println(k + "---->" + v);
}
});
//forEach内部其实是利用了第2种遍历方法(键值对)

(1)HashMap

HashMap(由键决定特点): 无序、不重复、无索引; (用的最多)。

底层原理

  • 和HashSet的底层原理一样,基于哈希表实现。实际上:原来学的Set系列集合的底层就是基于Map实现的,只是Set集合中的元素只要键数据,不要值数据而已。
  • JDK8之前,哈希表 = 数组+链表;JDK8开始,哈希表 = 数组+链表+红黑树;哈希表是一种增删改查数据,性能都较好的数据结构。但是它是无序,不能重复,没有索引支持的(由键决定特点)。
  • HashMap的键依赖hashCode方法和equals方法保证键的唯一
  • 如果键存储的是自定义类型的对象,可以通过重写hashCode和equals方法,这样可以保证多个对象内容一样时,HashMap集合就能认为是重复的。

(2)LinkedHashMap

LinkedHashMap (由键决定特点):由键决定的特点:有序、不重复、无索引。

底层原理

  • 底层数据结构依然是基于哈希表实现的,只是每个键值对元素又额外的多了一个双链表的机制记录元素顺序(保证有序)。实际上:原来学习的LinkedHashSet集合的底层原理就是LinkedHashMap。

(3)TreeMap

TreeMap (由键决定特点):按照(键的)大小默认升序排序、不重复、无索引。

底层原理

  • 特点:不重复、无索引、可排序(按照键的大小默认升序排序,只能对键排序)。
  • 原理:TreeMap跟TreeSet集合的底层原理是一样的,都是基于红黑树实现的排序。
  • TreeMap集合同样也支持两种方式来指定排序规则。
    • 让类实现Comparable接口,重写conpareTo()比较规则。
    • TreeMap集合有一个有参数构造器,支持创建Comparator比较器对象,以便用来指定比较规则。

补充知识:集合的嵌套

集合中的元素又是一个集合。

Stream流

JDK8开始最大的改变之一。(一共是两个:1.Lambda表达式,2.Stream流。)

什么是Stream?

  • 也叫Stream流,是Jdk8开始新增的一套API (java.util.stream.*),可以用于操作集合或者数组的数据。
  • 优势: Stream流大量的结合了Lambda的语法风格来编程,提供了一种更加强大,更加简单的方式操作集合或者数组中的数据,代码更简洁,可读性更好。

案例

有一个List集合,元素有"张三丰","张无忌","周芷若","赵敏","张强",找出姓张,且是3个字的名字,存入到一个新集合中去。

1
2
3
List<String> names = new ArrayList<>();
Collections.addAll(names, "张三丰","张无忌","周芷若","赵敏","张强");
System.out.println(names);

传统方式:

1
2
3
4
5
6
7
8
// 找出姓张,且是3个字的名字,存入到一个新集合中去。
List<String> list = new ArrayList<>();
for (String name : names) {
if(name.startsWith("张") && name.length() == 3){
list.add(name);
}
}
System.out.println(list);

Stream流:(支持链式编程。)

1
2
List<String> list2 = names.stream().filter(s -> s.startsWith("张")).filter(a -> a.length()==3).collect(Collectors.toList());
System.out.println(list2);

两次filter筛选,然后collect收集。

使用步骤

  1. 数据源。–>获取其Stream流(理解为流水线,能与数据源建立联系)。
  2. 中间方法。调用流水线的各种方法对数据进行处理和计算。例如,过滤、排序、去重等。
  3. 获取结果。便利、统计、收集到一个新的集合中并返回。
1
2
3
4
5
主要掌握下面四点:
1、如何获取List集合的Stream流?
2、如何获取Set集合的Stream流?
3、如何获取Map集合的Stream流?
4、如何获取数组的Stream流?

常用方法

获取Stream流

如何获取集合的Stream流?

  • Collection类。default Stream stream()。获取当前集合对象的Stream流。

如何获取数组的Stream流?

  • Arrays类。public static Stream stream(T[] array)。获取当前数组的Stream流。
  • Stream类。public static Stream of(T… values)。获取当前接收数据的Stream流。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
* 目标:掌握Stream流的创建。
*/
public class StreamTest2 {
public static void main(String[] args) {
// 1、如何获取List集合的Stream流?
List<String> names = new ArrayList<>();
Collections.addAll(names, "张三丰","张无忌","周芷若","赵敏","张强");
Stream<String> stream = names.stream();

// 2、如何获取Set集合的Stream流?
Set<String> set = new HashSet<>();
Collections.addAll(set, "刘德华","张曼玉","蜘蛛精","马德","德玛西亚");
Stream<String> stream1 = set.stream();
stream1.filter(s -> s.contains("德")).forEach(s -> System.out.println(s));

// 3、如何获取Map集合的Stream流?
Map<String, Double> map = new HashMap<>();
map.put("古力娜扎", 172.3);
map.put("迪丽热巴", 168.3);
map.put("马尔扎哈", 166.3);
map.put("卡尔扎巴", 168.3);

Set<String> keys = map.keySet();
Stream<String> ks = keys.stream();

Collection<Double> values = map.values();
Stream<Double> vs = values.stream();

Set<Map.Entry<String, Double>> entries = map.entrySet();
Stream<Map.Entry<String, Double>> kvs = entries.stream();
kvs.filter(e -> e.getKey().contains("巴"))
.forEach(e -> System.out.println(e.getKey()+ "-->" + e.getValue()));

// 4、如何获取数组的Stream流?
String[] names2 = {"张翠山", "东方不败", "唐大山", "独孤求败"};
Stream<String> s1 = Arrays.stream(names2);
Stream<String> s2 = Stream.of(names2);
}
}

中间方法

中间方法指的是调用完成后会返回新的Stream流,可以继续使用(支持链式编程)。(因此支持链式编程。)

常用方法

  1. Stream filter(Predicate<? super T> predicate)。用于对流中的数据进行过滤。
  2. Stream sorted()。对元素进行升序排序。
  3. Stream sorted(Comparator<? super T> comparator)。对元素进行升序排序。
  4. Stream limit(long maxSize)。获取前几个元素。
  5. Stream skip(long n)。跳过前几个元素。
  6. Stream distinct()。去除流中重复的元素。
  7. Stream map(Function<? super T,? extends R> mapper)。对元素进行加工,并返回对应的新流。
  8. static Stream concat(Stream a, Stream b)。合并a和b两个流为一个流。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**
* 目标:掌握Stream流提供的常见中间方法。
*/
public class StreamTest3 {
public static void main(String[] args) {
List<Double> scores = new ArrayList<>();
Collections.addAll(scores, 88.5, 100.0, 60.0, 99.0, 9.5, 99.6, 25.0);
// 需求1:找出成绩大于等于60分的数据,并升序后,再输出。
scores.stream().filter(s -> s >= 60).sorted().forEach(s -> System.out.println(s));

List<Student> students = new ArrayList<>();
Student s1 = new Student("蜘蛛精", 26, 172.5);
Student s2 = new Student("蜘蛛精", 26, 172.5);
Student s3 = new Student("紫霞", 23, 167.6);
Student s4 = new Student("白晶晶", 25, 169.0);
Student s5 = new Student("牛魔王", 35, 183.3);
Student s6 = new Student("牛夫人", 34, 168.5);
Collections.addAll(students, s1, s2, s3, s4, s5, s6);
// 需求2:找出年龄大于等于23,且年龄小于等于30岁的学生,并按照年龄降序输出.
students.stream().filter(s -> s.getAge() >= 23 && s.getAge() <= 30)
.sorted((o1, o2) -> o2.getAge() - o1.getAge())
.forEach(s -> System.out.println(s));

// 需求3:取出身高最高的前3名学生,并输出。
students.stream().sorted((o1, o2) -> Double.compare(o2.getHeight(), o1.getHeight()))
.limit(3).forEach(System.out::println);
System.out.println("-----------------------------------------------");

// 需求4:取出身高倒数的2名学生,并输出。 s1 s2 s3 s4 s5 s6
students.stream().sorted((o1, o2) -> Double.compare(o2.getHeight(), o1.getHeight()))
.skip(students.size() - 2).forEach(System.out::println);

// 需求5:找出身高超过168的学生叫什么名字,要求去除重复的名字,再输出。
students.stream().filter(s -> s.getHeight() > 168).map(Student::getName)
.distinct().forEach(System.out::println);

// distinct去重复,自定义类型的对象(希望内容一样就认为重复,重写hashCode,equals)
students.stream().filter(s -> s.getHeight() > 168)
.distinct().forEach(System.out::println);

Stream<String> st1 = Stream.of("张三", "李四");
Stream<String> st2 = Stream.of("张三2", "李四2", "王五");
Stream<String> allSt = Stream.concat(st1, st2); //如果合并的两个类型不一样,需要用Object来接。
allSt.forEach(System.out::println);
}
}

终结方法

终结方法指的是调用完成后,不会返回新Stream了,没法继续使用流了。

常用方法

  1. void forEach(Consumer action)。对此流运算后的元素执行遍历。
  2. long count()。统计此流运算后的元素个数。
  3. Optional max(Comparator<? super T> comparator)。获取此流运算后的最大值元素。
  4. Optional min(Comparator<? super T> comparator)。获取此流运算后的最小值元素。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
* 目标:Stream流的终结方法
*/
public class StreamTest4 {
public static void main(String[] args) {
List<Student> students = new ArrayList<>();
Student s1 = new Student("蜘蛛精", 26, 172.5);
Student s2 = new Student("蜘蛛精", 26, 172.5);
Student s3 = new Student("紫霞", 23, 167.6);
Student s4 = new Student("白晶晶", 25, 169.0);
Student s5 = new Student("牛魔王", 35, 183.3);
Student s6 = new Student("牛夫人", 34, 168.5);
Collections.addAll(students, s1, s2, s3, s4, s5, s6);
// 需求1:请计算出身高超过168的学生有几人。
long size = students.stream().filter(s -> s.getHeight() > 168).count();
System.out.println(size);

// 需求2:请找出身高最高的学生对象,并输出。
Student s = students.stream().max((o1, o2) -> Double.compare(o1.getHeight(), o2.getHeight())).get();
System.out.println(s);

// 需求3:请找出身高最矮的学生对象,并输出。
Student ss = students.stream().min((o1, o2) -> Double.compare(o1.getHeight(), o2.getHeight())).get();
System.out.println(ss);

// 需求4:请找出身高超过170的学生对象,并放到一个新集合中去返回。
// 流只能收集一次。
List<Student> students1 = students.stream().filter(a -> a.getHeight() > 170).collect(Collectors.toList());
System.out.println(students1);

Set<Student> students2 = students.stream().filter(a -> a.getHeight() > 170).collect(Collectors.toSet());
System.out.println(students2);

// 需求5:请找出身高超过170的学生对象,并把学生对象的名字和身高,存入到一个Map集合返回。
Map<String, Double> map =
students.stream().filter(a -> a.getHeight() > 170)
.distinct().collect(Collectors.toMap(a -> a.getName(), a -> a.getHeight()));
System.out.println(map);

// Object[] arr = students.stream().filter(a -> a.getHeight() > 170).toArray();
Student[] arr = students.stream().filter(a -> a.getHeight() > 170).toArray(len -> new Student[len]);
System.out.println(Arrays.toString(arr));
}
}

其他方法

  • 收集Stream流。就是把Stream流操作后的结果转回到集合或者数组中去返回。
  • Stream流:方便操作集合/数组的手段; 集合/数组:才是开发中的目的。
  1. R collect(Collector collector)。把流处理后的结果收集到一个指定的集合中去。
  2. Object[] toArray()。把流处理后的结果收集到一个数组中去。

collect具体的收集方式

  1. public static Collector toList()。把元素收集到List集合中。
  2. public static Collector toSet()。把元素收集到Set集合中。注意Set会去重
  3. public static Collector toMap(Function keyMapper , Function valueMapper)。把元素收集到Map集合中。注意需要在收集的时候指定键和值。而且它不能帮我们去重,因此可能报错,要自己在中间加一个distict()方法。

注意,流只能收集一次!不可以先用一个Steam对象接住Steam流,然后做两次收集操作。