Java8 Stream:2万字20个实例,玩转集合的筛选、归约、分组、聚合
Java如何对HashMap按值进行排序
HashMap的值是没有顺序的,它是按照key的HashCode来实现的。对于这个无序的HashMap我们要怎么来实现排序呢?(TreeMap类似)
Map<String, Long> map = new HashMap<String, Long>();
map.put("c", 33333L);
map.put("a", 11111L);
map.put("d", 44444L);
map.put("e", 55555L);
map.put("b", 22222L);
//将map.entrySet()转换成list
List<Map.Entry<String, Long>> list = new ArrayList<Map.Entry<String, Long>>(map.entrySet());
Collections.sort(list, new Comparator<Map.Entry<String, Long>>() {
//降序排序
@Override
public int compare(Entry<String, Long> o1, Entry<String, Long> o2) {
//return o1.getValue().compareTo(o2.getValue());
return o2.getValue().compareTo(o1.getValue());
}
});
for (Map.Entry<String, Long> mapping : list) {
System.out.println(mapping.getKey() + ":" + mapping.getValue());
}
运行结果
e:55555
d:44444
c:33333
b:22222
a:11111
关于 多条件 sorted
错误写法
下面代码实际上是,所有内容根据 A 排序
,然后 所有内容根据 B 排序
,最后所有内容根据 C 排序
,所以这样写 实际上只有最后的根据 C 排序是有效的
List<xxx> sortedResults = results.stream()
.sorted(Comparator.comparing(xxx::getA, Comparator.nullsLast(String::compareTo)))
.sorted(Comparator.comparing(xxx::getB, Comparator.nullsLast(String::compareTo)))
.sorted(Comparator.comparing(xxx::getC))
.collect(Collectors.toList());
多字段排序
Comparator.comparing
,此时会对第一字段进行排序
,如果第一字段相等,此时进入第二字段的排序Comparator.comparing().thenComparing()
,接着,第三个字段···第n个字段均可使用.thenComparing()来实现
value.stream()
.sorted(Comparator.comparing(TreeNodeAndIndex::getRow)
.thenComparing(TreeNodeAndIndex::getNodeVal)
字段排序的自定义排序
value.stream()
.sorted(Comparator.comparing(TreeNodeAndIndex::getRow)
.thenComparing(TreeNodeAndIndex::getNodeVal,(x,y)-> y-x))
对map<key,List> 中 list 进行排序
foreach
使用时,其得到的参数是形参,并非实参
;
在这样的一个写法下,TreeMap<Integet,List>
中的list并不会被真正的排序,因为操作的并不是其真正的内存地址
treeMap.forEach((key,value)-> value.stream()
.sorted(Comparator.comparing(TreeNodeAndIndex::getRow)
.thenComparing(TreeNodeAndIndex::getNodeVal,(x,y)-> y-x)));
要使得list真正的被排序
应该按下面的方式写:
treeMap.forEach((key,value)-> {
List<TreeNodeAndIndex> sortedList = value.stream()
.sorted(Comparator.comparing(TreeNodeAndIndex::getRow)
.thenComparingInt(TreeNodeAndIndex::getNodeVal))
.collect(Collectors.toList());
treeMap.put(key,sortedList);
});
将JSON对象中的某个字段进行分组
{
"systemid": "123",
"productid": "123",
"dataArray": [
{
"line_num": 2,
"subjectcodecv": 1,
"subjectname": "公司",
"subjectid": "1001",
"resource_members_id": "003",
"resource_members_name": "xx"
},
{
"line_num": 2,
"subjectcodecv": 2,
"subjectname": "产品",
"subjectid": "10",
"resource_members_id": "002",
"resource_members_name": "11xx"
},
{
"line_num": 2,
"subjectcodecv": 3,
"subjectname": "产品",
"subjectid": "10",
"resource_members_id": "001",
"resource_members_name": "22xx"
},
{
"line_num": 1,
"subjectcodecv": 1,
"subjectname": "公司",
"subjectid": "1001",
"resource_members_id": "005",
"resource_members_name": "xx"
},
{
"line_num": 1,
"subjectcodecv": 2,
"subjectname": "产品",
"subjectid": "10",
"resource_members_id": "004",
"resource_members_name": "11xx"
},
{
"line_num": 1,
"subjectcodecv": 3,
"subjectname": "产品",
"subjectid": "10",
"resource_members_id": "003",
"resource_members_name": "22xx"
}
]
}
将JSON字符串中的dataArray数组中的json对象按line_num字段进行分组,然后将每组中的json对象按照resource_members_id字段进行排序(后续还有对每组排序后的json对象进行业务操作,这里不做)
String jsonStr = "";
JSONObject originalJsonData = JSONObject.parseObject(jsonStr);
JSONArray dataArray = originalJsonData.getJSONArray("dataArray");
List<JSONObject> jsonObjectList = new ArrayList<>();
//将原始数据中的json数组中的json对象存到一个list集合中
for (Object o : dataArray) {
jsonObjectList.add((JSONObject) o);
}
//对集合中的json对象进行分组
//然后返回一个map集合,key代表组名,value代表该组中的数据
Map<String, List<JSONObject>> groupByLineNumData = jsonObjectList.stream()
.collect(Collectors.groupingBy(x -> x.getString("line_num")));
过滤
Java集合Stream 出自 Java8。
可谓是加量不加价,丰富了使用场景,还精简了代码。虽然牺牲了一点可读性,但总体来说是很好用的。以下总结其中 filter 的一些用法,整理相关的例子。
简单匹配过滤
public class TestFilter {
public static void main(String[] args) {
List<String> list = Arrays.asList("AAB","BBB","ACC");
System.out.println("过滤相等-------------------------------------------");
List<String> result1 = list.stream().filter(v -> v.equals("AAB")).collect(Collectors.toList());
result1.forEach(v-> System.out.println(v));
System.out.println("过滤 模糊相等(包含)-------------------------------------------");
List<String> result2 = list.stream().filter(v -> v.contains("B")).collect(Collectors.toList());
result2.forEach(v-> System.out.println(v));
System.out.println("多条件 过滤 与:&&; 或||-------------------------------------------");
List<String> result3 = list.stream().filter(v -> v.contains("A") && v.contains("C")).collect(Collectors.toList());
result3.forEach(v-> System.out.println(v));
}
}
对象匹配过滤
public class TestFilter1 {
public static void main(String[] args) {
List<User> userList = new ArrayList<>();
userList.add(new User("小明", 18));
userList.add(new User("王姐", 21));
System.out.println("过滤相等-------------------------------------------");
List<User> result1 = userList.stream().filter(v -> v.getName().equals("小明")).collect(Collectors.toList());
result1.forEach(v-> System.out.println(v));
}
}
自定义匹配过滤
看看filter接口的源码
public interface Stream<T> extends BaseStream<T, Stream<T>> {
/**
* Returns a stream consisting of the elements of this stream that match
* the given predicate.
*
* <p>This is an <a href="package-summary.html#StreamOps">intermediate
* operation</a>.
*
* @param predicate a <a href="package-summary.html#NonInterference">non-interfering</a>,
* <a href="package-summary.html#Statelessness">stateless</a>
* predicate to apply to each element to determine if it
* should be included
* @return the new stream
*/
Stream<T> filter(Predicate<? super T> predicate);
可以看到,filter接收的参数是Predicate,这是一个布尔接口,用于各种匹配,所以自定义filter实际上我们是你把Predicate的用法丰富起来
public class TestFilter1 {
public static void main(String[] args) {
List<User> userList = new ArrayList<>();
userList.add(new User("小明", 18));
userList.add(new User("王姐", 21));
System.out.println("过滤相等-------------------------------------------");
List<User> result2 = userList.stream().filter(new Predicate<User>() {
@Override
public boolean test(User user) {
if (user.getAge() > 18) {
return true;
}else{
return false;
}
}
}).collect(Collectors.toList());
result2.forEach(v-> System.out.println(v));
}
}
但是Predicate只能传入一个参数
从List集合中删除指定元素
public class ListRemove {
class User {
public String name;
public Integer age;
public String classNo;
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", classNo='" + classNo + '\'' +
'}';
}
}
public List<User> getUserList(int num) {
List<User> list = new ArrayList<>();
for (int i = 0; i < num; i++) {
User u = new User();
u.age = i + 10;
u.name = "a" + i;
u.classNo = String.valueOf(i);
list.add(u);
}
return list;
}
public static void main(String[] args) {
ListRemove listRemove = new ListRemove();
//数据生成
List<User> userList = listRemove.getUserList(5);
System.out.println("原数据:" + Arrays.toString(userList.toArray()));
//测试过滤掉 name = a2 或者 classNo = 4
//方法一:Iterator删除
List<User> iteratorList = new ArrayList<>(userList);
Iterator<User> iterator = iteratorList.iterator();
while (iterator.hasNext()) {
User next = iterator.next();
if ("a2".equals(next.name) || "4".equals(next.classNo)) {
iterator.remove();
}
}
System.out.println("Iterator删除:" + Arrays.toString(iteratorList.toArray()));
//方法二:removeIf删除
List<User> removeIfList = new ArrayList<>(userList);
removeIfList.removeIf(next -> "a2".equals(next.name) || "4".equals(next.classNo));
System.out.println("removeIf删除:" + Arrays.toString(removeIfList.toArray()));
//方法三:使用stream 注意:此方法测试未达到预期结果, 不适用
List<User> streamList = new ArrayList<>(userList);
streamList.stream().findFirst().map(e -> {
if ("a2".equals(e.name) || "4".equals(e.classNo)) {
streamList.remove(e);
}
return e;
});
System.out.println("stream删除:" + Arrays.toString(streamList.toArray()));
//方法四:使用stream+索引 注意:此方法测试未达到预期结果,不适用
List<User> streamIndexList = new ArrayList<>(userList);
IntStream.range(0, streamIndexList.size()).filter(i ->
"a2".equals(streamIndexList.get(i).name) || "4".equals(streamIndexList.get(i).classNo)).
boxed().findFirst().map(i -> streamIndexList.remove((int) i));
System.out.println("stream+index删除:" + Arrays.toString(streamIndexList.toArray()));
//方法三:使用filter
List<User> filterList = new ArrayList<>(userList);
filterList = filterList.stream().filter(e -> !"a2".equals(e.name) && !"4".equals(e.classNo)).collect(Collectors.toList());
System.out.println("filter删除:" + Arrays.toString(filterList.toArray()));
System.out.println("原数据:" + Arrays.toString(userList.toArray()));
}
}
去重
去除对象中的部分元素的重复
工作中遇到返回个一个list集合中,存在重复数据的问题,这里使用stream流的衍生功能,去除一个对象中的部分元素的重复如下:
ArrayList<ProductProcessDrawbackDto> collect = records1.stream().collect(Collectors.collectingAndThen(
Collectors.toCollection(() -> new TreeSet<>(
Comparator.comparing(
ProductProcessDrawbackDto::getId))), ArrayList::new));
其中records1是处理的对象,改对象的list集合,collect是处理后返回的结果
其中的ProductProcessDrawbackDto是处理的list中每一个对象,id是判断是否重复的条件(去除id相同的重复元素,只保留一条)
多个字段或者多个条件去重
ArrayList<PatentDto> collect1 = patentDtoList.stream().collect(Collectors.collectingAndThen(
Collectors.toCollection(() -> new TreeSet<>(
Comparator.comparing(p->p.getPatentName() + ";" + p.getLevel()))), ArrayList::new));
其原理利用了以下几点:
1.TreeSet里面不会有重复的元素,所以当把一个List放进TreeSet里面后,会自动去重
2.TreeSet去重也是有条件的,它依靠放入其中的元素的排序规则,所以放入其中的元素要有一个自定义的排序规则
也许有人说遍历,比较,这样自然也可以,但是当元素很多很多时,或者字段非常多时,那比较久非常麻烦了,因为涉及到foreach循环和字段的筛选.
编程不就是为了让一切更简单嘛,所以我们不用那种原始方法,我们用现成的简单方法,jdk给我们提供的TreeSet和Comparator搭配,特别适合做这种"比较"的事.
List<Person> persons = new ArrayList<Person>();
Person p1 = new Person("a",10,100);
Person p2 = new Person("a",10,100);
Person p3 = new Person("b",10,100);
persons.add(p1);
persons.add(p2);
persons.add(p3);
public static ArrayList<Person> removeDuplicated(List<Person> persons){
//1.创建一个带比较规则的set,这里使用匿名内部类创建了一个比较器
Set<Person> set = new TreeSet(new Comparator<Person>() {
public int compare(Person o1, Person o2) {
int a = o1.getName().compareTo(o2.getName());//比较name的自然顺序,0表示相同
int b = o1.getAge().compareTo(o2.getAge());//比较age的自然顺序,0表示相同
if(a==0 && b==0){
return 0;//如果name和age同时都相同,则返回0,0表示Person对象是相同的
}else{
return 1;
}
}
});
//2.将list放进Set中,自动去重
set.addAll(persons);
//3.将去重后的集合set再放进一个新的list中返回
return new ArrayList<Person>(set);
}
测试:
System.out.println(persons);
ArrayList<Person> newPersons = removeDuplicated(persons);
System.out.println(newPersons);
结果:
[Person{name='a', age=10, height=100}, Person{name='a', age=10, height=100}, Person{name='b', age=10, height=100}]
[Person{name='a', age=10, height=100}, Person{name='b', age=10, height=100}]
扩展:
以上是去除list中完全一样的元素,大家有没有发现,Person有三个字段,但我比较的时候却只使用了两个,为什么没使用第三个?
有意思的就在这里,因为你可以随心所欲去决定:只要对象的某些特征相同,我就可以判它们是相同对象
注意点: new Comparator<>()时一定要加泛型
对list
过滤掉电话相同的map
List<Map<String, Object>> mapList = new ArrayList<Map<String, Object>>();
Map<String, Object> mapStr1 = new HashMap<String, Object>();
mapStr1.put("name", "丽丽");
mapStr1.put("sex", "女");
mapStr1.put("age", 22);
mapStr1.put("tel", "110");
Map<String, Object> mapStr2 = new HashMap<String, Object>();
mapStr2.put("name", "丽丽");
mapStr2.put("sex", "女");
mapStr2.put("age", 23);
mapStr2.put("tel", "120");
Map<String, Object> mapStr3 = new HashMap<String, Object>();
mapStr3.put("name", "丽丽");
mapStr3.put("sex", "女");
mapStr3.put("age", 24);
mapStr3.put("tel", "110");
mapList.add(mapStr1);
mapList.add(mapStr2);
mapList.add(mapStr3);
ArrayList<Map<String, Object>> cs = mapList.stream().collect(Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(new Comparator<Map<String, Object>>() {
@Override //重写比较器
public int compare(Map<String, Object> o1, Map<String, Object> o2) {
if (o1.get("tel").equals(o2.get("tel"))) {
return 0;
}
return 1;
}
})), ArrayList::new));
//循环取出结果
cs.forEach(m -> {
System.out.println("======map========");
m.keySet().forEach(n -> System.out.println(n + "-->" + m.get(n)));
});
对list中对象T的指定属性的去重,比如唯一ID,tel,获取新的list:去除了重复出现的指定属性值的对象T
要求过滤掉电话相同的user
User u1 = new User(1, "小米1", "001号", "女", 21, "110");
User u2 = new User(2, "小米2", "002号", "男", 11, "120");
User u3 = new User(3, "小米3", "003号", "女", 21, "119");
User u4 = new User(4, "小米4", "004号", "女", 34, "110");
List<User> persons = Arrays.asList(u1, u2, u3, u4);
List<User> unique = persons.stream().collect(Collectors.collectingAndThen(
Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(User::getTel))), ArrayList::new));
//循环取出结果
unique.forEach(p -> System.out.println(p.toString()));
Stream实现List集合分页
private static List<User> getUserPageList(String searchKey,Integer pageIndex,Integer pageSize) {
List<User> userList = getUsers();
//先 filter 再分页(.skip(记录条数).limit(每页记录数))
List<User> users = userList.stream().filter(user -> user.getName().contains(searchKey)).skip((pageIndex-1)*pageSize).limit(pageSize).collect(Collectors.toList());
return users;
}
private static List<User> getUsers() {
List<User> userList = new ArrayList<>();
userList.add(new User(1,1,"北京1"));
....
return userList;
}
转int数组
如果转化的是存Integer类型数据的集合
,以下的所有intValue都可以用valueOf代替
但如果是存Long或Double等数据类型
的数据须用对应类型的intValue()方法
intValue()是java.lang.Number类的方法。Java中所有的数值类都继承它。也就是说 Integer,Double,Long等都有intValue()方法
//hashset转化为Integer数组
Set<Integer> set = new HashSet<>();
set.toArray(new Integer[set.size()]);
//ArrayList转化为Integer数组
List<Integer> list = new ArrayList<>();
list.toArray(new Integer[list.size()]);
不难发现: set 或 list 转化为 Integer 数组或者 int 数组的方式是一样的
//Integer[] 转换为 int[]
Integer[] arr = new Integer[10];
int[] intArr = Arrays.stream(arr).mapToInt(Integer::intValue).toArray();
//hashset用stream流转化为int数组
Set<Integer> set = new HashSet<>();
int[] arr = set.stream().mapToInt(Integer::intValue).toArray();
//ArrayList用stream流转化为int数组
List<Integer> list = new ArrayList<>();
int[] arr = list.stream().mapToInt(Integer::intValue).toArray();
多个list合并成一个
public static void main(String[] args) {
List<Integer> list1 = Arrays.asList(1, 2, 3);
List<Integer> list2 = Arrays.asList(4, 5, 6);
List<Integer> list3 = Arrays.asList(7, 8, 9);
List<Integer> mergedList = Stream.of(list1, list2, list3).flatMap(Collection::stream).collect(Collectors.toList());
System.out.println(mergedList);// [1, 2, 3, 4, 5, 6, 7, 8, 9]
}
List<String> fileIdList = ListIntegration(businessLicense,agencyDocument,otherDocuments,productsReport,threeYearsAchievements);
public List<String> ListIntegration(List<String> ...lists){
return Arrays.stream(lists).flatMap(List::stream).collect(Collectors.toList());
}
常用
1、一个集合根据主键msisdn去重,得到一个新的集合
List<Card> newList = list.stream().collect(Collectors.collectingAndThen(
Collectors.toCollection(() ->new TreeSet<>(Comparator.comparing(Card::getMsisdn))),
ArrayList::new));
2、获取一个集合中,某个对象指定属性的集合
List<Long> msisdnList = kqCardList.stream().map(KqCard::getMsisdn).collect(Collectors.toList());
3、将一个long类型的集合转换为String类型的集合
List<String> msisdnStrList = msisdnList.stream().map(String::valueOf).collect(Collectors.toList());
4、遍历一个集合中,对每个对象的指定属性进行赋值(这里我对每个对象赋值了三个参数,最后一个是使用UUID做主键)
List<CmiDataAmount> cmiDataAmountList = cmiDataAmountList.stream().map(
cmiDataAmount ->{
cmiDataAmount.setCreateTime(createTime);
cmiDataAmount.setCurrentMonth(currentMonth);
cmiDataAmount.setId(UUID.randomUUID().toString());
}).collect(Collectors.toList());
5、将String类型的字符串(比如用“,”分割)转换为集合
List<String> msisdnsList = Arrays.asList(msisdns.split(",")).stream().
map(s -> String.format(s.trim())).collect(Collectors.toList());
6、遍历集合,获取类型为A的元素,并放入新集合
List<CmiDataAmount> list = cmiDataAmountList.stream().
filter(item -> item.getType().equals("A")).
collect(Collectors.toList());
7、取list集合中的两个元素,转换为一个map 参照 2
Map<Integer, String> collect = list.stream().
collect(Collectors.toMap(Student::getAge, Student::getName));
8、如何将一个元素(对象、属性等)优雅的转换为一个集合
List<K> authcChannels = Stream.of(K).collect(Collectors.toList());