Java中List的删除、分类(分组)、排序、去重

前言

    本来没想要写的,但经常会用到,而且记不住区别就容易踩坑,只好记录一下,方便日后查阅。

一、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());
        }
    }

        上面五个案例的运行结果都大同小异,都可以把重复的“小红”去掉。运行结果如下:

  • 3
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值