brunch

You can make anything
by writing

C.S.Lewis

by 에디의 기술블로그 Apr 29. 2018

Collection - List, Set, Map

- Java Collection 에 대한 간략한 정리

Overview

Java Collection에 대해서 간략하게 정리해본다.  

by Eddy.Kim


Collection

Java Collection의 주요 인터페이스는 List, Set, Map 이다. 인터페이스를 구분하는 가장 중요한 개념은 "순서", "데이터중복여부" 이다.

List : 순서 O, 데이터중복 O

Set : 순서 X, 데이터중복 X

Map : Key&Value 저장, Key중복X, Value중복 O





List


List는 데이터를 순서에 맞게 일렬로 구성하는데, 인덱스가 부여되기 때문에 인덱스로 검색할 수 있다. Set 과는 다르게 중복을 허용한다. List 인터페이스의 메서드는 아래와 같다. 

boolean add(E e)

void add(int index, E element);

E set(int index, E element);

boolean contains(Object o);

E get(int index);

boolean isEmpty();

int size();

void clear();

E remove(int index);

boolean remove(Object o);

기본 메서드에 대한 설명은 생략한다. 


List 인터페이스를 구현한 클래스는 아래와 같다.

ArrayList

AttributeList

CopyOnWriteArrayList

LinkedList

RoleList

RoleUnresolvedList

Stack

Vector


이 글에서는 가장 많이 사용하는 ArrayList 위주로 스터디 한다.


ArrayList

ArrayList 는 resizable-array 이다. 즉, 저장 용량이 자동으로 늘어난다. 기본 생성자로 ArrayList 를 생성하면 10개의 객체 저장 용량을 갖는다. 물론, 처음부터 용량을 크게 잡을수 있다.


List myList = new ArrayList(100);

myList.add("Eddy.Kim");

String name = (String)list.get(0);


ArrayList는 Object타입으로 변환되어 저장되기 때문에, 모든 종류의 객체를 저장할 수 있다. 하지만, 저장 및 조회할 때 Object로 변환 해야 하므로 성능이 좋지는 않다. 이런 단점을 보완하는 것이 바로 제네릭이다. 


List<String> myList = new ArrayList<String>();

myList.add("Eddy.Kim");

String name = list.get(0);



ArrayList vs LinkedList

ArrayList와 마찬가지로 List 인터페이스를 구현하지만, 몇가지 차이점으로 인해서 어떤 클래스를 사용할지 결정하는 데 영향을 미칠 것이다. 

일반적으로 ArrayList는 LinkedList 보다 검색이 빠르다. 또한 순차적을 배열에 객체를 추가 및 삭제 하는 경우에도 ArrayList가 더 좋은 성능을 보인다. 반면에, 배열의 중간 위치에 추가 될 때 LinkedList는 Array 크기를 조정하거나, 인덱스를 업데이트할 필요가 없기 때문에 링크된 목록에서 객체의 추가, 제거 작업은 LinkedList가 ArrayList 보다 더 빠르다. 하지만, LinkedList 는 앞뒤 두개의 참조를 저장하기 때문에 ArrayList 보다 더 많은 메모리 영역을 차지하게 될 것이다. 



두 개의 List 의 교집합, 차집합, 합집합???

두 List 사이에 공통 데이터를 찾는 메서드는 아래와 같다. 

addAll  : 두 리스트의 모든 객체 저장

removeAll : 공통으로 포함된 객체 제거

retainAll : 공통으로 포함된 객체만 남김


List<String> trackA = new ArrayList<>();
List<String> trackB = new ArrayList<>();
trackA.addAll(Arrays.asList(new String[] { "홍길동", "박찬호", "박지성"}));
trackB.addAll(Arrays.asList(new String[] { "박지성"}));
trackA.retainAll(trackB);
System.out.println(trackA);

//[박지성]



Read-Only 배열

Collections 클래스의 unmodifiableList 메서드를 사용하여, Read-Only List 를 만들 수 있다. 


List<String> coffees = new ArrayList<>();
coffees.addAll(Arrays.asList(new String[] { "라떼", "모카", "아메리카노"}));
List<String> fixCoffees = Collections.unmodifiableList(coffees);
//Exception
fixCoffees.add("홍차");



Sorting

Collections 클래스는 Sorting을 위해서, 아래와 같은 Static 메소드를 제공한다.

sort(List<T> list)

sort(List<T> list, Comparator<? super T> c)


