티스토리 뷰

PL/JAVA

[JAVA] 자바 - 제네릭과 컬렉션

poopooreum 2024. 6. 7. 21:22
반응형

✏️ 컬렉션

컬렉션(Collection)은 안드로이드를 비롯한 자바 프로그램을 작성하는데 뺴놓을 수 없는 중요한 도구이다. 자바의 JDK는 소위 자료 구조 과목에서 배운 많은 자료 구조들을 컬렉션으로 만들어 제공하고 컬렉션은 제네릭이라는 기법으로 구현되어 있기 때문에 제네릭에 대한 공부도 필요하다. 컬렉션은 배열이 가진 고정 크기의 단점을 극복하기 위해 객체들을 쉽게 삽입, 삭제, 검색할 수 있는 가변 크기의 컨테이너(container)이다.

컬렉션은 제네렉(generics)이라는 기법으로 만들어져 있다. 컬렉션 클래스의 이름에는 <E>, <K>, <V> 등이 항상 포함된다. 이들은 타입 매개 변수라고 하는데, 특정 타입만 다루지 않고 여러 종류의 타입으로 변신할 수 있도록, 컬렉션을 일반화시키기 위해 <E>를 사용한다. 따라서 <E>를 일반화시킨 타입 혹은 제네릭 타입(generics type)이라고 부른다. 그리고 컬렉션의 요소는 객체들만 가능하여 int, char, double 등의 기본 타입의 데이터는 컬렉션의 요소로 사용할 수 없다.

 

 

✏️ 제네릭

제네릭은 JDK 1.5 버전부터 도입되었다. 제네렉인 모든 종류의 타입을 다룰 수 있도록, 클래스나 메소드를 타입 매개변수(generics type)를 이용하여 선언하는 기법이다. C++의 템플릿과 동일하다고 생각하면 될 것 같다. 제네릭 타입의 매개변수는 다음과 같다.

  • E : Element를 의미하며 컬렉션에서 요소임을 나타낸다
  • T : Type을 의미
  • V : Value를 의미
  • K : Key를 의미

 

✏️ Vector<E>

Vector<E>는 배열을 가변 크기로 다룰 수 있게 하고, 객체의 삽입, 삭제, 이동이 쉽도록 구성한 컬렉션 클래스이다. 벡터에 삽입되는 요소의 개수에 따라 자동으로 크기를 조절하고, 요소의 삽입과 삭제에 따라 자동으로 요소들의 자리를 이동한다. 아래 표는 Vector<E> 컬렉션의 주요 메소드이다. 그리고 Vector<E>자동 박싱자동 언박싱이 가능하다.

메소드 설명
boolean add(E element) 벡터의 맨 뒤에 element 추가
void add(int index, E element) 인덱스 indexelement를 삽입
int capacity() 벡터의 현재 용량 리턴
boolean addAll(Collection<? extends E> c) 컬렉션 c의 모든 요소를 벡터의 맨 뒤에 추가
void clear() 벡터의 모든 요소 삭제
boolean contains(Object o) 벡터가 지정된 객체 o를 포함하고 있으면 true 리턴
E elementAt(int index) 인덱스 index의 요소 리턴
E get(int index) 인덱스 index의 요소 리턴
int indexOf(Object o) o와 같은 첫 번째 요소의 인덱스 리턴, 없으면 -1 리턴
boolean isEmpty() 벡터가 비어 있으면 true 리턴
boolean remove(Object o) 객체 o와 같은 첫 번째 요소를 벡터에서 삭제
void removeAllElements() 벡터의 모든 요소를 삭제하고 크기를 0으로 만듦
int size() 벡터가 포함하는 요소의 개수 리턴
Object[] toArray() 벡터의 모든 요소를 포함하는 배열 리턴
E remove(int index) 인덱스 index의 요소 삭제

 

아래는 Vector<E>를 활용한 예제이다.

