이전 시간에 배열에 대해서 공부했다. 배열은 연관된 데이터를 관리하기 위한 수단이었다. 그런데 배열에는 몇가지 불편한 점이 있었는데 그 중의 하나가 한번 정해진 배열의 크기를 변경할 수 없다는 점이다. 이러한 불편함을 컬렉션즈 프래임워크를 사용하면 줄어든다.


ArrayList

package org.opentutorials.javatutorials.collection;
 
import java.util.ArrayList;
 
public class ArrayListDemo {
 
    public static void main(String[] args) {
        String[] arrayObj = new String[2];
        arrayObj[0] = "one";
        arrayObj[1] = "two";
        // arrayObj[2] = "three"; 오류가 발생한다.
        for(int i=0; i<arrayObj.length; i++){
            System.out.println(arrayObj[i]);
        }
         
        ArrayList al = new ArrayList();
        al.add("one");
        al.add("two");
        al.add("three");
        for(int i=0; i<al.size(); i++){
            System.out.println(al.get(i));
        }
    }
 
}

 

아래 코드처럼 배열은 그 크기를 한번 지정하면 크기보다 많은 수의 값을 저장할 수 없다.

String[] arrayObj = new String[2];
arrayObj[0] = "one";
arrayObj[1] = "two";
// arrayObj[2] = "three"; 오류가 발생한다.

 

 

하지만 ArrayList는 크기를 미리 지정하지 않기 때문에 얼마든지 많은 수의 값을 저장할 수 있다.

ArrayList al = new ArrayList();
al.add("one");
al.add("two");
al.add("three");

 

ArrayList는 Collections Framework 안에 있는 기능 중 하나이다.

 

ArrayList는 배열과는 사용방법이 조금 다르다. 배열의 경우 값의 개수를 구할 때 .length를 사용했지만 ArrayList는 메소드 .size를 사용한다. 또한, 특정한 값을 가져올 때 배열은 [인덱스 번호]를 사용했지만 컬렉션은 .get(인덱스 번호)를 사용한다.

for(int i=0; i<al.size(); i++){
    System.out.println(al.get(i));
}

 

 

그런데 ArrayList를 사용할 때 주의할 점이 있다. 위의 반복문을 아래처럼 바꿔보자.

for(int i=0; i<al.size(); i++){
    String val = al.get(i);
    System.out.println(val);
}

위의 코드는 컴파일 오류가 발생한다.

add는 어떠한 데이터 타입이라도 다 가져올 수 있다.  그러나 ArrayList의 메소드 add의 입장에서는 인자로 어떤 형태의 값이 올지 알 수 없다. 그렇기 때문에 모든 데이터 타입의 조상인 Object 형식으로 데이터를 받고 있다. 그러므로 one, two, three는 Object라는 데이터타입으로 저장되어 있다. String 데이터 타입으로 저장하려고 하니까 에러가 생기는 것이다. 

 

따라서 ArrayList 내에서 add를 통해서 입력된 값은 Object의 데이터 타입을 가지고 있고, get을 이용해서 이를 꺼내도 Object의 데이터 타입을 가지고 있게 된다. 그래서 위의 코드는 아래와 같이 String으로 형변환을 해야 한다.

for(int i=0; i<al.size(); i++){
    String val = (String)al.get(i);
    System.out.println(val);
}

 

 

get의 리턴값을 문자열로 형변환하고 있다. 원래의 데이터 타입이 된 것이다.

그런데 위의 방식은 옛날 방식이다. 이제는 아래와 같이 제네릭을 사용해야 한다.

ArrayList<String> al = new ArrayList<String>();
al.add("one");
al.add("two");
al.add("three");
for(int i=0; i<al.size(); i++){
    String val = al.get(i);
    System.out.println(val);
}

이제 get()을 통해 가져오는 데이터 타입은 Object가 아니라 String이 된다.

 

제네릭을 사용하면 ArrayList 내에서 사용할 데이터 타입을 인스턴스를 생성할 때 지정할 수 있기 때문에 데이터를 꺼낼 때(String val = al.get(i);) 형변환을 하지 않아도 된다.


Collections Framework 이란?

그럼 이제부터 컬렉션즈 프래임워크가 무엇인가 본격적으로 알아보자. 컬렉션즈 프래임워크라는 것은 다른 말로는 컨테이너라고도 부른다. 즉 값을 담는 그릇이라는 의미이다. 그런데 그 값의 성격에 따라서 컨테이너의 성격이 조금씩 달라진다. 자바에서는 다양한 상황에서 사용할 수 있는 다양한 컨테이너를 제공하는데 이것을 컬렉션즈 프래임워크라고 부른다. ArrayList는 그중의 하나다.

 

 

위의 그림은 컬렉션즈 프래임워크의 구성을 보여준다. Collection과 Map이라는 최상위 카테고리가 있고, 그 아래에 다양한 컬렉션들이 존재한다. 그럼 구체적인 컬렉션즈 프래임워크 클래스들을 살펴보자.

 

 

ArrayList를 찾아보자. Collection-List에 속해있다. ArrayList는 LIst라는 성격으로 분류되고 있는 것이다. List는 인터페이스이다. 그리고 List 하위의 클래스들은 모두 List 인터페이스를 구현하기 때문에 모두 같은 API를 가지고 있다. 클래스의 취지에 따라서 구현방법과 동작방법은 다르지만 공통의 조작방법을 가지고 있는 것이다. 익숙한 ArrayList를 바탕으로 나머지 컬렉션들의 성격을 파악해보자.


List 와 Set의 차이

List와 Set의 차이점은 List는 중복을 허용하고, Set은 허용하지 않는다.

package org.opentutorials.javatutorials.collection;
 
import java.util.ArrayList;
import java.util.HashSet;
 
import java.util.Iterator;
 
public class ListSetDemo {
 
    public static void main(String[] args) {
        ArrayList<String> al = new ArrayList<String>();
        al.add("one");
        al.add("two");
        al.add("two");
        al.add("three");
        al.add("three");
        al.add("five");
        System.out.println("array");
        Iterator ai = al.iterator();
        while(ai.hasNext()){
            System.out.println(ai.next());
        }
         
        HashSet<String> hs = new HashSet<String>();
        hs.add("one");
        hs.add("two");
        hs.add("two");
        hs.add("three");
        hs.add("three");
        hs.add("five");
        Iterator hi = hs.iterator();
        System.out.println("\nhashset");
        while(hi.hasNext()){
            System.out.println(hi.next());
        }
    }
 
}

결과는 아래와 같다.

array
one
two
two
three
three
five

hashset
two
five
one
three

 

List : 입력한 모든 값들이 저장된다. 순서 저장 O

Set : 입력한 값들의 고유한 값, 즉 하나의 값만 저장된다. 순서 저장 X

 


Set

Set은 한국어로 집합이라는 뜻이다. 여기서의 집합이란 수학의 집합과 같은 의미다. 수학에서의 집합도 순서가 없고 중복되지 않는 특성이 있다는 것이 기억날 것이다. (기억나지 않아도 상관없다) 수학에서 집합은 교집합(intersect), 차집합(difference), 합집합(union)과 같은 연산을 할 수 있었다. Set도 마찬가지다.

 

아래와 같이 3개의 집합 hs1, hs2, hs3이 있다고 하자. h1은 숫자 1,2,3으로 이루어진 집합이고, h2는 3,4,5 h3는 1,2로 구성되어 있다. set의 API를 이용해서 집합 연산을 해보자.

 

package org.opentutorials.javatutorials.collection;
 
import java.util.ArrayList;
import java.util.HashSet;
 
import java.util.Iterator;
 
public class SetDemo {
 
    public static void main(String[] args) {
        HashSet<Integer> A = new HashSet<Integer>();
        A.add(1);
        A.add(2);
        A.add(3);
         
        HashSet<Integer> B = new HashSet<Integer>();
        B.add(3);
        B.add(4);
        B.add(5);
         
        HashSet<Integer> C = new HashSet<Integer>();
        C.add(1);
        C.add(2);
         
        System.out.println(A.containsAll(B)); // false
        System.out.println(A.containsAll(C)); // true
         
        A.addAll(B);    // 합집합
        //A.retainAll(B); // 교집합
        //A.removeAll(B); // 차집합
         
        Iterator hi = A.iterator();
        while(hi.hasNext()){
            System.out.println(hi.next());
        }
    }
 
}

부분집합 (subset)

System.out.println(A.containsAll(B)); // false
System.out.println(A.containsAll(C)); // true

합집합(union)

A.addAll(B);

교집합(intersect)

A.retainAll(B);

차집합(difference)

A.removeAll(B);


 인터페이스 

 클래스 

 

Set 은 Collection과 동일한 API를 제공한다.

List 는 순서기반의 method API를 가지고 있다.

 


 iterator : 반복자 

import java.util.HashSet;
import java.util.Iterator;