id, 가격, 이름으로 구성된 Coffee 클래스가 있다고 가정하자.


public class Coffee{

    생략...

}


Collections.sort 를 사용하기 위해서는, 해당 클래스는 Comparable 인터페이스를 구현해야 한다. 그리고 compareTo 메서드에 비교 구문을 작성하자.


public class Coffee implements Comparable<Coffee> {

    public final Long id;

    public final Integer price;

    public final String name;

    public Coffee(Long id,Integer price, String name) {

        this.id = id;

        this.price = price;

        this.name = name;

    }

    @Override

    public String toString() {

        return String.format("%s:%d:%d", name, id, price);

    }

    @Override

    public int compareTo(Coffee o) {

        return id.compareTo(o.id);

    }

}


String, Long, Integer 등의 많은 기본 자바 클래스는 Comparable 인터페이스를 구현한다. 


어쩃든, 아래와 같이 Collections.sort 를 실행하면 기본 id필드 기준으로 정렬이 된다. 


List<Coffee> coffeeList = Arrays.asList(

            new Coffee(33L,1200, "모카"),

            new Coffee(24L,1100, "라떼"),

            new Coffee(27L,900, "아메리카노"),

            new Coffee(23L,1300, "차이티라떼")

        );      

        //기본 정렬

        Collections.sort(coffeeList);

        System.out.println(coffeeList);


만약, 커피 이름순으로 정렬하고 싶다면 아래와 같이 구현하면 된다. 


//3. Coffee Name 순으로 정렬
Collections.sort(coffeeList, new Comparator<Coffee>() {
    @Override
    public int compare(Coffee o1, Coffee o2) {
        return o1.name.compareTo(o2.name);
    }
});


해당 소스는 람다식으로 간략하게 한줄로 짤수 있다. 

Collections.sort(coffeeList, (o1, o2) -> o1.name.compareTo(o2.name));

Collections.sort(coffeeList, Comparator.comparing(o -> o.name));




Set


Set 은 중복된 요소를 포함할 수 없다. List 와는 다르게 인덱스를 사용하지 않기 때문에, 인덱스 매개변수가 없다. Set 인터페이스의 메서드는 아래와 같다. 

int size()

boolean isEmpty()

boolean contains(Object o);

Iterator<E> iterator();

Object[] toArray();

<T> T[] toArray(T[] a);

boolean add(E e);

boolean remove(Object o);

boolean containsAll(Collection<?> c);

boolean addAll(Collection<? extends E> c);

boolean retainAll(Collection<?> c);

boolean removeAll(Collection<?> c);

void clear();

boolean equals(Object o);

int hashCode();

add, clear, contains, isEmpty 등 기본 메서드 사용 방법에 대한 설명은 생략한다.


Set 인터페이스를 구현한 클래스는 아래와 같다 

HashSet

Linked HashSet

TreeSet


HashSet

해싱?이라고 불리는 로직을 사용하여 객체를 저장한다. 또한 기본적인 Set의 특징으로, 중복 데이터는 저장하지 않는다. 


Sorting

타입에 따라서 다르다. 

HashSet : 랜덤

LinkedHashSet : 입력 순서대로

TreeSet : comparator에 의해서


//1. HashSet 랜덤 정렬
Set<String> set1 = new HashSet<>();
set1.add("이진우");
set1.add("김정우");
set1.add("김시은");
System.out.println("HashSet_랜덤정렬_"+set1);

//2. LinkedHashSet 입력 순서대로 정렬
Set<String> set2 = new LinkedHashSet<>();
set2.add("이진우");
set2.add("김정우");
set2.add("김시은");
System.out.println("LinkedHashSet_입력순서대로_"+set2);

//3. TreeSet Comparator에 의해서
Set<String> set3 = new TreeSet<>(Comparator.reverseOrder());
set3.add("이진우");
set3.add("김정우");
set3.add("김시은");
System.out.println("TreeSet_Comparator_"+set3);



Stream API 를 사용하여, List 로 변환


Set<String> set = new HashSet<>();
set.add("김시은");
set.add("이진우");
set.add("김정우");
List<String> list = set.stream()
        .collect(Collectors.toList());
System.out.println(list);





Map


Map 은 Key 와 Value 로 구성된 객체를 저장한다. 키는 중복 저장될 수 없다. 기본 메서드는 아래와 같다. 

int size();

boolean isEmpty();

boolean containsKey(Object key);

boolean containsValue(Object value);

V get(Object key);

V put(K key, V value);

V remove(Object key);

