List 是 Java 中十分常见的数据结构,它与 Set 不同的一点是,可以存放相同的数据。有时候,我们想知道 Arraylist 中有哪些重复数据,这里提供几个思路。
数据分析
下面给出了一个包含重复数据的 list:
List<String> list = Arrays.asList("A", "B", "C", "D", "E", "F", "E", "D", "E");
现在我们需要得到的数据为:
- E 重复 3 次
- D 重复 2 次
承接这样的数据,我们首先想到的就是 Map,它能够很方便地记录某个元素重复出现多少次。
实现方案
下面,我们将研究一些不同的方法来计算ArrayList中的重复元素,返回一个 Map。
循环计数
我们的预期结果是一个Map对象,它包含输入列表中的所有元素作为键,每个元素的计数作为值。
最直接的解决方案是遍历输入列表和每个元素:
- 如果resultMap包含该元素,我们将计数器加 1
- 否则,构建一个新的键值对
private static void duplList(List<String> list){
Map<String, Integer> map = new HashMap<>();
for(String str : list){
if(map.containsKey(str)){
map.put(str, map.get(str) + 1);
} else{
map.put(str, 1);
}
}
System.out.println(map);
}
当然,我们可以利用一些 Java8 中的特性来简化一下写法:
private static void duplList(List<String> list){
Map<String, Integer> map = new HashMap<>();
list.forEach(s -> map.put(s, map.getOrDefault(s, 0) + 1));
System.out.println(map);
}
{A=1, B=1, C=1, D=2, E=3, F=1}
Map 的 compute()
Map 中新加入的 compute() 方法,可以方便地操作 Map,其第二个参数是一个 BiFunction 接口。对于给定的键,它要么返回1,要么返回其当前值加 1.
private static void dupListCompute(List<String> list){
Map<String, Integer> map = new HashMap<>();
list.forEach(s -> map.compute(s, (k, v) -> v == null ? 1 : v + 1));
}
Map 的 merge()
使用 merge() 方法有个好处——不用处理空值。
private static void dupListMerge(List<String> list){
Map<String, Integer> map = new HashMap<>();
list.forEach(s -> map.merge(s, 1, Integer::sum));
}
Collectors.toMap()
Stream API 中也提供了非常多实用的方法:
private static void dupListToMap(List<String> list){
Map<String, Integer> map = list.stream()
.collect(Collectors.toMap(Function.identity(), v -> 1, Integer::sum));
}
方法被极致压缩到只有一行代码。
Collectors.groupingBy()
利用 Collectors.groupingBy()和Collectors.counting() 更近一步减少代码。
这是代码量最少的解决方案,也是最不容易看懂代码逻辑的方案。
private static void dupListGroupingBy(List<String> list){
Map<String, Long> collect = list.stream()
.collect(Collectors.groupingBy(s -> s, Collectors.counting()));
}
泛化
上面的例子中,我们默认的 List 中为 String ,实际上,该数据可以是任意类型。由于需要作为 Map 的key,该对象应该满足一定条件 。
为了泛化方法,适配各种不同类型的List,我们的方法签名可以改写如下:
public <T> Map<T, Long> dupList(List<T> inputList) {
return inputList.stream().collect(Collectors.groupingBy(k -> k, Collectors.counting()));
}
方法内部并没有用到 String 的特殊方法,因此,上述例子中方法体不用做任何修改即可适配所有场景。