有时候我们会碰到这样的需求,将数据分组以后并且需要按照指定的字段排序,
我使用jdk8,实现了两种方式。
Map> map=new HashMap<>();
Map> map2=new HashMap<>();
List list = Arrays.asList(
new Book(1,"三国",12)
,new Book(2,"水浒",1)
,new Book(3,"Java++",2)
,new Book(1,"C++",12)
,new Book(2,"java 入门到放弃",3)
,new Book(3,"java入门到走火入魔",6)
,new Book(1,"helloworld",23)
);
分组不排序
list.forEach(e->{
//普通分组
map2.computeIfAbsent(e.getId(),s -> new ArrayList<>()).add(e);
});
分组并且排序
list.forEach(book->{
map.computeIfAbsent(book.getId(),s->new TreeSet<>(Comparator.comparing(Book::getNumber).reversed())).add(book);
});
分组不排序
//stream方式分组
Map> collect = list.stream().collect(Collectors.groupingBy(Book::getId));
分组排序
//stream方式分组并且排序 默认方式为正序排列
Map> streamMap = list.stream().sorted(Comparator.comparingInt(Book::getNumber).reversed()).collect(Collectors.groupingBy(Book::getId));
Compartor的比较方式默认是按照正序的方式,如果需要倒叙的话使用reversed()方法
存在的问题
正常方式2的排序分组的结果和方式1排序分组的结果是一样的,测试结果如下:
方式1的结果:
{1=[Book [bookId=1, name=helloworld, number=23], Book [bookId=1, name=三国, number=12]],
2=[Book [bookId=2, name=java 入门到放弃, number=3], Book [bookId=2, name=水浒, number=1]],
3=[Book [bookId=3, name=java入门到走火入魔, number=6], Book [bookId=3, name=Java++,number=2]]}
方式2的结果
{1=[Book [bookId=1, name=helloworld, number=23], Book [bookId=1, name=三国, number=12], Book [bookId=1, name=C++, number=12]],
2=[Book [bookId=2, name=java 入门到放弃, number=3], Book [bookId=2, name=水浒, number=1]],
3=[Book [bookId=3, name=java入门到走火入魔, number=6], Book [bookId=3, name=Java++, number=2]]}
我们发现丢失了一个数据,这个数据丢失问题是由于比较属性number的数量相等。
把其中一个12改成13,结果如下:方式1的结果如下:
{1=[Book [bookId=1, name=helloworld, number=23], Book [bookId=1, name=C++, number=13], Book [bookId=1, name=三国, number=12]], 2=[Book [bookId=2, name=java 入门到放弃, number=3], Book [bookId=2, name=水浒, number=1]], 3=[Book [bookId=3, name=java入门到走火入魔, number=6], Book [bookId=3, name=Java++, number=2]]}
treeset数据为什么会丢失呢?
treeSet的底层是treemap它的添加方法对应treemapd的添加方法,源码如下:
/**
* Associates the specified value with the specified key in this map.
* If the map previously contained a mapping for the key, the old
* value is replaced.
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
*
* @return the previous value associated with {@code key}, or
* {@code null} if there was no mapping for {@code key}.
* (A {@code null} return can also indicate that the map
* previously associated {@code null} with {@code key}.)
* @throws ClassCastException if the specified key cannot be compared
* with the keys currently in the map
* @throws NullPointerException if the specified key is null
* and this map uses natural ordering, or its comparator
* does not permit null keys
*/
public V put(K key, V value) {
Entry t = root;
if (t == null) {
compare(key, key); // type (and possibly null) check
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
int cmp;
Entry parent;
// split comparator and comparable paths
Comparator super K> cpr = comparator;
if (cpr != null) {
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
else {
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable super K> k = (Comparable super K>) key;
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
Entry e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
发现当调用TreeSet的add()方法时,在TreeSet的内部会间接调用compareTo()方法、然后和TreeSet中已经存在的其他元素一一进行比较,在比较的过程中完成“判断是否重复”以及“排序”的功能:当在某次比较的过程中发现compareTo()返回0,就会认为待加入的元素已经存在于TreeSet中,返回-1或1的话就会根据TreeSet默认的比较器进行排序。
解决方式如下:treeset使用下面的方式进行比较等于0的时候不返回,
list.forEach(book->{
map.computeIfAbsent(book.getId(),s->new TreeSet<>((e1,e2)->{
int flag = e2.getNumber() - e1.getNumber();
if(flag>0||flag==0){
return 1;
}else { return -1;}
})).add(book);
})
当然如果相等时你还可以指定再次使用另一个字段进行排序。
这样的方式就和使用stream得到的结果一模一样。