Java를 포함한 많은 프로그래밍 언어에서 반복자(iterator)와 컬렉션(collection)이라는 개념을 표준라이브러리에 포함하고 있다.
컬렉션은 여러 개의 오브젝트를 모아 놓은 데이타 구조이며 반복자는 그러한 데이터 구조를 따라 각각의 오브젝트 순회하는 기능을 제공한다.
자바 표준라이브러리(JDK)에서 이 부분에 해당하는 것을 자바 컬렉션 프레임워크(Java Collection Framework)라고 부르기도 한다.
이러한 컬렉션 프레임워크가 범용 프로그래밍 언어 표준라이브러리의 일부로 제공되기 시작한 것은 C++ 언어부터 유행하기 시작했다. Standard Template Library (STL)가 C++ 표준라이브러리의 컨테이너, 이터레이터, 알고리듬, 함수객체 관련 내용으로 표준화되기 시작한 것을 계기로 그 이후 Java를 비롯한 다른 언어들(C#, Python, JavaScript, ...)도 이런 추세를 따르고 있다. C++에서 컨테이너가 Java에서는 컬렉션에 해당하며 이터레이터는 C++에서도 Java에서도 똑같이 부르고 있다. 다른 언어들도 컬렉션(혹은 컨테이너)과 이터레이터(혹은 제너레이터) 이 두 개념을 포함하여 표준라이브러리로 제공하는 경우가 많다.
참고로 자바 컬렉션 프레임워크에는 없지만 유용한 컬렉션(mutiset 등)을 Guava나 Apache Commons 라이브러리에서 제공하고 있다.
Stream은 영어 단어 의미로는 물줄기 또는 시냇물을 의미한다. 이런 단어의 원래 이미지처럼 한번 주르륵 흘러가고 나면 없어지는 대상으로 일련의 물건들을 마치 컨베이어 벨트에서 흘러가면서 처리해서 내보내는 그런 것으로 생각하면 된다. 단 Stream은 실제로 대상을 처리하기 전까지는 실체화를 최대한 미루기 때문에 개념상으로는 매우 많은 객체를 포함하는 기다란 스트림을 정의하더라도 실제로 뽑아서 처리하는 객체들만 실체화되기도 하는데, 이를 게으른(lazy) 계산법을 구현한다고 이야기하기도 한다. map
, filter
등 함수형 프로그래밍의 관용구(idiom)격인 고차함수 메소드를 Java에서는 바로 보통 스트림을 통해 활용한다고 보면 된다.
String courses[] = {"데이터구조", "객체지향프로그래밍", "이산수학", "프로그래밍언어"};
자바의 배열 순회를 위해 두 가지 다른 형태의 for 문을 활용할 수 있다.
for (int i = 0; i < courses.length; ++i) { // 전통적인 for 문
System.out.println( courses[i] );
}
데이터구조 객체지향프로그래밍 이산수학 프로그래밍언어
for (String c : courses) { // 자바에 나중에 추가된 방식
System.out.println( c );
}
데이터구조 객체지향프로그래밍 이산수학 프로그래밍언어
List<String> cs = List.of("고급프로그래밍", "인공지능", "시스템프로그래밍");
기본적으로 제공되는 배열이 아닌 사용자 정의 데이타 구조도 Iterable 인터페이스를 구현하고 있는 경우에는 위에서 살펴본 두 번째 형태의 for 문을 활용할 수 있다.
cs instanceof List
true
cs instanceof Collection
true
cs instanceof Iterable
true
for (String c : cs) {
System.out.println( c );
}
고급프로그래밍 인공지능 시스템프로그래밍
// 위의 for 문을 같은 일을 하는 전통적인 for 문으로 바꿔 작성해보면
for (var i = cs.iterator(); i.hasNext(); /*내용없음*/ ) {
String c = i.next(); //
System.out.println( c );
}
고급프로그래밍 인공지능 시스템프로그래밍
List
: 일렬로 배열된 원소를 포함하는 데이터 구조에 대한 인터페이스. C++ 등 다른 언어에서는 이에 해당하는 개념을 Sequence라는 용어로 부르기도 함.Set
: 집합(중복 없는 원소들의 모임)을 표현하는 데이터 구조에 대한 인터페이스Map
: 키(key)에 대응되는 값(value)을 모아놓은 데이터 구조에 대한 인터페이스참고로 Map
은 Collection
이나 Iterable
의 하위 인터페이스가 아니다!!!
List
¶List<Integer> l1 = List.of(1,2,3,4);
l1.getClass()
class java.util.ImmutableCollections$ListN
l1.add(5)
--------------------------------------------------------------------------- java.lang.UnsupportedOperationException: null at java.base/java.util.ImmutableCollections.uoe(ImmutableCollections.java:71) at java.base/java.util.ImmutableCollections$AbstractImmutableCollection.add(ImmutableCollections.java:75) at .(#23:1)
List<Integer> l2 = new LinkedList<Integer>()
l2
[]
l2.add(1)
true
l2.add(2)
true
l2
[1, 2]
l2.getClass()
class java.util.LinkedList
List<Integer> l3 = new LinkedList<Integer>( List.of(1,2,3) );
l3
[1, 2, 3]
l3.add(4)
true
l3
[1, 2, 3, 4]
l3.getClass()
class java.util.LinkedList
Integer[] arr4 = {1,2,3,4};
List<Integer> l4 = Arrays.asList( arr4 ); // 자바 배열로부터 LinkedList를 초기화
l4;
[1, 2, 3, 4]
l2.getClass()
class java.util.LinkedList
List<Integer> l5 = new ArrayList<>( List.of(1,2,3,4,5) ); // ImmutableList로부터 초기화
l5
[1, 2, 3, 4, 5]
l5.getClass()
class java.util.ArrayList
l5.add(6)
true
l5
[1, 2, 3, 4, 5, 6]
import org.apache.commons.lang3.tuple.*;
List<Pair<String,Integer> > l6 = List.of( Pair.of("one",1), Pair.of("two",2), Pair.of("three",3) );
l6
[(one,1), (two,2), (three,3)]
List<List<Integer> > l7 = List.of( List.of(1,2), List.of(3,4,5), List.of(6,7,8,9) );
l7
[[1, 2], [3, 4, 5], [6, 7, 8, 9]]
Set
¶Set<Integer> s1 = Set.of(1,2,3,4);
s1
[3, 2, 1, 4]
s1.getClass()
class java.util.ImmutableCollections$SetN
s1.contains(3)
true
s1.contains(5)
false
s1 instanceof Iterable
true
for (Integer v : s1)
System.out.println(v)
3 2 1 4
s1.add(5)
--------------------------------------------------------------------------- java.lang.UnsupportedOperationException: null at java.base/java.util.ImmutableCollections.uoe(ImmutableCollections.java:71) at java.base/java.util.ImmutableCollections$AbstractImmutableCollection.add(ImmutableCollections.java:75) at .(#56:1)
Set<Integer> s2 = new HashSet<>( Set.of(2,3,4) ); // ImmutableSet으로 초기화
s2
[2, 3, 4]
s2.add(1)
true
s2.add(3)
false
s2.add(5)
true
s2
[1, 2, 3, 4, 5]
Set<String> s3 = new HashSet<>( Set.of("hello", "world", "hi", "wall") );
s3
[hi, world, hello, wall]
Set<String> s4 = new TreeSet<>( Set.of("hello", "world", "hi", "wall") );
s4
[hello, hi, wall, world]
Set<Pair<String,Integer> > s5 =
Set.of( Pair.of("one",1), Pair.of("two",2), Pair.of("three",3), Pair.of("three",4) );
s5
[(three,4), (one,1), (two,2), (three,3)]
Set<Pair<String,Integer> > s6 = new TreeSet<>( s5 );
s6
[(one,1), (three,3), (three,4), (two,2)]
Set<List<Integer> > s7 = Set.of( List.of(1,2), List.of(2,3,4), List.of(5,6,7,8) );
s7
[[2, 3, 4], [1, 2], [5, 6, 7, 8]]
Set<List<Integer> > s8 = new TreeSet<>( s7 ); // 왜 안될까?
--------------------------------------------------------------------------- java.lang.ClassCastException: class java.util.ImmutableCollections$ListN cannot be cast to class java.lang.Comparable (java.util.ImmutableCollections$ListN and java.lang.Comparable are in module java.base of loader 'bootstrap') at java.base/java.util.TreeMap.compare(TreeMap.java:1291) at java.base/java.util.TreeMap.put(TreeMap.java:536) at java.base/java.util.TreeSet.add(TreeSet.java:255) at java.base/java.util.AbstractCollection.addAll(AbstractCollection.java:352) at java.base/java.util.TreeSet.addAll(TreeSet.java:312) at java.base/java.util.TreeSet.<init>(TreeSet.java:160) at .(#73:1)
Map
¶Map<String,Integer> m1 = Map.of("Bob",13, "Sam",16, "Ted",17); // key1,value1, key2,value2, ...
m1
{Sam=16, Bob=13, Ted=17}
m1.size()
3
m1.get("Sam")
16
m1.get("Bob")
13
m1.put("Paul",20);
--------------------------------------------------------------------------- java.lang.UnsupportedOperationException: null at java.base/java.util.ImmutableCollections.uoe(ImmutableCollections.java:71) at java.base/java.util.ImmutableCollections$AbstractImmutableMap.put(ImmutableCollections.java:714) at .(#79:1)
m1.getClass()
class java.util.ImmutableCollections$MapN
m1.get("Paul") == null
true
m1 instanceof Iterable
false
for (var x : m1) System.out.println(x) // Iterable 인터페이스를 구현하지 않으므로
| for (var x : m1) System.out.println(x); for-each not applicable to expression type required: array or java.lang.Iterable found: java.util.Map<java.lang.String,java.lang.Integer>
m1.entrySet()
[Sam=16, Bob=13, Ted=17]
Set<Map.Entry<String,Integer> > es = m1.entrySet();
es instanceof Iterable
true
for (var e : es)
System.out.println(e)
Sam=16 Bob=13 Ted=17
for (var e : es)
System.out.println( e.getKey() ) // Map.Entry의 key 얻어오기
Sam Bob Ted
for (var e : es)
System.out.println( e.getValue() ) // Map.Entry의 value 얻어오기
16 13 17
Map<String,Integer> m2 = new HashMap<String,Integer>();
m2
{}
m2.size()
0
m2.put("Bob",13);
m2.put("Sam",16);
m2.put("Ted",17);
m2
{Ted=17, Bob=13, Sam=16}
m2.get("Sam");
16
m2.getClass()
m2.put("Paul",20)
m2
{Ted=17, Bob=13, Paul=20, Sam=16}
m2.put("Ted",19)
17
m2
{Ted=19, Bob=13, Paul=20, Sam=16}
m2.get("Ted")
19
TreeMap
도 있다 ... 여러분이 스스로
import java.util.stream.*;
Stream stream1 = Stream.of(1,2,3,4,2,3); // 유한개의 데이터로 스트림 정의
stream1.collect( Collectors.toList() ) // 스트림의 끝에서 정보를 모아 List를 만든다
[1, 2, 3, 4, 2, 3]
stream1.collect( Collectors.toSet() ) // 이미 한번 흘려보냈기 때문에 더 이상 유효하지 않은 스트림
--------------------------------------------------------------------------- java.lang.IllegalStateException: stream has already been operated upon or closed at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:229) at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578) at .(#112:1)
Stream stream2 = Stream.of(1,2,3,4,2,3); // 유한개의 데이터로 스트림 정의
stream2.collect( Collectors.toSet() ) // 스트림의 끝에서 정보를 모아 Set을 만든다
[1, 2, 3, 4]
List.of(1,2,3,4,2,3).stream().map( x -> x*2 ).collect( Collectors.toSet() );
[2, 4, 6, 8]
List.of(1,2,3,4,2,3).stream().map( x -> x*2 ).collect( Collectors.toList() );
[2, 4, 6, 8, 4, 6]
List<Integer> l6 = List.of(1,2,3,4,2,3)
l6.stream().map( x -> x*2 ).collect( Collectors.toSet() )
[2, 4, 6, 8]
l6
[1, 2, 3, 4, 2, 3]
// stream() 메소드를 부를 때마다 새로운 Stream 생성
l6.stream().map( x -> x*x ).collect( Collectors.toSet() )
[16, 1, 4, 9]
Stream<Integer> stream2 = Stream.of(1,2,3,4);
Stream<Integer> stream3 = Stream.of(11,12,13,14);
Stream.concat(stream2, stream3).collect( Collectors.toList() )
[1, 2, 3, 4, 11, 12, 13, 14]
List<Integer> l7 = List.of(4,5,6,4,5);
Set<Integer> s8 = Set.of(3,5,7,9);
// 리스트 l7과 리스트 s8의 모든 원소를 모아서 집합으로 만들고 싶다
Stream.concat(l7.stream(), s8.stream()).collect( Collectors.toSet() )
[3, 4, 5, 6, 7, 9]
// 1,2,3,4,... 무한 스트림 (지금 당장 무한한 개수를 계산하는 것은 아님)
Stream<Integer> stream4 = Stream.iterate( 1, x -> x+1 );
stream4.limit(100).collect( Collectors.toList() ) // 유한한 개수만큼만 실체화하여 List로 변환
[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, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100]
// 자연수를 제곱한 수들 중에서 앞에서 10개만 모아서 리스트로 변환
Stream.iterate( 1, x -> x+1 ).map(x -> x*x).limit(10).collect( Collectors.toList() )
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
// 자연수 중에서 홀수만 선택해 제곱한 수들 중에서 앞에서 10개만 모아서 리스트로 변환
Stream.iterate( 1, x -> x+1 )
.filter(x -> x%2 != 0)
.map(x -> x*x)
.limit(10).collect( Collectors.toList() )
[1, 9, 25, 49, 81, 121, 169, 225, 289, 361]
List< List<Integer> > ll1 = List.of( List.of(1,2), List.of(3,4,5), List.of(6,7,8,9,10) );
ll1.stream() // Stream< List<Integer> >
.map( l -> l.stream() ) // Stream< Stream<Integer> >
.collect( Collectors.toList() ) // List< Stream<Integer> >
[java.util.stream.ReferencePipeline$Head@7aaaf743, java.util.stream.ReferencePipeline$Head@31f7a210, java.util.stream.ReferencePipeline$Head@4a15f73f]
List< List<Integer> > ll2 = List.of( List.of(1,2,1),
List.of(2,3,4,3),
List.of(3,5,7,9,5) );
ll2.stream() // Stream< List<Integer> >
.map( l -> l.stream() ) // Stream< Stream<Integer> >
.map( s -> s.collect( Collectors.toList() ) ) // Stream< List<Integer> >
.collect( Collectors.toList() ) // List< List<Integer> >
[[1, 2, 1], [2, 3, 4, 3], [3, 5, 7, 9, 5]]
List< List<Integer> > ll2 = List.of( List.of(1,2,1),
List.of(2,3,4,3),
List.of(3,5,7,9,5) );
// flatMap은 Stream에 흘러가는 각각의 데이터를 처리해 결과로 스트림을 만들고
// (각각의 스트림을 만드는 방법은 인자로 작성한 람다함수를 각각에 데이터에 호출)
// 그렇게 만들어진 여러개의 모든 스트림을 다 이어붙여서 하나의 스트림으로 구성한다
ll2.stream() // Stream< List<Integer> >
.flatMap( l -> l.stream() ) // Stream<Integer>
.collect( Collectors.toList() ) // List<Integer>
[1, 2, 1, 2, 3, 4, 3, 3, 5, 7, 9, 5]
List<Integer> list1 = Stream.iterate(1, x -> x+1).limit(10).collect( Collectors.toList() );
list1
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
list1.stream().reduce( (x,y) -> x+y )
Optional[55]
list1.stream().reduce( (x,y) -> x+y ).get()
55
Stream<Integer> stream11 = Stream.of(); // 직접 비어있는 정수의 스트림 정의
stream11.reduce( (x,y) -> x+y ) /// 아무것도 없으면
Optional.empty
// 빈 리스트로부터 정수의 스트림을 생성
new LinkedList<Integer>().stream().reduce( (x,y) -> x+y ) // 아무것도 없으면
Optional.empty
list1.stream().reduce(0, (x,y) -> x+y)
55
new LinkedList<Integer>().stream().reduce(0, (x,y) -> x+y)
0
list1.stream().reduce( 1000, (x,y) -> x+y )
1055
new LinkedList<Integer>().stream().reduce( 1000, (x,y) -> x+y )
1000
// 노트에서 설명하지 않은 것 중에 많이 쓰는 스트림 메소드 forEach 등은 Do it 자바 교재 참고