void putAll(Map<? extends K, ? extends V> m);

void clear();

Set<K> keySet();

Collection<V> values();

Set<Map.Entry<K, V>> entrySet();

boolean equals(Object o);

int hashCode();

V getOrDefault(Object key, V defaultValue)

void forEach(BiConsumer<? super K, ? super V> action)

void replaceAll(BiFunction<? super K, ? super V, ? extends V> function)

V putIfAbsent(K key, V value)

boolean remove(Object key, Object value)

boolean replace(K key, V oldValue, V newValue)

V replace(K key, V value)

V computeIfAbsent(...

V computeIfPresent(...

V compute(...

V merge(...


구현하는 클래스는 아래와 같다. 

HashMap

TreeMap

LinkedHashMap

Hashtable


HashMap 기본 메서드

HashMap 선언
Map<KeyType, ValueType> maps = new HashMap<KeyType, ValueType>();

값 추가
maps.put("key", value);

조회
map.get("key");

key 체크
mpas.containsKey("key");

value 체크
maps.containsValue(value);

forEach
maps.forEach((key, value) -> 생략..);


HashMap Iterating

Keys
for(Integer key : maps.keySet()){

Values
for(String value : maps.values()){

Entries
for(Map.Entry<Integer, String> entry : maps.entrySet()){


HashMap에 대한 자세한 내용은 아래 링크를 참고하자. 

http://d2.naver.com/helloworld/831311


LinkedHashMap 

입력된 순서를 기억하는 HashMap이다. 아래와 같이 하면 입력한 순서대로 출력하는 것을 확인할 수 있다. 물론 그냥 HashMap은 순서대로 출력되지 않는다. 


Map<String, String> linkedHashMap = new LinkedHashMap<>();
linkedHashMap.put("이름", "Eddy.Kim");
linkedHashMap.put("나이", "비밀");
linkedHashMap.put("업종", "포털");
linkedHashMap.put("사는곳", "서울");

linkedHashMap.forEach((key, value) -> System.out.println(key + value));


아래 링크에서 좋은 내용을 참고할수 있다.

http://www.baeldung.com/java-linked-hashmap


LinkedHashMap , Json 으로의 변환 1 - jackson 라이브러리

LinkedHashMap 객체를 Json으로 변환해보자. 빠른 테스트를 위해서 스프링부트에서 테스트 한다. 스프링부트 2.0.1 에서는 jackson 라이브러리에 대한 스타터를 제공한다. 


dependencies {  
   compile('org.springframework.boot:spring-boot-starter-json:2.0.1.RELEASE') 
}


아래와 같이 LinkedHashMap 객체를 Json String 으로 변환할 수 있다. jackson 라이브러리는 LinkedHashMap 의 순서를 보장한다. 


ObjectMapper mapper = new ObjectMapper();
try{
   System.out.println(mapper.writeValueAsString(linkedHashMap));

   //{"이름":"Eddy.Kim","나이":"비밀","업종":"포털","사는곳":"서울"}  

   //순서 보장
}
catch (Exception ex){
}



LinkedHashMap , Json 으로의 변환 2 - google gson


//build.gradle 디펜던시 추가   -- compile('com.google.code.gson:gson:2.8.3')


Gson gson = new Gson();
String json = gson.toJson(linkedHashMap, LinkedHashMap.class);
System.out.println(json);


[참고]Gson vs Jackson 

Gson 과 Jackson 모두 직렬화하기 좋고, 사용이 간단하고 문서화가 잘 되어있다. Gson 는 단순하여 사용하기 좋고, 역직렬화시 Java 엔티티에 엑세스 할 필요가 없다. Jackson 은 SpringBoot 에서 스타터를 지원해주고, 광범위한 주석을 지원한다. 아래 링크를 꼭 참고하자. 

http://www.baeldung.com/jackson-vs-gson


기타 읽을만한 글


Java Collections 에 대한 질문과 답변을 정리한 글이다.

http://www.baeldung.com/java-collections-interview-questions


필자가 자주 보는 Java 무료 PDF 자료이다. 해당 글의 일부 내용도 아래 PDF 를 참고하였다. 

http://book.goalkicker.com/JavaBook/




정리


Collection 에 대해서 간략하게 정리하였다. 해당 글은 필자가 공부하기 위해 작성한 글이다. 일부 잘못된 내용도 있을 수 있으니 혹시라도 읽고 의견이 있다면 댓글로 피드백 남겨주길 바란다. 

브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari