본문 바로가기
Kotlin

Collection에대하여 -1

by 봄석 2019. 3. 9.

Collection에대하여 

java와 kotlin의 Collection에 대하여 알아보겠습니다.



자료구조 상속,구현도



1 . 다른 언어와 달리 kotlin에서는 변경가능하고 변경 불가능한 콜렉션(list, set, map)을 구분합니다.


예를들어 Kotlin의 List<out T>유형은 읽기 전용의 작업을 제공하는 인터페이스입니다. size와 get등의 함수가 내장되어있습니다.  자바와 마찬가지로 Collection<T>를 상속하며 , Iterable<T>도 상속합니다. 


목록을 변경하려면 MutableList<T>인터페이스를 이용해 추가할수 있습니다 .

이는 Set<out T>/ MutableSet<T>및 Map<K, out V>/MutableMap<K, V>도 동일합니다



ex)

val list = listOf()                //변경 불가능한 list
val mutableList = mutableListOf()  //변경 가능한 list 
 
val set = setOf()                  //변경 불가능한 set
val mutableSet = mutableSetOf()    //변경 가능한 set
 
 
val map = mapOf()                  //변경 불가능한 map

val mutableMap = mutableMapOf()    //변경 가능한 map



2. List(ArrayList, LinkedList)

list는 순서를 중요하게 여기는 자료구조입니다

arrayList는 List<T>의 하위 유형이라고 정의할 수 있습니다.



List와 ArrayList의 유사점

둘 다 초기화 이후에 요소를 추가할 수 없습니다. (immutable입니다)

array.add(1) // cannot be expanded or shrank

list.add("dog") // cannot be expanded or shrank

둘 다 일반적으로 컬렉션이라고 하는 여러 요소를 저장하는 데 사용되는 컨테이너입니다.

val array = arrayOf(1, 2, 3)

val list = listOf("apple", "ball", "cow")




List와 ArrayList의 차이점
ArrayList는 참조를 통해 변경할 수 있습니다. 즉 콘텐츠를 다시 할당할 수 있습니다. List는 변경 불가능합니다 
val array = arrayOf(1, 2, 3)
array[2] = 4 // OK
for (element in array){
  println("$element, ") // 1, 2, 4
}
val list = listOf("apple", "ball", "cow")
list[2] = "cat" //will not compile, lists are immutable
// println(list) = {apple, ball, cow}  

Arrays는 기본 배열(프리미티브타입)에 최적화되어있습니다. java의 기본배열(int [], double [], char [])에 매핑되는 별도의 IntArray, DoubleArray, CharArray등이 있습니다.
val optimisedIntegerArray = intArrayOf ( 1 , 2 , 3 , 4 , 5 , 6 ) // 정수 배열에 최적화 됨
val optimisedDoubleArray = doubleArrayOf ( 1.2 , 2.3 , 3.4 , 4.5 , 5.6 , 6.7 , 7.8 ) // Double Array에 최적화 됨
val optimisedCharacterArray = charArrayOf ( 'a' , 'b' , 'c' , 'd' ) // 문자 배열에 최적화 됨

// List에는 intListOf, charListOf가 없으므로 기본 배열에 최적화되어 있지 않습니다.

  • Array<TYPE>은 정해진 구현을 가진 클래스입니다. 항목을 저장하는 연속된 고정 크기 메모리 영역입니다.
  •  List<T> 및 MutableList<T>는 다른 구현을 가지는 인터페이스입니다.

LinkedList

LinkedList

각각의 데이터는 각각의 node란 곳에 저장이 됩니다.

node는 두 부분으로 나뉘어져 있는데

한곳은 데이터를 저장하고, 다른 한곳은 다음 노드의 주소를 참조합니다.

node A는 ‘내 다음 node는 B다!’ 라는 건 알지만

C나 D의 위치, 존재여부도 모릅니다.


배열과 비슷하지만, 메모리 관리 측면에서 좀 더 효율적입니다.

예를 들어 크기 5의 배열이 있다고 가정하겠습니다.

1번째 위치의 값이 삭제된다면 2, 3, 4번째 위치의 값은

하나씩 앞으로 당겨져야 합니다.

LinkedList의 경우를 살펴보겠습니다.

5개의 값이 있는 LinkedList 객체가 있습니다.

0번째 노드는 1번째 노드를 참조하고, 1번쨰 노드는 2번째 노드는 참조하겠죠.

이 상황에서 1번째 노드를 삭제하면 2, 3, 4번째 노드의 위치가 다 옮겨지는게 아닙니다.

그냥 0번째 노드가 참조하는 노드를 2번째 노드로 설정하면 끝입니다.

즉, 중간에 있는 값이 삭제되거나 추가되는 상황에서는

배열보다 LinkedList를 쓰는게 메모리 관리 측면에서 더 효율적입니다.


자바에서 LinkedList 클래스를 살펴보겠습니다.

Java_api_LinkedList

지금은 Queue를 구현하는데 사용하고 있지만 List도 구현하는 걸 확인할 수 있습니다.


LinkedList 클래스의 생성자를 살펴보겠습니다.

Java_LinkedList_Constuctor

LinkedList는 객체 생성시 크기를 지정하지 않는 걸 볼 수 있습니다.(empty list)

각 데이터들이 앞뒤로 연결되는 구조이기 때문에 미리 공간을 만들어 놓을 필요가 없습니다.


