前言
本来没想要写的,但经常会用到,而且记不住区别就容易踩坑,只好记录一下,方便日后查阅。
一、List删除某个或多个元素
List删除元素有多种方法,我目前学习到的有四种
(1)for循环-正序
@Test
public void test1() {
List<String> list = new ArrayList<String>();
list.add("小紫");
list.add("小白");
list.add("小红");
list.add("小紫");
list.add("小紫(被忽略)");
list.add("小紫");
list.add("小绿");
list.add("小兰");
list.add("小紫");
for (int i=0; i<list.size(); i++) {
if(list.get(i).contains("小紫")){
list.remove(i);//删除对应的元素
//break;
}
}
for (String string : list) {
System.out.println(string);
}
}
运行结果如下:
这种方法用来删除单个元素没什么问题,删除完直接break就好。但当它想删除多个元素时,就会出现上面的Bug,即漏了一个小紫。因为当删除掉某个元素后,list的大小发生了变化,数据自动向前进位,索引也跟着发生了变化。看着代码解释就是,当第一个小紫被删除后,小白的索引变为0,此时循环的 i 已经变为1,遍历时就已经把小白漏掉了。为了更清楚的展示效果,我在中间同时放入了三个小紫,当第一个小紫被删除后,被忽略的小紫向前进位,循环的 i 依然自顾自的增加,导致list.get(i)就直接读取第三小紫的身上,导致了Bug的产生。所以用的时候一定要注意这一点。
(2)for循环-倒序
@Test
public void test2() {
List<String> list = new ArrayList<String>();
list.add("小紫");
list.add("小白");
list.add("小红");
list.add("小紫");
list.add("小绿");
list.add("小兰");
list.add("小紫");
for (int i = list.size()-1; i > -1; i--) {
if(list.get(i).equals("小紫")){
list.remove(i);//删除对应的元素
//break;
}
}
for (String string : list) {
System.err.println(string);
}
}
运行结果如下:
这种方法没有上面的Bug,删除单个元素或多个元素都没问题,就是理解起来有点绕。因为他是反过来循环的,删除后发生进位的是已经遍历过的元素,所以不会影响接下来的遍历。
(3)增强for循环
@Test
public void test3() {
List<String> list = new ArrayList<String>();
list.add("小白");
list.add("小红");
list.add("小紫");
list.add("小绿");
list.add("小兰");
list.add("小紫");
for (String string : list) {
if(string.contains("小紫")){
list.remove(string);//删除对应的元素
break;
}
}
for (String string : list) {
System.out.println(string);
}
}
运行结果如下:
这种方法只能删除单个元素,如果把代码中的break去掉,想同时删除两个小紫,就会跳出 java.util.ConcurrentModificationException 异常。
(4)iterator迭代器遍历
@Test
public void test4() {
List<String> list = new ArrayList<String>();
list.add("小紫");
list.add("小白");
list.add("小红");
list.add("小紫");
list.add("小绿");
list.add("小兰");
list.add("小紫");
Iterator<String> it = list.iterator();
while(it.hasNext()){
String x = it.next();
if(x.equals("小紫")){
it.remove();
}
}
for (String string : list) {
System.out.println(string);
}
}
运行结果如下:
这种方法是目前我认为比较好用,也比较容易理解的一种,可删除某个元素,也可删除多个元素,不会报错。需要注意的是它用的是迭代器 Iterator 中的 remove() 方法,而不是List的,如果用list.remove() 也会报 java.util.ConcurrentModificationException 异常。
总结:删除单个元素用哪个方法都行,删除多个元素最好用(2)和(4),我遇到的坑都展示出来了,用哪个自己分辨吧。上面有提到ConcurrentModificationException异常,关于它的分析我担心篇幅太长,放在另一篇文章中:java.util.ConcurrentModificationException异常解决和分析。
拓展:
有时不仅仅要删除元素,还要增加、修改元素,但 Iterator 只有 remove() 操作,这时怎么办?Iterator 的子类 ListIterator 就派上用场啦!
@Test
public void test5() {
List<String> list = new ArrayList<String>();
list.add("小紫");
list.add("小白");
list.add("小红");
list.add("小紫");
list.add("小绿");
list.add("小兰");
list.add("小紫");
ListIterator<String> it = list.listIterator();
while(it.hasNext()){
String s = it.next();
if(s.equals("小紫")){
it.remove();
}
if (s.equals("小白")) {
it.set("小黑");
it.add("插班生小叉");
}
}
for (String string : list) {
System.out.println(string);
}
}
运行结果如下:
二、List分类(分组)
主要有两种方式,一种是普通的循环遍历去判断划分,另一种则是使用jdk8的流式编程对list集合进行分组。为了方便演示,我新建了Users对象。有如下参数:
同时,设置了一些测试数据。
public static void main(String[] args) {
List<Student> list = new ArrayList<Student>();
list.add(new Student(1101, "小白", 18, "男", new BigDecimal(100)));
list.add(new Student(1102, "小紫", 16, "女", new BigDecimal(95)));
list.add(new Student(1103, "小红", 17, "女", new BigDecimal(98)));
list.add(new Student(1104, "小绿", 16, "男", new BigDecimal(96)));
list.add(new Student(1105, "小兰", 18, "女", new BigDecimal(99)));
list.add(new Student(1106, "小蓝", 18, "男", new BigDecimal(92)));
//test1(list);
//test2(list);
//test3(list);
//test4(list);
test5(list);
}
(1)循环遍历判断分组
private static void test1(List<Student> list) {
Map<Integer, List<Student>> map = new HashMap<>();
//分组
for (Student user:list){
if (map.containsKey(user.getAge())) { //判断是否存在该key
map.get(user.getAge()).add(user); //存在就获取该key的value然后add
} else {
List<Student> lt = new ArrayList<>();
lt.add(user);
map.put(user.getAge(), lt); //不存在就put
}
}
for (Integer key:map.keySet()){
System.out.println(map.get(key));
for (Student user:map.get(key)){
System.out.println(key + " --- "+ user.getName());
}
}
}
(2)jdk8的流式编程分组
private static void test2(List<Student> list) {
//使用jdk8的流式编程对list集合进行分组
Map<Integer, List<Student>> map = list.stream().collect(Collectors.groupingBy(t -> t.getAge()));
for (Integer key:map.keySet()){
System.out.println(map.get(key));
for (Student user:map.get(key)){
System.out.println(key + " --- "+ user.getName());
}
}
}
运行结果是一样滴,如下
其实两种方法都可以,各有各的优缺点,使用中自己体会咯!参考文章
拓展:
1、List过滤
private static void test3(List<Student> list) {
//过滤出符合条件的数据
List<Student> filterList = list.stream().filter(t -> t.getSex().equals("女")).collect(Collectors.toList());
for (Student student : filterList) {
System.out.println(student.getName());
}
}
2、List排序
private static void test4(List<Student> list) {
list.sort(Comparator.comparingDouble(t -> t.getScore().doubleValue()));
for (Student student : list) {
System.out.println(student.getName() + " --- " +student.getScore());
}
}
3、List求和
private static void test5(List<Student> list) {
//计算 总年龄
//BigDecimal totalMoney = list.stream().map(Student::getScore).reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal totalMoney = list.stream().map(t -> t.getScore()).reduce(BigDecimal.ZERO, (a,b)->a.add(b));
System.out.println("总成绩:" + totalMoney);
}
注:求和这里涉及到了两个知识点,一个是 reduce() 方法,一个是 :: 双冒号操作符。
(1)reduce 可以说是一个迭代运算器,在上述例子中,它的作用可以理解为一个“累加器”,具体的分析可以去参考这篇文章。
(2)双冒号运算操作符是类方法的句柄,lambda表达式的一种简写,这种简写的学名叫eta-conversion或者叫η-conversion。下面的例子顺带演示了一下用双冒号操作符遍历的样子。lambda表达式和双冒号运算操作符之间的转换就自己体会吧。
三、List去重
去重的方式有几种,我简单的列举一些我目前了解到的。
public static void main(String[] args) {
List<Student> list = new ArrayList<Student>();
Student student = new Student(1103, "小红", 17, "女", new BigDecimal(98));
list.add(new Student(1101, "小白", 18, "男", new BigDecimal(100)));
list.add(new Student(1102, "小紫", 16, "女", new BigDecimal(95)));
list.add(student);
list.add(student);
list.add(new Student(1104, "小绿", 16, "男", new BigDecimal(96)));
list.add(new Student(1105, "小兰", 18, "女", new BigDecimal(99)));
list.add(new Student(1106, "小蓝", 18, "男", new BigDecimal(92)));
list.forEach(ListDeal::printValur);
//等同于
Consumer<Student> methodParam = ListDeal::printValur; // 方法参数
list.forEach(x -> methodParam.accept(x));// 方法执行accept,可变成 list.forEach(methodParam::accept);
test1(list);
//test2(list);
//test3(list);
//test4(list);
//test5(list);
}
(1)案例一:利用Java8的特性去重
private static void test1(List<Student> list) {
List<Student> myList = list.stream().distinct().collect(Collectors.toList());
for (Student student : myList) {
System.out.println(student.getName() + " --- " +student.getScore());
}
}
private static void printValur(Student student) {
System.out.println(student.getName());
}
(2)案例二:新建对象遍历去重
private static void test2(List<Student> list) {
List<Student> listNew = new ArrayList<Student>();
for (Student str : list) {
if (!listNew.contains(str)) {
listNew.add(str);//遍历并判断是否有重复的数据,无则添加
}
}
for (Student student : listNew) {
System.out.println(student.getName() + " --- " +student.getScore());
}
}
(3)案例三:利用Set集合特性无序去重
private static void test3(List<Student> list) {
final boolean sta = (null != list) && (list.size() > 0);
List<Student> doubleList= new ArrayList<Student>();
if (sta) {
Set<Student> set = new HashSet<Student>();
set.addAll(list);//由于Set的无序性,顺序会被打乱
doubleList.addAll(set);
}
for (Student student : doubleList) {
System.out.println(student.getName() + " --- " +student.getScore());
}
}
(4)案例四:利用Set集合特性保持顺序一致去重
private static void test4(List<Student> list) {
//方法一
List<Student> listNew = new ArrayList<Student>(new TreeSet<Student>(list));
//方法二
List<Student> listNew2 = new ArrayList<Student>(new LinkedHashSet<Student>(list));
for (Student student : listNew2) {
System.out.println(student.getName() + " --- " +student.getScore());
}
}
这个案例需要注意的是,treeSet为了确保他是有序的就必须比较,这个时候发现这两个key根本无法比较,则抛出该异常错误。所以Student对象需要implements Comparable,并重写 compareTo() 方法,否则会报 Student cannot be cast to class java.lang.Comparable 的类型转换错误。上述案例中我重写了 compareTo() 方法,在其中去比较他们的 id ,所以不会报错。
@Override
public int compareTo(Student stu) {
// TODO Auto-generated method stub
return this.id - stu.id;
}
(5)案例五:利用List自身的 remove() 方法
private static void test5(List<Student> list) {
if (null != list && list.size() > 0) {
//循环list集合,类似冒泡排序,理解起来比较绕
for( int i = 0 ; i < list.size() - 1 ; i++ ) {
for( int j = list.size() - 1; j > i; j-- ) {
if (list.get(j).equals(list.get(i))) {
list.remove(j);
}
}
}
}
for (Student student : list) {
System.out.println(student.getName() + " --- " +student.getScore());
}
}
上面五个案例的运行结果都大同小异,都可以把重复的“小红”去掉。运行结果如下: