👈🏻 Select language
为方便初学 Java8/C# 集合操作的人,特意写下这篇文章.
前期准备
单集合
分类筛选
- 计数(Count)
1
2
3
Date time1 = convertLocalDateToTimeZone(LocalDate.of(1990, 1, 1));
//0
Long count1 = list1.stream().filter(o -> o.getBirthday().equals(time1)).count();
1
2
3
4
5
6
int count1 = list1.Where(o => o.Birthday.Equals(new DateTime(1990, 1, 1)) && o.Sex == Sex.Male).Count();
long count2 = list1.Where(o => o.Birthday.Equals(new DateTime(1990, 1, 1)) && o.Sex == Sex.Male).LongCount();
/*
0
0
*/
- 分组(GroupBy)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Map<Sex, List<Person>> group1 = list1.stream().collect(Collectors.groupingBy(Person::getSex));
Iterator it = group1.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<Sex, List<Person>> groupByItem = (Map.Entry) it.next();
Sex sex = groupByItem.getKey();
out.println(sex);
groupByItem.getValue().forEach(person -> {
out.println(new Gson().toJson(person));
});
}
/*
输出结果:
Male
{"height":170,"weight":50,"identifier":"2","address":"北京","birthday":"Feb 1, 1982 12:00:00 AM","hobbies":["吃飯","看電影"],"sex":"Male"}
Female
{"height":165,"weight":50,"identifier":"1","address":"北京","birthday":"Jan 1, 1981 12:00:00 AM","hobbies":["吃飯","逛街"],"sex":"Female"}
X
{"height":170,"weight":50,"identifier":"3","address":"北京","birthday":"Mar 1, 1983 12:00:00 AM","hobbies":["吃飯","上網"],"sex":"X"}
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
var group1 = list1.GroupBy(o => o.Sex);
//当我们使用 GroupBy() 扩展方法时,使用了延迟执行。 这意味着,当你遍历集合的时候,下一个要出现的项目可能会或者可能不会被加载。 这是一个很大的性能改进,但它会引起有趣的副作用。
list1.RemoveAll(o => o.Sex == Sex.X);//定义 groupby 集合后对原集合进行修改,会发现group1里面已经没了 Sex=X的分组
foreach (var groupByItem in group1)
{
Sex sex = groupByItem.Key;
System.Console.WriteLine(sex);
foreach (Person person in groupByItem)
{
System.Console.WriteLine(JsonConvert.SerializeObject(person));
}
}
/*
输出结果:
{"Height":165,"Weight":50,"Birthday":"1981-01-01T00:00:00","Hobbies":["吃飯","逛街"],"Identifier":"1","Address":"北京","Sex":2}
Male
{"Height":170,"Weight":50,"Birthday":"1982-02-01T00:00:00","Hobbies":["吃飯","看電影"],"Identifier":"2","Address":"北京","Sex":1}
Female
{"Height":165,"Weight":50,"Birthday":"1981-01-01T00:00:00","Hobbies":["吃飯","逛街"],"Identifier":"1","Address":"北京","Sex":2}
Male
{"Height":170,"Weight":50,"Birthday":"1982-02-01T00:00:00","Hobbies":["吃飯","看電影"],"Identifier":"2","Address":"北京","Sex":1}
*/
//该 ToLookup() 方法创建一个类似 字典(Dictionary ) 的列表List, 但是它是一个新的 .NET Collection 叫做 lookup。 Lookup,不像Dictionary, 是不可改变的。 这意味着一旦你创建一个lookup, 你不能添加或删除元素。
var group2 = list1.ToLookup(o => o.Sex);
foreach (var groupByItem in group2)
{
Sex sex = groupByItem.Key;
foreach (Person person in groupByItem)
{
System.Console.WriteLine(sex);
System.Console.WriteLine(JsonConvert.SerializeObject(person));
}
}
/*
输出结果:
{"Height":165,"Weight":50,"Birthday":"1981-01-01T00:00:00","Hobbies":["吃飯","逛街"],"Identifier":"1","Address":"北京","Sex":3}
{"Height":170,"Weight":50,"Birthday":"1982-02-01T00:00:00","Hobbies":["吃飯","看電影"],"Identifier":"2","Address":"北京","Sex":3}
*/
与此对比,stream没有RemoveAll的操作
- 匹配的第一项(findFirst/First,FirstOrDefault)
1
2
3
4
5
Person after90 = list1.stream()
.filter(o -> o.getBirthday().after(convertLocalDateToTimeZone(LocalDate.of(1990, 1, 1))))
.findFirst()
.orElse(null);
// null
1
2
3
4
5
var after90 = list1.Where(o => o.Birthday >= new DateTime(1990, 1, 1)).First();//如果结果为空,将会导致异常,所以一般极少使用该方法
//An unhandled exception of type 'System.InvalidOperationException' occurred in System.Linq.dll: 'Sequence contains no elements'
after90 = list1.Where(o => o.Birthday >= new DateTime(1990, 1, 1)).FirstOrDefault();
var after00 = list1.Where(o => o.Birthday >= new DateTime(2000, 1, 1)).FirstOrDefault();
- 遍历(ForEach)
1
2
3
4
5
6
7
8
9
10
11
12
13
list1.stream().forEach(o -> {
//在ForEach當中可對集合進行操作
o.setSex(Sex.X);
});
list1.forEach(o -> {
out.println(new Gson().toJson(o));
});
/*
{"height":165,"weight":50,"identifier":"1","address":"北京","birthday":"Jan 1, 1981 12:00:00 AM","hobbies":["吃飯","逛街"],"sex":"X"}
{"height":170,"weight":50,"identifier":"2","address":"北京","birthday":"Feb 1, 1982 12:00:00 AM","hobbies":["吃飯","看電影"],"sex":"X"}
{"height":170,"weight":50,"identifier":"3","address":"北京","birthday":"Mar 1, 1983 12:00:00 AM","hobbies":["吃飯","上網"],"sex":"X"}
*/
1
2
3
4
5
6
7
8
9
10
list1.ForEach(item =>
{
//在ForEach當中可對集合進行操作
item.Sex = Sex.X;
});
list1.ForEach(item =>
{
System.Console.WriteLine(JsonConvert.SerializeObject(item));
});
- 极值Max/Min
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//IntStream的max方法返回的是OptionalInt,要先判断有没有值再读取值.isPresent=false 时直接getAsInt会报错.mapToLong,mapToDouble同理
OptionalInt maxHeightOption = list1.stream().mapToInt(Person::getHeight).max();
//字符串拼接、数值的 sum、min、max、average 都是特殊的 reduce。
//当集合为长度0的集合时会返回起始值Integer.MIN_VALUE,起始值也不能乱传,个中缘由我暂不清楚
int maxHeight = list1.stream().mapToInt(Person::getHeight).reduce(Integer.MIN_VALUE, Integer::max);
out.println(maxHeight);
if (maxHeightOption.isPresent()) {
maxHeight = maxHeightOption.getAsInt();
out.println(maxHeight);
}
//mapToInt参数的2种写法都一样,我比较喜欢以下写法,但是 idea 会报 warning
OptionalInt minWeightOption = list1.stream().mapToInt(o -> o.getHeight()).min();
int minWeight = list1.stream().mapToInt(o -> o.getHeight()).reduce(Integer.MAX_VALUE, Integer::min);
1
2
3
4
int maxHeight = list1.Select(o => o.Height).Max();
//同 list1.Max(o => o.Height);
int minWeight = list1.Min(o => o.Weight);
- 跳过(skip/Skip),截取(limit/Take)
1
2
3
//skip和 limit参数都是long, 这个要注意
list1.stream().skip(1L).limit(2L);
排序
- 去重复(Distinct)
1
list1.stream().map(Person::getIdentifier).distinct();
1
list1.Select(o=>o.Identifier).Distinct();
1
list1.Skip(1).Take(2);
- 升序(sort/OrderBy)
1
2
3
4
5
6
out.println("------------------------------------|升序|------------------------------------");
list1 = list1.stream().sorted(Comparator.comparing(Person::getBirthday)).collect(Collectors.toList());
out.println(new Gson().toJson(list1));
list1 = list1.stream().sorted((left, right) -> left.getBirthday().compareTo(right.getBirthday())).collect(Collectors.toList());
out.println(new Gson().toJson(list1));
1
2
//升序
list1 = list1.OrderBy(o => o.Birthday).ToList();
- 降序(sort/OrderByDescending)
1
2
3
4
5
6
7
out.println("------------------------------------|降序|------------------------------------");
list1 = list1.stream().sorted(Comparator.comparing(Person::getBirthday).reversed()).collect(Collectors.toList());
out.println(new Gson().toJson(list1));
list1 = list1.stream().sorted((left, right) -> right.getBirthday().compareTo(left.getBirthday())).collect(Collectors.toList());
out.println(new Gson().toJson(list1));
1
2
3
//降序
list1 = list1.OrderByDescending(o => o.Birthday).ToList();
多集合
- 交集 list1 ∩ list2
1
2
3
out.println("------------------------------------|交集 list1 ∩ list2|------------------------------------");
list1.stream().filter(o -> list2.contains(o)).collect(Collectors.toList());
1
2
3
4
5
//连接,下面表示把 list1和 list2当中相同身份证号的取出来,生成一个新的集合
//实际上, join 有另外的用法,类似 sqlserver 里面的多表连接,将不同数据源结合到一起,生成新的数据结构
var intersect = list1.Join(list2, o => o.Identifier, o => o.Identifier, (a, b) => a).ToList();
//交集 list1 ∩ list2
intersect = list1.Intersect(list2).ToList();
- 并集list1 ∪ list2
1
2
3
out.println("------------------------------------|并集list1 ∪ list2 |------------------------------------");
list1.addAll(list2);
1
2
3
//并集list1 ∪ list2
var union = list1.Union(list2).ToList();
- 差集list1 - list2
1
2
3
out.println("------------------------------------|差集list1 - list2|------------------------------------");
list1.stream().filter(item1 -> !list2.contains(item1)).collect(Collectors.toList());
1
2
//差集list1 - list2
var except = list1.Except(list2).ToList();
数据结构转换
1
2
3
4
5
6
7
out.println("------------------------------------|数据结构转换|------------------------------------");
List<Person> list3 = list1.stream().filter(o -> true).collect(Collectors.toList());
ArrayList<Person> list4 = list1.stream().filter(o -> true).collect(Collectors.toCollection(ArrayList::new));
Set<Person> list5 = list1.stream().filter(o -> true).collect(Collectors.toSet());
Object[] list6 = list1.stream().toArray();
Person[] list7 = list1.stream().toArray(Person[]::new);
1
2
3
4
5
//数据结构转换
list1.ToArray();
//注意如果 key 重复,ToDictionary会导致出错
list1.ToDictionary(o => o.Identifier, o => o);
list1.ToHashSet();
- Preparation
- Define Entity
- Define Collection
- Single Collection
- Filtering and Classification
- Count
- GroupBy
- First Matching Item (findFirst/First, FirstOrDefault)
- ForEach
- Max/Min
- Skip, Take (limit)
- Sorting
- Distinct
- Ascending (sort/OrderBy)
- Descending (sort/OrderByDescending)
- Filtering and Classification
- Multiple Collections
- Intersection list1 ∩ list2
- Union list1 ∪ list2
- Difference list1 - list2
- Data Structure Conversion
This article is written specifically to help beginners learning Java8/C# collection operations.
Preparation
Single Collection
Filtering and Classification
- Count
1
2
3
Date time1 = convertLocalDateToTimeZone(LocalDate.of(1990, 1, 1));
//0
Long count1 = list1.stream().filter(o -> o.getBirthday().equals(time1)).count();
1
2
3
4
5
6
int count1 = list1.Where(o => o.Birthday.Equals(new DateTime(1990, 1, 1)) && o.Sex == Sex.Male).Count();
long count2 = list1.Where(o => o.Birthday.Equals(new DateTime(1990, 1, 1)) && o.Sex == Sex.Male).LongCount();
/*
0
0
*/
- GroupBy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Map<Sex, List<Person>> group1 = list1.stream().collect(Collectors.groupingBy(Person::getSex));
Iterator it = group1.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<Sex, List<Person>> groupByItem = (Map.Entry) it.next();
Sex sex = groupByItem.getKey();
out.println(sex);
groupByItem.getValue().forEach(person -> {
out.println(new Gson().toJson(person));
});
}
/*
Output:
Male
{"height":170,"weight":50,"identifier":"2","address":"北京","birthday":"Feb 1, 1982 12:00:00 AM","hobbies":["吃飯","看電影"],"sex":"Male"}
Female
{"height":165,"weight":50,"identifier":"1","address":"北京","birthday":"Jan 1, 1981 12:00:00 AM","hobbies":["吃飯","逛街"],"sex":"Female"}
X
{"height":170,"weight":50,"identifier":"3","address":"北京","birthday":"Mar 1, 1983 12:00:00 AM","hobbies":["吃飯","上網"],"sex":"X"}
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
var group1 = list1.GroupBy(o => o.Sex);
//When we use the GroupBy() extension method, deferred execution is used. This means that when you iterate through the collection, the next item to appear may or may not be loaded. This is a big performance improvement, but it can cause interesting side effects.
list1.RemoveAll(o => o.Sex == Sex.X);//After defining the groupby collection, modifying the original collection will find that the Sex=X group is already gone from group1
foreach (var groupByItem in group1)
{
Sex sex = groupByItem.Key;
System.Console.WriteLine(sex);
foreach (Person person in groupByItem)
{
System.Console.WriteLine(JsonConvert.SerializeObject(person));
}
}
/*
Output:
{"Height":165,"Weight":50,"Birthday":"1981-01-01T00:00:00","Hobbies":["吃飯","逛街"],"Identifier":"1","Address":"北京","Sex":2}
Male
{"Height":170,"Weight":50,"Birthday":"1982-02-01T00:00:00","Hobbies":["吃飯","看電影"],"Identifier":"2","Address":"北京","Sex":1}
Female
{"Height":165,"Weight":50,"Birthday":"1981-01-01T00:00:00","Hobbies":["吃飯","逛街"],"Identifier":"1","Address":"北京","Sex":2}
Male
{"Height":170,"Weight":50,"Birthday":"1982-02-01T00:00:00","Hobbies":["吃飯","看電影"],"Identifier":"2","Address":"北京","Sex":1}
*/
//The ToLookup() method creates a Dictionary-like List, but it's a new .NET Collection called lookup. Lookup, unlike Dictionary, is immutable. This means once you create a lookup, you cannot add or remove elements.
var group2 = list1.ToLookup(o => o.Sex);
foreach (var groupByItem in group2)
{
Sex sex = groupByItem.Key;
foreach (Person person in groupByItem)
{
System.Console.WriteLine(sex);
System.Console.WriteLine(JsonConvert.SerializeObject(person));
}
}
/*
Output:
{"Height":165,"Weight":50,"Birthday":"1981-01-01T00:00:00","Hobbies":["吃飯","逛街"],"Identifier":"1","Address":"北京","Sex":3}
{"Height":170,"Weight":50,"Birthday":"1982-02-01T00:00:00","Hobbies":["吃飯","看電影"],"Identifier":"2","Address":"北京","Sex":3}
*/
In contrast, stream doesn’t have a RemoveAll operation
- First Matching Item (findFirst/First, FirstOrDefault)
1
2
3
4
5
Person after90 = list1.stream()
.filter(o -> o.getBirthday().after(convertLocalDateToTimeZone(LocalDate.of(1990, 1, 1))))
.findFirst()
.orElse(null);
// null
1
2
3
4
5
var after90 = list1.Where(o => o.Birthday >= new DateTime(1990, 1, 1)).First();//If the result is empty, it will cause an exception, so this method is rarely used
//An unhandled exception of type 'System.InvalidOperationException' occurred in System.Linq.dll: 'Sequence contains no elements'
after90 = list1.Where(o => o.Birthday >= new DateTime(1990, 1, 1)).FirstOrDefault();
var after00 = list1.Where(o => o.Birthday >= new DateTime(2000, 1, 1)).FirstOrDefault();
- ForEach
1
2
3
4
5
6
7
8
9
10
11
12
13
list1.stream().forEach(o -> {
//Can operate on the collection within ForEach
o.setSex(Sex.X);
});
list1.forEach(o -> {
out.println(new Gson().toJson(o));
});
/*
{"height":165,"weight":50,"identifier":"1","address":"北京","birthday":"Jan 1, 1981 12:00:00 AM","hobbies":["吃飯","逛街"],"sex":"X"}
{"height":170,"weight":50,"identifier":"2","address":"北京","birthday":"Feb 1, 1982 12:00:00 AM","hobbies":["吃飯","看電影"],"sex":"X"}
{"height":170,"weight":50,"identifier":"3","address":"北京","birthday":"Mar 1, 1983 12:00:00 AM","hobbies":["吃飯","上網"],"sex":"X"}
*/
1
2
3
4
5
6
7
8
9
10
list1.ForEach(item =>
{
//Can operate on the collection within ForEach
item.Sex = Sex.X;
});
list1.ForEach(item =>
{
System.Console.WriteLine(JsonConvert.SerializeObject(item));
});
- Max/Min
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//IntStream's max method returns OptionalInt, must check if there's a value before reading. getAsInt will error when isPresent=false. mapToLong, mapToDouble are similar
OptionalInt maxHeightOption = list1.stream().mapToInt(Person::getHeight).max();
//String concatenation, sum, min, max, average of numbers are all special reduces.
//When the collection is a length 0 collection, it returns the starting value Integer.MIN_VALUE. The starting value cannot be passed randomly, I'm not clear on the reason yet
int maxHeight = list1.stream().mapToInt(Person::getHeight).reduce(Integer.MIN_VALUE, Integer::max);
out.println(maxHeight);
if (maxHeightOption.isPresent()) {
maxHeight = maxHeightOption.getAsInt();
out.println(maxHeight);
}
//Both ways of writing mapToInt parameters are the same. I prefer the following, but idea will report a warning
OptionalInt minWeightOption = list1.stream().mapToInt(o -> o.getHeight()).min();
int minWeight = list1.stream().mapToInt(o -> o.getHeight()).reduce(Integer.MAX_VALUE, Integer::min);
1
2
3
4
int maxHeight = list1.Select(o => o.Height).Max();
//Same as list1.Max(o => o.Height);
int minWeight = list1.Min(o => o.Weight);
- Skip, Take (limit)
1
2
3
//Both skip and limit parameters are long, note this
list1.stream().skip(1L).limit(2L);
Sorting
- Distinct
1
list1.stream().map(Person::getIdentifier).distinct();
1
list1.Select(o=>o.Identifier).Distinct();
1
list1.Skip(1).Take(2);
- Ascending (sort/OrderBy)
1
2
3
4
5
6
out.println("------------------------------------|Ascending|------------------------------------");
list1 = list1.stream().sorted(Comparator.comparing(Person::getBirthday)).collect(Collectors.toList());
out.println(new Gson().toJson(list1));
list1 = list1.stream().sorted((left, right) -> left.getBirthday().compareTo(right.getBirthday())).collect(Collectors.toList());
out.println(new Gson().toJson(list1));
1
2
//Ascending
list1 = list1.OrderBy(o => o.Birthday).ToList();
- Descending (sort/OrderByDescending)
1
2
3
4
5
6
7
out.println("------------------------------------|Descending|------------------------------------");
list1 = list1.stream().sorted(Comparator.comparing(Person::getBirthday).reversed()).collect(Collectors.toList());
out.println(new Gson().toJson(list1));
list1 = list1.stream().sorted((left, right) -> right.getBirthday().compareTo(left.getBirthday())).collect(Collectors.toList());
out.println(new Gson().toJson(list1));
1
2
3
//Descending
list1 = list1.OrderByDescending(o => o.Birthday).ToList();
Multiple Collections
- Intersection list1 ∩ list2
1
2
3
out.println("------------------------------------|Intersection list1 ∩ list2|------------------------------------");
list1.stream().filter(o -> list2.contains(o)).collect(Collectors.toList());
1
2
3
4
5
//Join, below means take out items with the same ID number from list1 and list2, generate a new collection
//Actually, join has other uses, similar to multi-table joins in sqlserver, combining different data sources together to generate new data structures
var intersect = list1.Join(list2, o => o.Identifier, o => o.Identifier, (a, b) => a).ToList();
//Intersection list1 ∩ list2
intersect = list1.Intersect(list2).ToList();
- Union list1 ∪ list2
1
2
3
out.println("------------------------------------|Union list1 ∪ list2 |------------------------------------");
list1.addAll(list2);
1
2
3
//Union list1 ∪ list2
var union = list1.Union(list2).ToList();
- Difference list1 - list2
1
2
3
out.println("------------------------------------|Difference list1 - list2|------------------------------------");
list1.stream().filter(item1 -> !list2.contains(item1)).collect(Collectors.toList());
1
2
//Difference list1 - list2
var except = list1.Except(list2).ToList();
Data Structure Conversion
1
2
3
4
5
6
7
out.println("------------------------------------|Data Structure Conversion|------------------------------------");
List<Person> list3 = list1.stream().filter(o -> true).collect(Collectors.toList());
ArrayList<Person> list4 = list1.stream().filter(o -> true).collect(Collectors.toCollection(ArrayList::new));
Set<Person> list5 = list1.stream().filter(o -> true).collect(Collectors.toSet());
Object[] list6 = list1.stream().toArray();
Person[] list7 = list1.stream().toArray(Person[]::new);
1
2
3
4
5
//Data Structure Conversion
list1.ToArray();
//Note if key is duplicate, ToDictionary will cause an error
list1.ToDictionary(o => o.Identifier, o => o);
list1.ToHashSet();
- 準備
- エンティティの定義
- コレクションの定義
- 単一コレクション
- 複数コレクション
- 積集合 list1 ∩ list2
- 和集合list1 ∪ list2
- 差集合list1 - list2
- データ構造の変換
Java8/C#コレクション操作を学び始める人のために、この記事を書きました。
準備
単一コレクション
分類フィルタリング
- カウント(Count)
1
2
3
Date time1 = convertLocalDateToTimeZone(LocalDate.of(1990, 1, 1));
//0
Long count1 = list1.stream().filter(o -> o.getBirthday().equals(time1)).count();
1
2
3
4
5
6
int count1 = list1.Where(o => o.Birthday.Equals(new DateTime(1990, 1, 1)) && o.Sex == Sex.Male).Count();
long count2 = list1.Where(o => o.Birthday.Equals(new DateTime(1990, 1, 1)) && o.Sex == Sex.Male).LongCount();
/*
0
0
*/
- グループ化(GroupBy)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Map<Sex, List<Person>> group1 = list1.stream().collect(Collectors.groupingBy(Person::getSex));
Iterator it = group1.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<Sex, List<Person>> groupByItem = (Map.Entry) it.next();
Sex sex = groupByItem.getKey();
out.println(sex);
groupByItem.getValue().forEach(person -> {
out.println(new Gson().toJson(person));
});
}
/*
出力結果:
Male
{"height":170,"weight":50,"identifier":"2","address":"北京","birthday":"Feb 1, 1982 12:00:00 AM","hobbies":["吃飯","看電影"],"sex":"Male"}
Female
{"height":165,"weight":50,"identifier":"1","address":"北京","birthday":"Jan 1, 1981 12:00:00 AM","hobbies":["吃飯","逛街"],"sex":"Female"}
X
{"height":170,"weight":50,"identifier":"3","address":"北京","birthday":"Mar 1, 1983 12:00:00 AM","hobbies":["吃飯","上網"],"sex":"X"}
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
var group1 = list1.GroupBy(o => o.Sex);
//GroupBy()拡張メソッドを使用する場合、遅延実行が使用されます。これは、コレクションを反復処理するとき、次に出現する項目がロードされる場合とされない場合があることを意味します。これは大きなパフォーマンス改善ですが、興味深い副作用を引き起こす可能性があります。
list1.RemoveAll(o => o.Sex == Sex.X);//groupbyコレクションを定義した後、元のコレクションを変更すると、group1にSex=Xのグループがすでにないことがわかります
foreach (var groupByItem in group1)
{
Sex sex = groupByItem.Key;
System.Console.WriteLine(sex);
foreach (Person person in groupByItem)
{
System.Console.WriteLine(JsonConvert.SerializeObject(person));
}
}
/*
出力結果:
{"Height":165,"Weight":50,"Birthday":"1981-01-01T00:00:00","Hobbies":["吃飯","逛街"],"Identifier":"1","Address":"北京","Sex":2}
Male
{"Height":170,"Weight":50,"Birthday":"1982-02-01T00:00:00","Hobbies":["吃飯","看電影"],"Identifier":"2","Address":"北京","Sex":1}
Female
{"Height":165,"Weight":50,"Birthday":"1981-01-01T00:00:00","Hobbies":["吃飯","逛街"],"Identifier":"1","Address":"北京","Sex":2}
Male
{"Height":170,"Weight":50,"Birthday":"1982-02-01T00:00:00","Hobbies":["吃飯","看電影"],"Identifier":"2","Address":"北京","Sex":1}
*/
//ToLookup()メソッドは、DictionaryのようなListを作成しますが、lookupという新しい.NET Collectionです。Lookupは、Dictionaryとは異なり、不変です。これは、lookupを作成すると、要素を追加または削除できないことを意味します。
var group2 = list1.ToLookup(o => o.Sex);
foreach (var groupByItem in group2)
{
Sex sex = groupByItem.Key;
foreach (Person person in groupByItem)
{
System.Console.WriteLine(sex);
System.Console.WriteLine(JsonConvert.SerializeObject(person));
}
}
/*
出力結果:
{"Height":165,"Weight":50,"Birthday":"1981-01-01T00:00:00","Hobbies":["吃飯","逛街"],"Identifier":"1","Address":"北京","Sex":3}
{"Height":170,"Weight":50,"Birthday":"1982-02-01T00:00:00","Hobbies":["吃飯","看電影"],"Identifier":"2","Address":"北京","Sex":3}
*/
これと対照的に、streamにはRemoveAll操作がありません
- 一致する最初の項目(findFirst/First,FirstOrDefault)
1
2
3
4
5
Person after90 = list1.stream()
.filter(o -> o.getBirthday().after(convertLocalDateToTimeZone(LocalDate.of(1990, 1, 1))))
.findFirst()
.orElse(null);
// null
1
2
3
4
5
var after90 = list1.Where(o => o.Birthday >= new DateTime(1990, 1, 1)).First();//結果が空の場合、例外が発生するため、このメソッドは通常ほとんど使用されません
//An unhandled exception of type 'System.InvalidOperationException' occurred in System.Linq.dll: 'Sequence contains no elements'
after90 = list1.Where(o => o.Birthday >= new DateTime(1990, 1, 1)).FirstOrDefault();
var after00 = list1.Where(o => o.Birthday >= new DateTime(2000, 1, 1)).FirstOrDefault();
- 反復処理(ForEach)
1
2
3
4
5
6
7
8
9
10
11
12
13
list1.stream().forEach(o -> {
//ForEach内でコレクションを操作できます
o.setSex(Sex.X);
});
list1.forEach(o -> {
out.println(new Gson().toJson(o));
});
/*
{"height":165,"weight":50,"identifier":"1","address":"北京","birthday":"Jan 1, 1981 12:00:00 AM","hobbies":["吃飯","逛街"],"sex":"X"}
{"height":170,"weight":50,"identifier":"2","address":"北京","birthday":"Feb 1, 1982 12:00:00 AM","hobbies":["吃飯","看電影"],"sex":"X"}
{"height":170,"weight":50,"identifier":"3","address":"北京","birthday":"Mar 1, 1983 12:00:00 AM","hobbies":["吃飯","上網"],"sex":"X"}
*/
1
2
3
4
5
6
7
8
9
10
list1.ForEach(item =>
{
//ForEach内でコレクションを操作できます
item.Sex = Sex.X;
});
list1.ForEach(item =>
{
System.Console.WriteLine(JsonConvert.SerializeObject(item));
});
- 極値Max/Min
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//IntStreamのmaxメソッドはOptionalIntを返します。値を読み取る前に値があるかどうかを確認する必要があります。isPresent=falseの場合、直接getAsIntするとエラーになります。mapToLong、mapToDoubleも同様です
OptionalInt maxHeightOption = list1.stream().mapToInt(Person::getHeight).max();
//文字列の連結、数値のsum、min、max、averageはすべて特殊なreduceです。
//コレクションが長さ0のコレクションの場合、開始値Integer.MIN_VALUEを返します。開始値も乱用できません。理由はまだ明確ではありません
int maxHeight = list1.stream().mapToInt(Person::getHeight).reduce(Integer.MIN_VALUE, Integer::max);
out.println(maxHeight);
if (maxHeightOption.isPresent()) {
maxHeight = maxHeightOption.getAsInt();
out.println(maxHeight);
}
//mapToIntパラメータの2つの書き方は同じです。以下の書き方を好みますが、ideaは警告を報告します
OptionalInt minWeightOption = list1.stream().mapToInt(o -> o.getHeight()).min();
int minWeight = list1.stream().mapToInt(o -> o.getHeight()).reduce(Integer.MAX_VALUE, Integer::min);
1
2
3
4
int maxHeight = list1.Select(o => o.Height).Max();
//list1.Max(o => o.Height)と同じ
int minWeight = list1.Min(o => o.Weight);
- スキップ(skip/Skip),取得(limit/Take)
1
2
3
//skipとlimitパラメータは両方ともlongです。これに注意してください
list1.stream().skip(1L).limit(2L);
ソート
- 重複の削除(Distinct)
1
list1.stream().map(Person::getIdentifier).distinct();
1
list1.Select(o=>o.Identifier).Distinct();
1
list1.Skip(1).Take(2);
- 昇順(sort/OrderBy)
1
2
3
4
5
6
out.println("------------------------------------|昇順|------------------------------------");
list1 = list1.stream().sorted(Comparator.comparing(Person::getBirthday)).collect(Collectors.toList());
out.println(new Gson().toJson(list1));
list1 = list1.stream().sorted((left, right) -> left.getBirthday().compareTo(right.getBirthday())).collect(Collectors.toList());
out.println(new Gson().toJson(list1));
1
2
//昇順
list1 = list1.OrderBy(o => o.Birthday).ToList();
- 降順(sort/OrderByDescending)
1
2
3
4
5
6
7
out.println("------------------------------------|降順|------------------------------------");
list1 = list1.stream().sorted(Comparator.comparing(Person::getBirthday).reversed()).collect(Collectors.toList());
out.println(new Gson().toJson(list1));
list1 = list1.stream().sorted((left, right) -> right.getBirthday().compareTo(left.getBirthday())).collect(Collectors.toList());
out.println(new Gson().toJson(list1));
1
2
3
//降順
list1 = list1.OrderByDescending(o => o.Birthday).ToList();
複数コレクション
- 積集合 list1 ∩ list2
1
2
3
out.println("------------------------------------|積集合 list1 ∩ list2|------------------------------------");
list1.stream().filter(o -> list2.contains(o)).collect(Collectors.toList());
1
2
3
4
5
//結合、以下はlist1とlist2から同じID番号のものを取り出し、新しいコレクションを生成することを意味します
//実際、joinには別の用法があり、sqlserverの多テーブル結合と同様に、異なるデータソースを結合して新しいデータ構造を生成します
var intersect = list1.Join(list2, o => o.Identifier, o => o.Identifier, (a, b) => a).ToList();
//積集合 list1 ∩ list2
intersect = list1.Intersect(list2).ToList();
- 和集合list1 ∪ list2
1
2
3
out.println("------------------------------------|和集合list1 ∪ list2 |------------------------------------");
list1.addAll(list2);
1
2
3
//和集合list1 ∪ list2
var union = list1.Union(list2).ToList();
- 差集合list1 - list2
1
2
3
out.println("------------------------------------|差集合list1 - list2|------------------------------------");
list1.stream().filter(item1 -> !list2.contains(item1)).collect(Collectors.toList());
1
2
//差集合list1 - list2
var except = list1.Except(list2).ToList();
データ構造の変換
1
2
3
4
5
6
7
out.println("------------------------------------|データ構造の変換|------------------------------------");
List<Person> list3 = list1.stream().filter(o -> true).collect(Collectors.toList());
ArrayList<Person> list4 = list1.stream().filter(o -> true).collect(Collectors.toCollection(ArrayList::new));
Set<Person> list5 = list1.stream().filter(o -> true).collect(Collectors.toSet());
Object[] list6 = list1.stream().toArray();
Person[] list7 = list1.stream().toArray(Person[]::new);
1
2
3
4
5
//データ構造の変換
list1.ToArray();
//キーが重複している場合、ToDictionaryはエラーを引き起こすことに注意してください
list1.ToDictionary(o => o.Identifier, o => o);
list1.ToHashSet();
- Подготовка
- Определение сущности
- Определение коллекции
- Одна коллекция
- Классификация и фильтрация
- Подсчет (Count)
- Группировка (GroupBy)
- Первый совпадающий элемент (findFirst/First, FirstOrDefault)
- Обход (ForEach)
- Экстремальные значения Max/Min
- Пропуск (skip/Skip), извлечение (limit/Take)
- Сортировка
- Удаление дубликатов (Distinct)
- По возрастанию (sort/OrderBy)
- По убыванию (sort/OrderByDescending)
- Классификация и фильтрация
- Несколько коллекций
- Пересечение list1 ∩ list2
- Объединение list1 ∪ list2
- Разность list1 - list2
- Преобразование структуры данных
Эта статья написана специально для помощи начинающим изучать операции с коллекциями Java8/C#.
Подготовка
Одна коллекция
Классификация и фильтрация
- Подсчет (Count)
1
2
3
Date time1 = convertLocalDateToTimeZone(LocalDate.of(1990, 1, 1));
//0
Long count1 = list1.stream().filter(o -> o.getBirthday().equals(time1)).count();
1
2
3
4
5
6
int count1 = list1.Where(o => o.Birthday.Equals(new DateTime(1990, 1, 1)) && o.Sex == Sex.Male).Count();
long count2 = list1.Where(o => o.Birthday.Equals(new DateTime(1990, 1, 1)) && o.Sex == Sex.Male).LongCount();
/*
0
0
*/
- Группировка (GroupBy)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Map<Sex, List<Person>> group1 = list1.stream().collect(Collectors.groupingBy(Person::getSex));
Iterator it = group1.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<Sex, List<Person>> groupByItem = (Map.Entry) it.next();
Sex sex = groupByItem.getKey();
out.println(sex);
groupByItem.getValue().forEach(person -> {
out.println(new Gson().toJson(person));
});
}
/*
Вывод:
Male
{"height":170,"weight":50,"identifier":"2","address":"北京","birthday":"Feb 1, 1982 12:00:00 AM","hobbies":["吃飯","看電影"],"sex":"Male"}
Female
{"height":165,"weight":50,"identifier":"1","address":"北京","birthday":"Jan 1, 1981 12:00:00 AM","hobbies":["吃飯","逛街"],"sex":"Female"}
X
{"height":170,"weight":50,"identifier":"3","address":"北京","birthday":"Mar 1, 1983 12:00:00 AM","hobbies":["吃飯","上網"],"sex":"X"}
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
var group1 = list1.GroupBy(o => o.Sex);
//Когда мы используем метод расширения GroupBy(), используется отложенное выполнение. Это означает, что при итерации по коллекции следующий элемент может быть или не быть загружен. Это большое улучшение производительности, но может вызвать интересные побочные эффекты.
list1.RemoveAll(o => o.Sex == Sex.X);//После определения коллекции groupby, изменение исходной коллекции обнаружит, что группа Sex=X уже отсутствует в group1
foreach (var groupByItem in group1)
{
Sex sex = groupByItem.Key;
System.Console.WriteLine(sex);
foreach (Person person in groupByItem)
{
System.Console.WriteLine(JsonConvert.SerializeObject(person));
}
}
/*
Вывод:
{"Height":165,"Weight":50,"Birthday":"1981-01-01T00:00:00","Hobbies":["吃飯","逛街"],"Identifier":"1","Address":"北京","Sex":2}
Male
{"Height":170,"Weight":50,"Birthday":"1982-02-01T00:00:00","Hobbies":["吃飯","看電影"],"Identifier":"2","Address":"北京","Sex":1}
Female
{"Height":165,"Weight":50,"Birthday":"1981-01-01T00:00:00","Hobbies":["吃飯","逛街"],"Identifier":"1","Address":"北京","Sex":2}
Male
{"Height":170,"Weight":50,"Birthday":"1982-02-01T00:00:00","Hobbies":["吃飯","看電影"],"Identifier":"2","Address":"北京","Sex":1}
*/
//Метод ToLookup() создает список, похожий на Dictionary, но это новая коллекция .NET под названием lookup. Lookup, в отличие от Dictionary, неизменяем. Это означает, что после создания lookup вы не можете добавлять или удалять элементы.
var group2 = list1.ToLookup(o => o.Sex);
foreach (var groupByItem in group2)
{
Sex sex = groupByItem.Key;
foreach (Person person in groupByItem)
{
System.Console.WriteLine(sex);
System.Console.WriteLine(JsonConvert.SerializeObject(person));
}
}
/*
Вывод:
{"Height":165,"Weight":50,"Birthday":"1981-01-01T00:00:00","Hobbies":["吃飯","逛街"],"Identifier":"1","Address":"北京","Sex":3}
{"Height":170,"Weight":50,"Birthday":"1982-02-01T00:00:00","Hobbies":["吃飯","看電影"],"Identifier":"2","Address":"北京","Sex":3}
*/
В отличие от этого, stream не имеет операции RemoveAll
- Первый совпадающий элемент (findFirst/First, FirstOrDefault)
1
2
3
4
5
Person after90 = list1.stream()
.filter(o -> o.getBirthday().after(convertLocalDateToTimeZone(LocalDate.of(1990, 1, 1))))
.findFirst()
.orElse(null);
// null
1
2
3
4
5
var after90 = list1.Where(o => o.Birthday >= new DateTime(1990, 1, 1)).First();//Если результат пуст, это вызовет исключение, поэтому этот метод редко используется
//An unhandled exception of type 'System.InvalidOperationException' occurred in System.Linq.dll: 'Sequence contains no elements'
after90 = list1.Where(o => o.Birthday >= new DateTime(1990, 1, 1)).FirstOrDefault();
var after00 = list1.Where(o => o.Birthday >= new DateTime(2000, 1, 1)).FirstOrDefault();
- Обход (ForEach)
1
2
3
4
5
6
7
8
9
10
11
12
13
list1.stream().forEach(o -> {
//Можно работать с коллекцией внутри ForEach
o.setSex(Sex.X);
});
list1.forEach(o -> {
out.println(new Gson().toJson(o));
});
/*
{"height":165,"weight":50,"identifier":"1","address":"北京","birthday":"Jan 1, 1981 12:00:00 AM","hobbies":["吃飯","逛街"],"sex":"X"}
{"height":170,"weight":50,"identifier":"2","address":"北京","birthday":"Feb 1, 1982 12:00:00 AM","hobbies":["吃飯","看電影"],"sex":"X"}
{"height":170,"weight":50,"identifier":"3","address":"北京","birthday":"Mar 1, 1983 12:00:00 AM","hobbies":["吃飯","上網"],"sex":"X"}
*/
1
2
3
4
5
6
7
8
9
10
list1.ForEach(item =>
{
//Можно работать с коллекцией внутри ForEach
item.Sex = Sex.X;
});
list1.ForEach(item =>
{
System.Console.WriteLine(JsonConvert.SerializeObject(item));
});
- Экстремальные значения Max/Min
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//Метод max IntStream возвращает OptionalInt, нужно проверить, есть ли значение, прежде чем читать. getAsInt выдаст ошибку, когда isPresent=false. mapToLong, mapToDouble аналогичны
OptionalInt maxHeightOption = list1.stream().mapToInt(Person::getHeight).max();
//Конкатенация строк, sum, min, max, average чисел — все это специальные reduce.
//Когда коллекция имеет длину 0, возвращается начальное значение Integer.MIN_VALUE. Начальное значение нельзя передавать случайно, причина мне пока не ясна
int maxHeight = list1.stream().mapToInt(Person::getHeight).reduce(Integer.MIN_VALUE, Integer::max);
out.println(maxHeight);
if (maxHeightOption.isPresent()) {
maxHeight = maxHeightOption.getAsInt();
out.println(maxHeight);
}
//Оба способа записи параметров mapToInt одинаковы. Я предпочитаю следующий, но idea выдаст предупреждение
OptionalInt minWeightOption = list1.stream().mapToInt(o -> o.getHeight()).min();
int minWeight = list1.stream().mapToInt(o -> o.getHeight()).reduce(Integer.MAX_VALUE, Integer::min);
1
2
3
4
int maxHeight = list1.Select(o => o.Height).Max();
//То же, что list1.Max(o => o.Height);
int minWeight = list1.Min(o => o.Weight);
- Пропуск (skip/Skip), извлечение (limit/Take)
1
2
3
//Оба параметра skip и limit имеют тип long, обратите на это внимание
list1.stream().skip(1L).limit(2L);
Сортировка
- Удаление дубликатов (Distinct)
1
list1.stream().map(Person::getIdentifier).distinct();
1
list1.Select(o=>o.Identifier).Distinct();
1
list1.Skip(1).Take(2);
- По возрастанию (sort/OrderBy)
1
2
3
4
5
6
out.println("------------------------------------|По возрастанию|------------------------------------");
list1 = list1.stream().sorted(Comparator.comparing(Person::getBirthday)).collect(Collectors.toList());
out.println(new Gson().toJson(list1));
list1 = list1.stream().sorted((left, right) -> left.getBirthday().compareTo(right.getBirthday())).collect(Collectors.toList());
out.println(new Gson().toJson(list1));
1
2
//По возрастанию
list1 = list1.OrderBy(o => o.Birthday).ToList();
- По убыванию (sort/OrderByDescending)
1
2
3
4
5
6
7
out.println("------------------------------------|По убыванию|------------------------------------");
list1 = list1.stream().sorted(Comparator.comparing(Person::getBirthday).reversed()).collect(Collectors.toList());
out.println(new Gson().toJson(list1));
list1 = list1.stream().sorted((left, right) -> right.getBirthday().compareTo(left.getBirthday())).collect(Collectors.toList());
out.println(new Gson().toJson(list1));
1
2
3
//По убыванию
list1 = list1.OrderByDescending(o => o.Birthday).ToList();
Несколько коллекций
- Пересечение list1 ∩ list2
1
2
3
out.println("------------------------------------|Пересечение list1 ∩ list2|------------------------------------");
list1.stream().filter(o -> list2.contains(o)).collect(Collectors.toList());
1
2
3
4
5
//Соединение, ниже означает извлечение элементов с одинаковым номером ID из list1 и list2, создание новой коллекции
//На самом деле, join имеет другое использование, подобное многотабличным соединениям в sqlserver, объединяя разные источники данных вместе для создания новых структур данных
var intersect = list1.Join(list2, o => o.Identifier, o => o.Identifier, (a, b) => a).ToList();
//Пересечение list1 ∩ list2
intersect = list1.Intersect(list2).ToList();
- Объединение list1 ∪ list2
1
2
3
out.println("------------------------------------|Объединение list1 ∪ list2 |------------------------------------");
list1.addAll(list2);
1
2
3
//Объединение list1 ∪ list2
var union = list1.Union(list2).ToList();
- Разность list1 - list2
1
2
3
out.println("------------------------------------|Разность list1 - list2|------------------------------------");
list1.stream().filter(item1 -> !list2.contains(item1)).collect(Collectors.toList());
1
2
//Разность list1 - list2
var except = list1.Except(list2).ToList();
Преобразование структуры данных
1
2
3
4
5
6
7
out.println("------------------------------------|Преобразование структуры данных|------------------------------------");
List<Person> list3 = list1.stream().filter(o -> true).collect(Collectors.toList());
ArrayList<Person> list4 = list1.stream().filter(o -> true).collect(Collectors.toCollection(ArrayList::new));
Set<Person> list5 = list1.stream().filter(o -> true).collect(Collectors.toSet());
Object[] list6 = list1.stream().toArray();
Person[] list7 = list1.stream().toArray(Person[]::new);
1
2
3
4
5
//Преобразование структуры данных
list1.ToArray();
//Обратите внимание, если ключ дублируется, ToDictionary вызовет ошибку
list1.ToDictionary(o => o.Identifier, o => o);
list1.ToHashSet();