目录
1 List元素去重
1.1 移除List中指定某一元素
1.1.1 For循环移除
1.1.1.1 For移除不彻底问题
假如去除List
中的Morning
元素
@Test
public void testRemoveDuplicate(){
List<String> strings = Arrays.asList("Morning", "Midday", "Evening", "Night","Morning","Morning","Morning","Midday");
List<String> l1= new ArrayList<>(strings);
for (int i = 0; i < l1.size(); i++) {
if("Morning".equals(l1.get(i))){
l1.remove(i);
}
}
System.out.println(l1);
}
执行结果:
[Midday, Evening, Night, Morning, Midday]
查看执行结果发现还有元素Morning
没有去掉
1.1.1.2 用 i-- 解决问题
产生的原因就是:ArrayList
是一个数组元素的集合当删掉第一个元素Morning
后,集合后面的元素会往前移动,但是此时 i 又指向下一个元素
解决办法:在 list.remove(i)
后 i--
修改后:
@Test
public void testRemoveDuplicate(){
List<String> strings = Arrays.asList("Morning", "Midday", "Evening", "Night","Morning","Morning","Morning","Midday");
List<String> l1= new ArrayList<>(strings);
for (int i = 0; i < l1.size(); i++) {
if("Morning".equals(l1.get(i))){
l1.remove(i);
i--;
}
}
System.out.println(l1);
}
执行结果:
[Midday, Evening, Night, Midday]
1.1.1.3 倒序遍历移除元素
@Test
public void testRemoveDuplicate(){
List<String> strings = Arrays.asList("Morning", "Midday", "Evening", "Night","Morning","Morning","Morning","Midday");
List<String> l1= new ArrayList<>(strings);
for (int i = l1.size()-1; i >=0 ; i--) {
if("Morning".equals(l1.get(i))){
l1.remove(i);
}
}
System.out.println(l1);
}
执行结果:
[Midday, Evening, Night, Midday]
1.1.2 ForEach移除
1.1.2.1 ConcurrentModificationException异常
@Test
public void testRemoveDuplicate(){
List<String> strings = Arrays.asList("Morning", "Midday", "Evening", "Night","Morning","Morning","Morning","Midday");
List<String> l1= new ArrayList<>(strings);
for (String str:l1) {
if("Morning".equals(str:l1)){
l1.remove(i);
}
}
System.out.println(l1);
}
执行结果:
[Midday, Evening, Night, Midday]
会抛出 ConcurrentModificationException
异常
ConcurrentModificationException
:当方法检测到对象的并发修改,但不允许这种修改时,抛出此异常。
原因:ArraayList
迭代器有个Itr
,其内部有个属性expectedModCount
。而集合有个fail-fast
快速失败检测机制,当进行remove()
操作时,会比对expectedModCount
是否与modCount
相等,而前者一般不会改变,但 remove
操作会导致 modCount
发生改变。一旦两者不等,就会抛出ConcurrentModificationException
异常。
解决办法:使用迭代器remove()
方法
1.1.2.2 iterator遍历
@Test
public void testRemoveDuplicate(){
List<String> strings = Arrays.asList("Morning", "Midday", "Evening", "Night","Morning","Morning","Morning","Midday");
List<String> l1= new ArrayList<>(strings);
Iterator<String> iterator = l1.iterator();
while (iterator.hasNext()){
String next = iterator.next();
if("Morning".equals(next)){
iterator.remove();
}
}
System.out.println(l1);
}
执行结果:
[Midday, Evening, Night, Midday]
1.2 移除List中重复元素
1.2.1 ForEach添加去重
这个是创建一个空的List
,添加前判断下存在与否,不存在才添加,这样就抱着了元素不重复
@Test
public void testRemoveDuplicate(){
List<String> strings = Arrays.asList("Morning", "Midday", "Evening", "Night","Morning","Morning","Morning","Midday");
List<String> l1= new ArrayList<>(strings);
List<String> l2 = new ArrayList<>();
for(String str:l1){
if(!l2.contains(str)){
l2.add(str);
}
}
System.out.println(l2);
}
执行结果:
[Midday, Evening, Night, Midday]
1.2.2 For双循环去重
从头和尾一起遍历,判断是否有相等,再移除
@Test
public void testRemoveDuplicate(){
List<String> strings = Arrays.asList("Morning", "Midday", "Evening", "Night","Morning","Morning","Morning","Midday");
List<String> l1= new ArrayList<>(strings);
for (int i = 0; i < l1.size()-1; i++) {
for(int j=l1.size()-1;j>i;j--){
if(l1.get(j).equals(l1.get(i))){
l1.remove(j);
System.out.println("i=:"+i+",j=:"+j+l1);
}
}
}
System.out.println(l1);
}
执行结果:
i=:0,j=:6[Morning, Midday, Evening, Night, Morning, Morning, Midday]
i=:0,j=:5[Morning, Midday, Evening, Night, Morning, Midday]
i=:0,j=:4[Morning, Midday, Evening, Night, Midday]
i=:1,j=:4[Morning, Midday, Evening, Night]
[Morning, Midday, Evening, Night]
1.2.3 ForEach循环重复坐标去重
这个方式需要先复制出一个List2
,再循环遍历List2
,判断List
中的元素首尾出现的坐标位置是否一致,若一致,则说明没有重复的,否则重复,并移除重复位置的元素
@Test
public void testRemoveDuplicate(){
List<String> strings = Arrays.asList("Morning", "Midday", "Evening", "Night","Morning","Morning","Morning","Midday");
List<String> l1= new ArrayList<>(strings);
List<String> l2= new ArrayList<>(strings);
for(String s2:l2){
if(l1.indexOf(s2)!=l1.lastIndexOf(s2)){
l1.remove(l1.lastIndexOf(s2));
}
}
System.out.println(l1);
}
执行结果:
[Morning, Midday, Evening, Night]
1.2.4 Set去重
@Test
public void testRemoveDuplicate(){
List<String> strings = Arrays.asList("Morning", "Midday", "Evening", "Night","Morning","Morning","Morning","Midday");
List<String> l1= new ArrayList<>(strings);
List<String> l2= new ArrayList<>(new HashSet(l1));
System.out.println(l2);
}
执行结果:
[Evening, Night, Morning, Midday]
这个方式去重重复元素最简单,但是不能保证顺序,可以把HashSet
替换成LinkedHashSet
,就可以保证原来的顺序了
@Test
public void testRemoveDuplicate(){
List<String> strings = Arrays.asList("Morning", "Midday", "Evening", "Night","Morning","Morning","Morning","Midday");
List<String> l1= new ArrayList<>(strings);
List<String> l2= new ArrayList<>(new LinkedHashSet(l1));
System.out.println(l2);
}
执行结果:
[Morning, Midday, Evening, Night]
1.2.5 Stream去重
@Test
public void testRemoveDuplicate(){
List<String> strings = Arrays.asList("Morning", "Midday", "Evening", "Night","Morning","Morning","Morning","Midday");
List<String> l1= new ArrayList<>(strings);
List<String> collect = l1.stream().distinct().collect(Collectors.toList());
System.out.println(collect);
}
执行结果:
[Morning, Midday, Evening, Night]
2 List对象去重
2.1 使用的实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
@String
public class User(){
private String userId;
private String userName;
private Integer userAge;
}
2.2 利用Collectors.toMap去重
2.2.1 toMap去重说明
List<User> userList = new ArrayList<>();
userList.add(new User("a", "xiaoming",12));
userList.add(new User("b", "xiaoming",13));
userList.add(new User("d", "xiaoming",15));
userList.add(new User("a", "xiaoming",14));
System.out.println("利用Collectors.toMap去重:");
//利用Collectors.toMap去重
userList.stream()
//或者这样写 Collectors.toMap(m -> m.getUserId(),
.collect(Collectors.toMap(User::getUserId,
Function.identity(), (oldValue, newValue) -> oldValue))
.values()
.stream()
.forEach(System.out::println); //打印
输出结果:
[{"userAge":12,"userId":"a","userName":"xiaoming"},
{"userAge":13,"userId":"b","userName":"xiaoming"},
{"userAge":15,"userId":"d","userName":"xiaoming"}]
其中,Collectors.toMap
需要使用三个参数的版本,前两个参数一个是keyMapper
函数一个是valueMapper
函数的,用过toMap
的都知道,去重的关键在于第三个参数BinaryOperator
函数接口。
这个BinaryOperator
函数接收两个参数,如上面代码,一个oldValue
,一个newValue
,字面意思,第一个旧值,第二个是新值。当stream
构造Map时,会先调用Map的get方法获取该key对应节点的旧值,如果该值为null,则不会调用BinaryOperator
函数,如果不为null,则会把获取的旧值与新值作为参数传给函数执行,然后把函数的返回值作为新值put到Map中。如果看不懂,请看源码吧
2.2.2 Funcion.identity()解释
Java 8
允许在接口中加入具体方法。接口中的具体方法有两种,default
方法和static
方法,identity()
就是Function
接口的一个静态方法
。
Function.identity()
返回一个输出跟输入一样的Lambda
表达式对象,等价于形如t -> t
形式的Lambda
表达式。
identity()
方法JDK源码如下:
static Function identity() {
return t -> t;
}
下面的代码中,Task::getTitle
需要一个task
并产生一个仅有一个标题的key
task -> task
是一个用来返回自己的lambda
表达式,上例中返回一个task
private static Map<String, Task> taskMap(List<Task> tasks) {
return tasks.stream().collect(toMap(Task::getTitle, task -> task));
}
可以使用Function
接口中的默认方法identity
来让上面的代码代码变得更简洁明了、传递开发者意图时更加直接,下面是采用identity函数的代码。
import static java.util.function.Function.identity;
private static Map<String, Task> taskMap(List<Task> tasks) {
return tasks.stream().collect(toMap(Task::getTitle, identity()));
}
2.3 利用Collectors.toCollection和TreeSet去重
List<User> userList = new ArrayList<>();
userList.add(new User("a", "xiaoming",12));
userList.add(new User("b", "xiaoming",13));
userList.add(new User("d", "xiaoming",15));
userList.add(new User("a", "xiaoming",14));
System.out.println("利用Collectors.toMap去重:");
//利用Collectors.toMap去重
userList.stream()
.collect(Collectors.toCollection(() ->
new TreeSet<>(Comparator.comparing(User::getUserId))))
.values()
.stream()
.forEach(System.out::println); //打印
输出结果:
[{"userAge":12,"userId":"a","userName":"xiaoming"},
{"userAge":13,"userId":"b","userName":"xiaoming"},
{"userAge":15,"userId":"d","userName":"xiaoming"}]
利用TreeSet
原理去重,TreeSet
内部使用的是TreeMap
,使用指定Comparator
比较元素,如果元素相同,则新元素代替旧元素,
TreeMap
的put
方法来放入元素的,有兴趣可以自己找源码
如果不想要返回TreeSet
类型,那也可以使用Collectors.collectingAndThen
转换成ArrayList
,也可以用new ArrayList(set)
,原理一样,如下:
List<User> distinctList = userList.stream()
.collect(Collectors.collectingAndThen(Collectors.toCollection(()
-> new TreeSet<>(Comparator.comparing(User::getUserId))), ArrayList::new));
注意
:如果想根据两个或三个字段去重,可以在上述三个方法中的Comparator.comparing(User::getUserId))
修改为Comparator.comparing(u -> u.getUserId() +"#" + u.getUserName() ))
这样就是根据两个字段去重,中间的#
作用就是为了增加辨识度,也可以不加这个#
,无论多少个字段去重只用在这里用+
连接就可以了