package ex;
import java.util.*;
public class VectorEx {
    public static void main(String[] args) {
        Vector<Integer>  v = new Vector<Integer>();
        v.add(5); // 5 삽입
        v.add(4); // 4 삽입
        v.add(-1); // -1 삽입

        v.add(2,100); // 4와 -1의 사이에 100 삽입

        System.out.println("v.size() = " + v.size()); // 크기 3
        System.out.println("v.capacity() = " + v.capacity()); // 벡터 용량

        for(int i=0;i<v.size();i++){
            int n= v.get(i); // 벡터의 i번째 정수
            System.out.println("n = " + n);
        }

        int sum=0;
        for(int i=0;i<v.size();i++){
            int n = v.elementAt(i);
            sum+=n;
        }
        System.out.println("sum = " + sum);
    }
}

/*
v.size() = 4
v.capacity() = 10
n = 5
n = 4
n = 100
n = -1
sum = 108
*/

 

 

 

✏️ ArrayList<E>

ArrayList<E>는 가변 크기의 배열을 구현한 컬렉션 클래스로서 경로명은 java.util.ArrayList이며, Vector 클래스와 거의 동일하다. 다른 점으로는 ArrayList는 스레드 간에 동기화를 지원하지 않기 때문에, 다수의 스레드가 동시에 ArrayList에 요소를 삽입하거나 삭제할 때 데이터가 훼손될 우려가 있다. 하지만 멀티스레드 동기화를 위한 시간 소모가 없기 때문에, ArrayListVector보다 속도가 빨라, 단일 스레드 운용에는 효과적이다. 아래 표는 ArrayList 클래스의 주요 메소드이다.

메소드 설명
boolean add(E element) ArrayList의 맨 뒤에 element 추가
void add(int index, E element) 인덱스 indexelement를 삽입
boolean addAll(Collection<? extends E> c) 컬렉션 c의 모든 요소를 벡ArrayList의 맨 뒤에 추가
void clear() ArrayList의 모든 요소 삭제
boolean contains(Object o) ArrayList가 지정된 객체 o를 포함하고 있으면 true 리턴
E elementAt(int index) 인덱스 index의 요소 리턴
E get(int index) 인덱스 index의 요소 리턴
int indexOf(Object o) o와 같은 첫 번째 요소의 인덱스 리턴, 없으면 -1 리턴
E remove(int index) index 인덱스의 요소 삭제
boolean remove(Object o) o와 같은 첫 번째 요소를 ArrayList에서 삭제
int size() ArrayList가 포함하는 요소의 개수 리턴
Object[] toArray() ArrayList의 모든 요소를 포함하는 배열 리턴

 

아래는 ArrayList를 활용한 예제이다.

package ex;
import java.util.*;
public class ArrayListEx {
    public static void main(String[] args) {
        ArrayList<String> a = new ArrayList<>();

        Scanner s = new Scanner(System.in);
        for(int i=0; i<4;i++){
            String str = s.next();
            a.add(str);
        }

        for(int i=0;i<a.size();i++){
            String name = a.get(i);
            System.out.println("name = " + name);
        }

        int longLength=0;
        for(int i=0; i<a.size();i++){
            if(a.get(longLength).length()<a.get(i).length())
                longLength = i;
        }
        System.out.println("가장 긴 이름은 = " + a.get(longLength));
        s.close();
    }
}
/*
pooreum
jungpooreum
pooreumjeong
podfjh
name = pooreum
name = jungpooreum
name = pooreumjeong
name = podfjh
가장 긴 이름은 = pooreumjeong
*/

 

 

✏️ Iterator

Vector, ArrayList, Set과 같이 요소가 순서대로 저장된 컬렉션에서 요소를 순차 검색할 때 java,util 패키지 Iterator<E> 인터페이스를 사용하면 편리하다. Iterator의 메소드는 아래와 같다.

메소드 설명
booleanhasNext() 방문할 요소가 남아 있으면 true 리턴
E next() 다음 요소 리턴
void remove() 마지막으로 리턴된 요소 제거

 

아래는 Iterator<E>를 이용한 예제이다.

package ex;
import java.util.*;
public class IteratorEx {
    public static void main(String[] args) {
        Vector<Integer> v = new Vector<Integer>();
        v.add(5);
        v.add(4);
        v.add(-1);
        v.add(2,100);
        
        Iterator<Integer> it = v.iterator();
        while(it.hasNext()){
            int n=it.next();
            System.out.println(n);
        }
        
        int sum=0;
        it = v.iterator();
        while(it.hasNext()){
            int n = it.next();
            sum+=n;
        }
        System.out.println("sum = " + sum);
    }
}
/*
5
4
100
-1
sum = 108
*/

 

 

✏️ HashMap<K, V>

HashMap<K, V> 컬렉션은 경로명이 java.util.HaspMap이며 키(key)와 값(value)의 쌍으로 구성되는 요소를 다룬다. K는 키로 사용할 데이터 타입은, V는 값으로 사용할 데이터 타입의 타입매개변수이다. 해쉬맵은 요소의 삽입, 삭제 시간이 매우 빠르다. 요소의 삽입 삭제 시 다른 요소들의 위치 이동이 필요가 없기 때문이다. 또한, 요소 검색은 더욱 빠른데 해시맵의 get(key) 메소드가 호출되면 해시 함수가 key가 저장된 위치를 단번에 찾아내므로 요소를 하나하나씩 비교하는 시간낭비가 없어진다. 다만 해시맵은 인덱스로 접근할 수 없기 때문에 빠른 삽입과 검색이 필요한 응용에만 적합하다. 해쉬맵의 메소드는 아래와 같다.

메소드 설명
void clear() 해시맵의 모든 요소 삭제
boolean containsKey(Object key) 지정된 키(key)를 포함하고 있으면 true 리턴
boolean containsValue(Object value) 지정된 값(value)에 일치하는 키가 있으면 true 리턴
V get(Object Key) 지정된 키(key)의 값 리턴, 키가 없으면 null 리턴
boolean isEmpty() 해시맵이 비어 있으면 true 리턴
Set<K> keySet() 해시맵의 모든 키를 담은 Set<K> 컬렉션 리턴
V put(K key, V value) keyvalue 쌍을 해시맵에 저장
V remove(Object key) 지정된 키(key)를 찾아 키와 값 모두 삭제
int size() 해시맵에 포함된 요소의 개수 리턴

 

아래는 해시맵을 활용한 예제 코드이다.

package ex;
import java.util.*;
public class HashMapEx {
    public static void main(String[] args) {
        HashMap<String, String> dic = new HashMap<>();

        dic.put("baby","아기");
        dic.put("love","사랑");
        dic.put("apple","사과");

        Scanner s = new Scanner(System.in);
        while(true){
            System.out.print("찾고 싶은 단어는?");
            String str = s.next();
            if(str.equals("exit")) {
                System.out.println("종료");
                break;
            }

            String inp = dic.get(str);
            if(inp==null)
                System.out.println("없는 단어");
            else
                System.out.println(inp);
        }
    }
}

/*
찾고 싶은 단어는? apple
사과
찾고 싶은 단어는? babo
없는 단어
찾고 싶은 단어는? exit
종료
*/

 

 

✏️ LinkedList<E>

LinkedList<E>List<E> 인터페이스를 구현한 클래스로서 java.util 패키지에 존재한다. LinkedList 요소들을 양방향으로 연결하여 관리한다는 점을 제외하면 Vector, ArrayList와 거의 같다. 값을 추가하는 add() 메소드와 값을 가져올 수 있는 get()메소드가 있으며, 맨 앞과 맨 뒤를 가리키는 head, tail 레퍼런스를 가리키고 있어 맨 앞이나 맨 뒤, 중간에 요소의 삽입이 가능하며 인덱스를 이용하여 요소에 접근할 수 있다.

 

 

✏️ Collections 클래스 활용

  • sort() : 컬렉션에 포함된 요소들의 정렬
  • reverse() : 요소를 반대 순으로 정렬
  • max(), min() : 요소들의 최댓값과 최솟값 찾아내기
  • binarySearch() : 이진 검색
package ex;
import java.util.*;
public class CollectionEx {
    
    static void printList(LinkedList<String> l){
        
        Iterator<String> it = l.iterator();
        while(it.hasNext()){
            String e = it.next();
            String sep;
            if(it.hasNext())
                sep = "->";
            else
                sep = "\n";
            System.out.print(e+sep);
        }
    }

    public static void main(String[] args) {
        LinkedList<String>myList = new LinkedList<>();
        myList.add("트랜스포머");
        myList.add("스타워즈");
        myList.add("매트릭스");
        myList.add(0,"터미네이터");
        myList.add(2,"아바타");
        
        Collections.sort(myList);
        printList(myList);
        
        Collections.reverse(myList);
        printList(myList);
        
        int index = Collections.binarySearch(myList,"아바타")+1;
        System.out.println("index = " + index);
    }

}

/*
매트릭스->스타워즈->아바타->터미네이터->트랜스포머
트랜스포머->터미네이터->아바타->스타워즈->매트릭스
index = 3
*/

 

 

✏️ 제네릭 클래스

제네릭 클래스를 작성하는 방법은 기존의 클래스 작성 방법과 유사하고, 클래스 이름 다음에 일반화된 타입의 매개변수를 <와 >사이에 추가한다는 차이점이 있다. 아래 예시 코드를 보면서 알아보자

// 제네릭 클래스 작성
public class Myclass<T>{ // 제네릭 클래스 Myclass 타입 매개변수 T
    T val; // 변수 val의 타입은 T
    void set(T a){
    	val=a; // T 타입의 값을 val에 저장
    }
    T get(){
    	return val; // T 타입의 값 val 리턴
    }
}
// 제네릭 클래스에 대한 레퍼런스 변수 선언
Myclass<String> S; // <T>를 String으로 구체화
List<Integer> li; // <E>를 Integer로 구체화
Vector<String> vs; // <E>를 String으로 구체화
// 제네릭 객체 생성 - 구체화(Specialization)
Myclass<String> s = new Myclass<String>(); // 제네릭 타입 T를 String으로 구체화
s.set("Hello");
System.out.println(s.get()); // Hello 출력

Myclass<Ineger> n = new Myclass<Integer>(); // 제네릭 타입 T를 Integer로 구체화
n.set(5);
System.out.println(n.get());
// 제네릭 구체화 주의점
Vector<int> vi = new Vector<int>(); // 컴파일 오류, int는 사용 불가

public class MyVector<E>{
    E create(){
    	E a = new E(); // 제네릭 타입의 객체 생성 불가
        return a;
    }
}

 

예제 코드

package ex;
class GStack<T>{
    int tos;
    Object [] stck;
    public GStack(){
        tos=0;
        stck = new Object[10]; // 만약 stck = new T[10]; 으로 선언하면 컴파일 오류
    }
    public void push(T item){
        if(tos==10)
            return;
        stck[tos]=item;
        tos++;
    }
    public T pop(){
        if(tos==0)
            return null;
        tos--;
        return (T)stck[tos]; // 타입 매개변수 타입으로 캐스팅
    }
}

public class MyStack {
    public static void main(String[] args) {
        GStack<String> stringStack = new GStack<>();

        stringStack.push("seoul");
        stringStack.push("busan");
        stringStack.push("LA");

        for(int i=0;i<3;i++)
            System.out.println(stringStack.pop());

        GStack<Integer> intStack = new GStack<>();
        intStack.push(1);
        intStack.push(3);
        intStack.push(5);

        for(int i=0;i<3;i++)
            System.out.println(intStack.pop());
    }
}

/*
LA
busan
seoul
5
3
1
*/

 

제네릭의 특징

  • 제네릭 클래스 또는 인터페이스 타입의 배열을 선언할 수 없다.
  • 클래스의 일부 메소드만 제네릭으로 구현할 수도 있다.
  • 동적으로 타입이 결정되지 않고 컴파일 시에 타입이 결정되므로 보다 안전한 프로그래밍 가능
  • 런타임 타입 충돌 문제 방지
  • 개발 시 타입 캐스팅 절차 불필요
  • ClassCastException 방지

 

반응형
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/07   »
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
글 보관함