集合是最为常见的容器,在日常工作之中经常用到,一些集合的常规操作以及不同的集合之间的转换,虽然看似是基础中的基础,但实践中会发现并不是那么显而易见的,特别是涉及boxing的时候,这篇就是想总结 出一些最优的方式来进行集合操作和转换。
注意 :这里集合的意思是容器,是一个更为宽泛的概念,包括数组,列表,Map,Set等。
核心理念
不造轮子,也就是说尽可能的复用JDK里面的函数库,无论是数组结构,还是针对数组结构的操作。
另外,就是尽可能的用函数化,也即是Stream API来完成,因为这更直观,当然 这里不能为了用而用,还是要保持代码的简洁和易懂。
另外,集合中存放的都是对象,也就是Object的子类,但对于基础数据如int,float等并不是对象,但集合中难免会存放基础数据,这就涉及了Autoboxing ,通常会有一定的性能开销,但最大的麻烦在于有时候在不同的集合中来回转换时autoboxing不起作用,就需要一些额外的操作。
这里为了简化说明,涉及对象的都用String对象,基础数据类型都用int,这样既简单又具体普适代表性。
本文代码所在位置请看这里 。
一维集合
包括,数组,列表,Set,以及Queue和Stack。
列表
为啥要先说列表呢,因为列表是最为常用的集合,并且它的函数式操作也是最为容易理解的,所以我们就从它开始。
其他集合转化为列表
Arrays.asList()
List.of()
列表的经典操作
遍历
转化
过滤
折叠
最大值
最小值
求和
求平均值
转化为数组
带有索引来遍历
很多时候,带有索引来遍历列表或者数组很有用,但很不幸,没有直接方法,要不然就只能用for-loop。
最好的方法,就是借助IntStream
1
2
3
IntStream . range ( 0 , names . length )
. mapToObj ( i -> String . format ( "#%d: %s %d" , i , names [ i ], names [ i ]. length ()))
. forEach ( System . out :: println );
数组
初始化数组
声明数组时,可以直接使用花括号来初始化,数组的尺寸可以省略,因为编译器能从初始化时推断出来
1
2
3
4
5
6
7
int [] a = { 1 , 2 , 3 };
int [][] b = {
{ 1 , 2 , 3 },
{ 4 , 5 , 6 },
{ 7 , 8 , 9 }
};
String [] names = { "James Harden" , "Kevin Durant" , "Kyrie Irving" };
如果不能在声明时初始化,那就只能用数组的下标来进行单个赋值了
1
2
3
4
5
int [] a = new int [ 3 ];
a = { 1 , 2 , 3 }; // compile error
a [ 0 ] = 1 ;
a [ 1 ] = 2 ;
a [ 3 ] = 3 ;
数组操作
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
// 数组求和
System . out . println (
Arrays . stream ( a )
. sum ()
);
// 二维数组,找行和最大值
System . out . println (
Arrays . stream ( b )
. map ( row -> Arrays . stream ( row ). sum ())
. max ( Integer: : compareTo ). get ()
);
// 二维数组求和
System . out . println (
Arrays . stream ( b )
. flatMapToInt ( row -> Arrays . stream ( row ))
. sum ()
);
Arrays . stream ( names ). forEach ( System . out :: println );
IntStream . range ( 0 , names . length )
. mapToObj ( i -> String . format ( "#%d: %s %d" , i , names [ i ], names [ i ]. length ()))
. forEach ( System . out :: println );
// outputs
//6
//24
//45
//James Harden
//Kevin Durant
//Kyrie Irving
//#0: James Harden 12
//#1: Kevin Durant 12
//#2: Kyrie Irving 12
数组转为列表
如果数组的元素是Object,可以直接使用Arrays.asList来转换
1
2
3
4
5
List < String > nameList = Arrays . asList ( names );
System . out . println ( nameList );
//output
//[James Harden, Kevin Durant, Kyrie Irving]
但如果数组元素是基础数据,则不能直接使用Arrays.asList
1
2
3
List < Integer > aToList = Arrays . asList ( a ); // compile error
System . out . println ( Arrays . asList ( a ));
// [[I@38af3868]
对于基础类型,Arrays.asList会把整个数组当成一个对象,结果的列表会变成List of array,也即是一个数组对象的列表,里面每个元素是数组对象,数组本身也是一个Object。这就与预期结果不一样,预期结果应该是List,结果却变成了List<int[]>。
可以用Stream来完成正确的转换。
1
2
3
4
5
List < Integer > aToList = Arrays . stream ( a )
. boxed ()
. collect ( Collectors . toList ());
System . out . println ( aToList );
// [1, 2, 3]
如果是Java 16,可以直接写成:
1
List < Integer > aToList = Arrays . stream ( a ). boxed (). toList ();
列表转化为数组
一般来说应该尽可能用列表,因为列表是可以自己管理长度,很多时候我们并不知道集合的长度。
但有时候吧,因为接口或者各种原因,它需要的又是数组,这就需要从列表转化为数组。
如果列表里的元素是对象的话,可以直接使用List#toArray,如果是基础类型的话,就需要做一下转换:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
String [] nameArray = nameList . toArray ( new String [ 0 ]);
Arrays . stream ( nameArray ). forEach ( System . out :: println );
// James Harden
// Kevin Durant
// Kyrie Irving
int [] aArray =
aToList . stream ()
. mapToInt ( Integer: : intValue )
. toArray ();
Arrays . stream ( aArray ). forEach ( System . out :: println );
//1
//2
//3
二维集合
二维数组(矩阵),嵌套列表,Map,图。
创建和初始化
二维数组的创建和初始化可以参考 上面数组的一节,就不重复了,主要就是在声明的时候可以直接初始化。
嵌套列表的创建和初始化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
List < List < String >> lists = Arrays . asList (
Arrays . asList ( "James" , "Harden" ),
Arrays . asList ( "Kevin" , "Durant" ),
Arrays . asList ( "Kyrie" , "Irving" )
);
List < List < Integer >> ageHeights = List . of (
List . of ( 35 , 200 ),
List . of ( 32 , 211 ),
List . of ( 30 , 192 )
);
System . out . println ( lists );
// [[James, Harden], [Kevin, Durant], [Kyrie, Irving]]
System . out . println ( ageHeights );
// [[35, 200], [32, 211], [30, 192]]
转化
嵌套列表转化为二维数组
1
2
3
4
5
6
7
8
9
10
11
String [][] nameArrays = lists . stream ()
. map ( row -> row . toArray ( String []:: new ))
. toArray ( String [][]:: new );
System . out . println ( Arrays . deepToString ( nameArrays ));
// [[James, Harden], [Kevin, Durant], [Kyrie, Irving]]
int [][] ageArrays = ageHeights . stream ()
. map ( row -> row . stream (). mapToInt ( Integer: : intValue ). toArray ())
. toArray ( int [][]:: new );
System . out . println ( Arrays . deepToString ( ageArrays ));
// [[35, 200], [32, 211], [30, 192]]
二维数组转化为列表
这里又分两种情况,一种是把二维数组展平为一维列表,另外,就是转化为嵌套列表
1
2
3
4
5
6
7
8
9
10
11
12
List < String > nameList = Arrays . stream ( nameArrays )
. flatMap ( Arrays: : stream )
. collect ( Collectors . toList ());
System . out . println ( nameList );
// [James, Harden, Kevin, Durant, Kyrie, Irving]
List < Integer > ageList = Arrays . stream ( ageArrays )
. flatMapToInt ( Arrays: : stream )
. mapToObj ( Integer: : valueOf )
. collect ( Collectors . toList ());
System . out . println ( ageList );
// [35, 200, 32, 211, 30, 192]
二维数组转化为嵌套列表
有了前面一维的方法,这个就好办了,无非就是多一层转换
1
2
3
4
5
6
7
8
9
10
11
List < List < String >> nameNList = Arrays . stream ( nameArrays )
. map ( Arrays: : asList )
. collect ( Collectors . toList ());
System . out . println ( nameNList );
// [[James, Harden], [Kevin, Durant], [Kyrie, Irving]]
List < List < Integer >> ageNList = Arrays . stream ( ageArrays )
. map ( row -> Arrays . stream ( row ). boxed (). collect ( Collectors . toList ()))
. collect ( Collectors . toList ());
System . out . println ( ageNList );
// [[35, 200], [32, 211], [30, 192]]
数组列表转化为二维数组
有些时候会有一些数组列表,也就是外层 是一个列表,但每个元素都是一个数组,这时如果想转成二维数组,就可以直接用链式来转就行了。
1
2
3
4
5
6
7
8
List < int []> list = new ArrayList <>();
list . add ( new int [] { 1 , 2 });
list . add ( new int [] { 3 , 4 });
list . add ( new int [] { 5 , 6 });
int [][] matrix = list . stream (). toArray ( int [][]:: new );
System . out . println ( Arrays . deepToString ( matrix ));
// [[1, 2], [3, 4], [5, 6]]
Map操作和转化
Map也是非常常用的数组结构,它的特点是键值的映射关系,优点是快速查询、删除和修改,能达到常数O(1)级别,在日常使用中特别常见。
Map的遍历
遍历有很多方法,这里列出四种最常见的遍历方式
forEach,里面lambda的参数是key, value键值对
通过entrySet,这里是把entry转化为一个Set
通过keySet,把所有的Key转成一个Set
通过values,把所有的Value,转成一个Collection类型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Map < String , String > map = new HashMap <>();
map . put ( "Guard" , "James Harden" );
map . put ( "Forward" , "Kevin Durant" );
map . put ( "Point Guard" , "Kyrie Irving" );
map . forEach (
( k , v ) -> System . out . println ( k + " -> " + v )
);
map . entrySet (). forEach ( entry -> System . out . println ( entry . getKey () + " -> " + entry . getValue ()));
map . keySet (). forEach ( System . out :: println );
map . values (). stream (). forEach ( System . out :: println );
当然还有基础的遍历方法,如用for-loop或者用Iterator,就不重复了,网上一搜一大把。
Map转成列表
Map毕竟是二维数组结构,它里面有key和value,列表是一维数组,在转化的过程,涉及选择key还是选择value,要依实际目的决定。
而且需要注意的是Map并没有直接生成stream的方法,要想使用Stream API只能使用entrySet().stream()。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
List < String > guards = map . entrySet (). stream ()
. filter ( entry -> entry . getKey (). contains ( "Guard" ))
. map ( Map . Entry :: getValue )
. collect ( Collectors . toList ());
System . out . println ( guards );
// [James Harden, Kyrie Irving]
List < String > positions = map . keySet (). stream (). collect ( Collectors . toList ());
System . out . println ( positions );
// [Forward, Guard, Point Guard]
List < String > players = map . values (). stream (). collect ( Collectors . toList ());
System . out . println ( players );
// [Kevin Durant, James Harden, Kyrie Irving]
Map转成数组
可以把key, value转成数组,也可以把key和value转成数组。
1
2
3
4
5
6
String [] keyArray = map . keySet (). toArray ( String []:: new );
System . out . println ( Arrays . deepToString ( keyArray ));
// [Forward, Guard, Point Guard]
String [] valueArray = map . values (). toArray ( String []:: new );
System . out . println ( Arrays . deepToString ( valueArray ));
// [Kevin Durant, James Harden, Kyrie Irving]
但如果是Stream,比如用了entrySet().stream()或者keySet().stream()之后,就没有办法直接转成数组了,这时只能先转成列表,再转成数组。
多维集合
多维数组
参考资料