API 문서에서 LinkedList의 메소드를 살펴보면

여러 인터페이스를 구현한 만큼 메소드도 많습니다.

그런데 자세히 살펴보면 중복된 기능을 가진 메소드들이 많습니다.

예를 들어 add(), addLast(), offer(), offerLast()

이 4개의 메소드들은 모두 LinkedList 객체 가장 뒤에 값을 추가하는 역할을 합니다.

이렇게 중복된 메소드들이 많은 이유는 마찬가지로 여러 인터페이스를 구현했기 때문입니다.

저 메소드를 섞어서 쓰면 보는 사람은 더 혼동될 것이기 때문에

하나를 정해서 그것만 쓰는게 좋습니다.

저는 addLast()가 가장 명확하게 기능을 말해주고 있는거 같아 맘에 드네요





3.  Set (HashSet,TreeSet, LinkedHashSet)
우선 set은 자료들의 순서를 신경쓰지 않습니다.
데이터가 존재하는지, 안하는지가 (중복인지 아닌지)중요합니다.
그렇다면 어떨때 쓰일까요 ?

만약 어떤 웹사이트에서 하루 접속하는 사람들 수를 구하고자 합니다. 
접속하는 IP의 숫자를 세면 되겟죠. 근데 한사람이 여러번 접속하면 한 IP가 여러번 찍히게 될것입니다. 이건 한번으로 카운트해줘야 제대로 된 접속자 수를 구할수 있습니다 .
이럴때 Set을 유용하게 쓸 수 있습니다.

Set 인터페이스를 구현하는 클래스들

 클래스

특징 

 HashSet

순서가 필요없는 데이터를 hashTable에 저장. 

Set중에 성능 가장 좋음

 TreeSet

저장된 데이터의 값에 따라 정렬됨. 

red-black tree 타입으로 값이 저장. HashSet보다 성능이 느림 

LinkedHashSet

연결된 목록 타입으로 구현된 hash table에 데이터 저장. 

저장된 순서에 따라 값이 정렬. 셋중 가장 느림 

위 3개의 클래스들이 성능 차이가 나는 이유는 데이터 정렬 때문입니다. 

HashSet은 별도의 정렬작업을안해 가장 빠릅니다.



HashSet_Java_API

먼저 HashSet의 상속관계를 살펴보겠습니다.

AbstractCollection을 확장한 건 ArrayList와 같지만,

HashSet은 AbstractSet을 확장했습니다.

AbstractSet 문서에 들어가보면 구현되어 있는 메소드는

equals(), hashCode(), removeAll() 3개 뿐입니다.

Set은 순서가 필요없고, 중복을 허용하지 않으므로

중복을 체크하는 equals()와 hashCode()는 필수인거 같습니다


HashSet의 생성자

Java_HashSet_Constructor

HashSet의 메소드

Java_HashSet_method

순서가 필요없어서 그런지 메소드가 적습니다.



4.  Map(HashMap, TreeMap)

map은 'Key-Value' pair 형식으로 데이터를 저장합니다. key와 value는 매치되어있습니다.

Map의 메소드를 찾아보면 다음과 같습니다.

java_map_method

이 중 가장 많이 쓰이는 건 put(), get(), remove()입니다.


map을 구현하고 있는 클래스는 HashMap, TreeMap, LinkedHashMap 입니다.


HashMap

HashMap 은 요소가 Map에  배열되는 값들의 순서를 보장하지 않습니다 .

즉, HashMap 의  와  을 반복하면서 순서를 지정할 수는 없습니다 .

@Test
public void whenInsertObjectsHashMap_thenRandomOrder() {
    Map<Integer, String> hashmap = new HashMap<>();
    hashmap.put(3, "TreeMap");
    hashmap.put(2, "vs");
    hashmap.put(1, "HashMap");
     
    assertThat(hashmap.keySet(), containsInAnyOrder(1, 2, 3));

}


TreeMap 

자연 순서에 따라 정렬 .

TreeMap 객체를 자연 순서에 따라 소트 할 수없는 경우 Comparator 또는 Comparable 를 사용해, 요소가  내에 배치되는 순서를 정의 할 수 있습니다 .

@Test
public void whenInsertObjectsTreeMap_thenNaturalOrder() {
    Map<Integer, String> treemap = new TreeMap<>();
    treemap.put(3, "TreeMap");
    treemap.put(2, "vs");
    treemap.put(1, "HashMap");
     
    assertThat(treemap.keySet(), contains(1, 2, 3));

}


HashMap과 TreeMap의 차이


HashMap 은 하나의 null  와 많은 null 값을 저장할 수 있습니다 .

예를 들어 보겠습니다.

@Test
public void whenInsertNullInHashMap_thenInsertsNull() {
    Map<Integer, String> hashmap = new HashMap<>();
    hashmap.put(null, null);
     
    assertNull(hashmap.get(null));

}

그러나 TreeMap 은 널 (null) 키를 허용하지 않지만 많은  값을 포함 할 수 있습니다 .

 키가 있기 때문에 허용되지 않습니다 은 compareTo () 또는 비교 () 메소드는 발생 NullPointerException가 :

@Test(expected = NullPointerException.class)
public void whenInsertNullInTreeMap_thenException() {
    Map<Integer, String> treemap = new TreeMap<>();
    treemap.put(null, "NullPointerException");

}




댓글