public class SetDemo {
    public static void main(String[] args) {
        HashSet<Integer> A = new HashSet<Integer>();
        A.add(1);
        A.add(2);
        A.add(3);

        Iterator hi = A.iterator();
        while(hi.hasNext()){
                System.out.println(hi.next());
        }
    }
}

HashSet은 Collection interface를 구현하고 있는 클래스이기 때문에 당연히 iterator를 쓸 수 있다.

A는 1,2,3 이 들어있다.

A.iterator(); 를 하게 되면

iterator라는 메쏘드를 통해서 Iterator라는 데이터 타입을 가지고 있는 인스턴스를 hi라는 변수를 만든다.

 

이 상태에서 hi.hasNext()를 호출하게 되면

hi.iterator 안에 들어가 있는 값들이 존재하는지를 확인한다.

값이 1, 2, 3  총 3개가 있으므로 while문 안에는 true가 된다.

그 후 로직이 실행되고 hi.next()를 호출하게 되면 hi에 있는 값중 하나를 리턴해준다.

그리고 hi안에 있는 1,2,3 중 1이 사라지게 된다.

 

다시 hi.hasNext()를 호출하고

hi.next()를 호출하고 2를 가져오면 hi 안의 2가 사라지게 된다.

 

다 가져오면 while문 안이 false가 되고 멈춘다.

 

이때 hi는 iterator를 통해서 참조값만을 가지고 있기 때문에 original 데이터 값이 사라지지 않는다는 것을 기억해야 한다.

 

import java.util.ArrayList;
import java.util.Iterator;

public class SetDemo {
    public static void main(String[] args) {
        ArrayList<Integer> A = new ArrayList<Integer>();
        A.add(1);
        A.add(2);
        A.add(3);

        Iterator hi = A.iterator();
        while(hi.hasNext()){
                System.out.println(hi.next());
        }
    }
}

ArrayList로 바꿔도 에러가 나지 않는 이유는 둘다 Collection interface를 구현하고 있고,

iterator라는 메쏘드를 통해서 반복자라는 것을 제공하도록 강제되어있기 때문이다.

 

import java.util.HashSet;
import java.util.Collection;
import java.util.Iterator;

public class SetDemo {
    public static void main(String[] args) {
        Collection<Integer> A = new HashSet<Integer>();
        A.add(1);
        A.add(2);
        A.add(3);

        Iterator hi = A.iterator();
        while(hi.hasNext()){
                System.out.println(hi.next());
        }
    }
}

Hashset이나 ArrayList나 Collection이라는 interface를 구현하고 있고,

Collection이라는 interface 안에는 iterator라는 공통적인 API가 존재하기 때문에 데이타입을 Collection으로 사용해도 된다.

따라서 가급적이면 상위 데이터 타입을 사용해야 한다.

 


 Map 

 

이번에는 Map 컬렉션에 대해서 알아보자.

Map 컬렉션은 key와 value의 쌍으로 값을 저장하는 컬렉션이다.

 

key 값은 중복될 수 없다.

value 값은 중복될 수 있다.

 

package org.opentutorials.javatutorials.collection;
 
import java.util.*;
 
public class MapDemo {
 
    public static void main(String[] args) {
        HashMap<String, Integer> a = new HashMap<String, Integer>();
        a.put("one", 1);
        a.put("two", 2);
        a.put("three", 3);
        a.put("four", 4);
        System.out.println(a.get("one"));
        System.out.println(a.get("two"));
        System.out.println(a.get("three"));
         
        iteratorUsingForEach(a);
        iteratorUsingIterator(a);
    }
     
    static void iteratorUsingForEach(HashMap map){
        Set<Map.Entry<String, Integer>> entries = map.entrySet();
        for (Map.Entry<String, Integer> entry : entries) {
            System.out.println(entry.getKey() + " : " + entry.getValue());
        }
    }
     
    static void iteratorUsingIterator(HashMap map){
        Set<Map.Entry<String, Integer>> entries = map.entrySet();
        Iterator<Map.Entry<String, Integer>> i = entries.iterator();
        while(i.hasNext()){
            Map.Entry<String, Integer> entry = i.next();
            System.out.println(entry.getKey()+" : "+entry.getValue());
        }
    }
 
}

 

put이라는 메쏘드는 map만 가지고 있다.

put( key, value ); // key에 value를 넣는다.
get( key ) ; // key의 value 값을 리턴한다.


 

'📌 java > java' 카테고리의 다른 글

java - TreeSet  (0) 2020.06.13
java - HashMap  (0) 2020.06.12
java 용어 요약  (0) 2020.06.02
java - Generic  (0) 2020.06.02
java - Anonymous class  (0) 2020.05.16
복사했습니다!