List集合对象去重及按属性去重的8种方法

List集合对象去重及按属性去重的8种方法

初始化数据

public class ListRmDuplicate {
  private List<String> list;
  private List<Player> playerList;

  @BeforeEach
  public void setup() {
    list  =  new ArrayList<>();
    list.add("kobe");
    list.add("james");
    list.add("curry");
    list.add("zimug");
    list.add("zimug");

    playerList= new ArrayList<>();
    playerList.add(new Player("kobe","10000"));  //科比万岁
    playerList.add(new Player("james","32"));
    playerList.add(new Player("curry","30"));
    playerList.add(new Player("zimug","27"));   // 注意这里名字重复
    playerList.add(new Player("zimug","18"));   //注意这里名字和年龄重复
    playerList.add(new Player("zimug","18")); //注意这里名字和年龄重复

  }
}

一、元素整体去重

  1. 先把List数据放入Set,因为Set数据结构本身具有去重的功能,所以再将SET转为List之后就是去重之后的结果。这种方法在去重之后会改变原有的List元素顺序,因为HashSet本身是无序的,而TreeSet排序也不是List种元素的原有顺序。
@Test
void testRemove1()  {
  /*Set set = new HashSet<>(list);
  List newList = new ArrayList<>(set);*/

  //去重并排序的方法(如果是字符串,按字母表排序。如果是对象,按Comparable接口实现排序)
  //List newList = new ArrayList<>(new TreeSet<>(list));

  //简写的方法
  List<String> newList = new ArrayList<>(new HashSet<>(list));

  System.out.println( "去重后的集合: " + newList);
}

去重后的集合: [kobe, james, zimug, curry]
  1. 先用stream方法将集合转换成流,然后distinct去重,最后在将Stream流collect收集为List
@Test
void testRemove2()  {
  List<String> newList = list.stream().distinct().collect(Collectors.toList());

  System.out.println( "去重后的集合: " + newList);
}

去重后的集合: [kobe, james, curry, zimug]
  1. 利用set.add(T),如果T元素已经存在集合中,就返回false。利用这个方法进行是否重复的数据判断,如果不重复就放入一个新的newList中,这个newList就是最终的去重结果
//三个集合类list、newList、set,能够保证顺序
@Test
void testRemove3()  {

  Set<String> set = new HashSet<>();
  List<String> newList = new  ArrayList<>();
  for (String str :list) {
    if(set.add(str)){ //重复的话返回false
      newList.add(str);
    }
  }
  System.out.println( "去重后的集合: " + newList);

}

去重后的集合: [kobe, james, curry, zimug]
  1. 使用newList.contains(T)方法,在向新的List添加数据的时候判断这个数据是否已经存在,如果存在就不添加,从而达到去重的效果
//优化 List、newList、set,能够保证顺序
@Test
void testRemove4() {

  List<String> newList = new  ArrayList<>();
  for (String cd:list) {
    if(!newList.contains(cd)){  //主动判断是否包含重复元素
      newList.add(cd);
    }
  }
  System.out.println( "去重后的集合: " + newList);

}

去重后的集合: [kobe, james, curry, zimug]

二、按照集合元素对象属性去重

  1. TreeSet实现Comparator接口,如果我们希望按照Player的name属性进行去重,就去在Comparator接口中比较name。下文中写了两种实现Comparator接口方法:
  • lambda表达式:(o1, o2) -> o1.getName().compareTo(o2.getName())
  • 方法引用:Comparator.comparing(Player::getName)
@Test
void testRemove5() {
  //Set playerSet = new TreeSet<>((o1, o2) -> o1.getName().compareTo(o2.getName()));
  Set<Player> playerSet = new TreeSet<>(Comparator.comparing(Player::getName));
  playerSet.addAll(playerList);

  /*new ArrayList<>(playerSet).forEach(player->{
    System.out.println(player.toString());
  });*/
  //将去重之后的结果打印出来
  new ArrayList<>(playerSet).forEach(System.out::println);
}

输出结果如下:三个zimug因为name重复,另外两个被去重。但是因为使用到了TreeSet,list中元素被重新排序。

Player{name='curry', age='30'}
Player{name='james', age='32'}
Player{name='kobe', age='10000'}
Player{name='zimug', age='27'}
  1. 首先用stream()把list集合转换成流,然后用collect及toCollection把流转换成集合,然后剩下的就和第一种方法一样了
@Test
void testRemove6() {
  List<Player> newList = playerList.stream().collect(Collectors
          .collectingAndThen(
                  Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(Player::getName))),
                  ArrayList::new));

  newList.forEach(System.out::println);
}

Player{name='curry', age='30'}
Player{name='james', age='32'}
Player{name='kobe', age='10000'}
Player{name='zimug', age='27'}
  1. 这种方法也是笔者建议大家使用的一种方法,咋一看好像代码量更大了,但实际上这种方法是应用比较简单的方法。
  • 首先我们定义一个谓词Predicate用来过滤,过滤的条件是distinctByKey。谓词返回ture元素保留,返回false元素被过滤掉。
  • 当然我们的需求是过滤掉重复元素。我们去重逻辑是通过map的putIfAbsent实现的。putIfAbsent方法添加键值对,如果map集合中没有该key对应的值,则直接添加,并返回null,如果已经存在对应的值,则依旧为原来的值。
  • 如果putIfAbsent返回null表示添加数据成功(不重复),如果putIfAbsent返回value(value==null :false),则满足了distinctByKey谓词的条件元素被过滤掉。
  • 这种方法虽然看上去代码量增大了,但是distinctByKey谓词方法只需要被定义一次,就可以无限复用。
@Test
void testRemove7() {
  List<Player> newList = new ArrayList<>();
  playerList.stream().filter(distinctByKey(p -> p.getName()))  //filter保留true的值
          .forEach(newList::add);

  newList.forEach(System.out::println);
}

static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
  Map<Object,Boolean> seen = new ConcurrentHashMap<>();
  //putIfAbsent方法添加键值对,如果map集合中没有该key对应的值,则直接添加,并返回null,如果已经存在对应的值,则依旧为原来的值。
  //如果返回null表示添加数据成功(不重复),不重复(null==null :TRUE)
  return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
}

  1. 上面的例子都是按某一个对象属性进行去重,如果我们想按照某几个元素进行去重,就需要对上面的三种方法进行改造。
    我只改造其中一个,另外几个改造的原理是一样的,就是把多个比较属性加起来,作为一个String属性进行比较。
@Test
void testRemove8() {
  Set<Player> playerSet = new TreeSet<>(Comparator.comparing(o -> (o.getName() + "" + o.getAge())));

  playerSet.addAll(playerList);

  new ArrayList<>(playerSet).forEach(System.out::println);
}

原文见:List集合对象去重及按属性去重的8种方法

你可能感兴趣的:(经验贴,list,java,数据结构)