Generic과 컬렉션(Collection)

제민·2024년 7월 31일

Java 개념 공부

목록 보기
20/21
post-thumbnail

Generic

Generic은 클래스나 메서드를 선언할 때, 데이터 타입을 미리 정하지 않고 나중에 사용할 때 지정할 수 있도록 하는 방법입니다. 이를 통해 코드의 재사용성을 높이고, 타입 안전성을 확보할 수 있습니다.

Generic 사용 예제

package m.generic;

public class Run {
    public static void main(String[] args) {
        // 객체를 생성하는 시점에 타입을 지정
        Box<Integer> aBox = new Box<>();

        Integer[] arr = new Integer[10];
        aBox.setObArr(arr);
       
        aBox.getObArr()[0] = 10;
       
        System.out.println(aBox.toString());
    }
}

package m.generic;

import java.util.Arrays;

public class Box<T> {
    private T[] obArr;
    private int size;
   
    public Box() {
        super();
    }

    public Box(T[] obArr, int size) {
        super();
        this.obArr = obArr;
        this.size = size;
    }

    public T[] getObArr() {
        return obArr;
    }

    public void setObArr(T[] obArr) {
        this.obArr = obArr;
    }

    public int getSize() {
        return size;
    }

    public void setSize(int size) {
        this.size = size;
    }

    @Override
    public String toString() {
        return "Box [obArr=" + Arrays.toString(obArr) + ", size=" + size + "]";
    }
}

위 코드에서는 Box라는 클래스에 Generic 타입 <T>를 사용하여 다양한 타입의 배열을 처리할 수 있게 했습니다.
Box 클래스는 Integer 배열을 다룰 수 있으며, 다른 타입의 배열도 사용할 수 있습니다.

Collection

Collection은 Java에서 데이터 구조를 다루기 위해 제공되는 프레임워크입니다. 이를 통해 데이터를 효율적으로 추가, 삭제, 조회, 정렬, 수정할 수 있습니다.

Collection의 장점

  • 크기 지정 불필요: 배열과 달리, Collection은 크기를 미리 지정할 필요가 없습니다. 데이터가 추가되면 자동으로 크기가 조절됩니다.
  • 다양한 타입 저장 가능: 한 Collection에 여러 타입의 데이터를 저장할 수 있습니다. 단, 객체만 저장 가능합니다.
  • 알고리즘 제공: 데이터를 추가하거나 삭제할 때, 복잡한 알고리즘을 직접 구현할 필요 없이 메서드를 호출하여 처리할 수 있습니다.

ArrayList 사용 예제

아래는 ArrayList를 사용하여 데이터를 관리하는 예제입니다.

package o.collection.list;

import java.util.ArrayList;
import java.util.List;

public class ListRun {

    public static void main(String[] args) {
        List list = new ArrayList(3);

        list.add(new Music("레이니데이", "파테코"));
        list.add(new Music("비가오는날에", "김경호"));
        list.add(new Music("팔레트", "아이유"));
        list.add("끝");

        System.out.println(list);

        list.add(1, new Music("사랑인가봐", "윤도현"));
        System.out.println(list);

        list.remove(1);
        System.out.println(list);

        list.set(2, new Music("사랑인가봐", "윤도현"));
        System.out.println(list);

        System.out.println(list.size());

        Music m = (Music) list.get(0);
        System.out.println(m);

        List sub = list.subList(1, 3);
        System.out.println(sub);

        list.addAll(sub);
        System.out.println(list);

        System.out.println(list.isEmpty());

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

        for (Object o : list) {
            System.out.println(o);
        }
    }
}

위 코드에서는 ArrayList를 사용하여 Music 객체를 추가, 삭제, 수정하는 방법을 보여줍니다. Collection을 사용하면 데이터 구조를 보다 효율적으로 관리할 수 있습니다.

Generic과 Collection 결합 예제

Generic과 Collection을 함께 사용하면, 명시된 타입의 객체만 저장하도록 제한할 수 있으며, 저장된 객체를 사용할 때 형변환이 필요 없게 됩니다.

package o.collection.list;

import java.util.ArrayList;
import java.util.List;

public class GenericListRun {

    public static void main(String[] args) {
        List<Music> list = new ArrayList<>();

        list.add(new Music("레이니데이", "파테코"));
        list.add(new Music("비가오는날에", "김경호"));
        list.add(new Music("팔레트", "아이유"));

        System.out.println(list);

        for (Music m : list) {
            System.out.println(m);
        }
    }
}

위 코드에서 List<Music>을 사용하여 Music 객체만 저장하도록 했습니다. 이를 통해 코드의 가독성과 안정성을 높일 수 있습니다.

Map

Map은 키(key)와 값(value)으로 구성되어 있으며, 키와 값은 모두 객체입니다. 키는 중복 저장을 허용하지 않고, 값은 중복 저장이 가능합니다. 키가 중복되는 경우, 기존에 있는 키에 해당하는 값을 덮어 씌웁니다. 구현 클래스로 HashMap, HashTable, LinkedHashMap, Properties, TreeMap이 있습니다.

HashMap 사용 예제

package o.collection.map;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Set;

public class MapRun {
	
	public static void main(String[] args) {
		HashMap hm = new HashMap();
		
		// 계층구조를 보면
		// List계열, Set계열의 클래스들은 collection을 구현한 클래스이다.
		// => 객체를 추가하고자 할 때 공통적으로 add메소드를 이용한다.
		
		// Map계열은 Collection을 구현할 클래스가 아니다.
		// => 객체를 추가하고자할 때 put메소드를 이용(key + value한쌍으로 담아야한다.)
		
		// 1. put(k,v) : map에 키 벨류 세트로 추가시켜주는 메소드
		hm.put("다이제", new Snack("초코맛", 1000));
		hm.put("칸초", new Snack("단맛", 500));
		hm.put("새우깡", new Snack("짠맛", 600));
		hm.put("아이셔", new Snack("신맛", 300));
		
		System.out.println(hm);
		// 저장되는 순서 유지 안됨! value값이 중복되어도 키값이 중복되지 않으면 잘 저장됨.
		
		hm.put("새우깡", new Snack("매운맛",700));
		System.out.println(hm);
		// 동일한 키값으로 다시 추가하는 경우 value값이 덮어 씌워진다. (중복된 키값이 공존할 수 없다. 키값이 식별자 역할을 한다.)
		
		// 2. get(Object key) : 해당 키값을 가지는 value값을 리턴해준다.
		System.out.println(hm.get("다이제"));
		
		// 3. size() : 담겨있는 객체들의 수
		System.out.println(hm.size());
		
		// 4. replace(key, value) => 해당 키값을 찾아서 다시 전달한 value값으로 수정
		hm.replace("새우깡", new Snack("아주 매운맛", 800));
		System.out.println(hm);
		
		// 5. remove(Object key) => 해당키값을 찾아서 그 벨류세트를 삭제시켜주는 메소드
		hm.remove("다이제");
		System.out.println(hm);
		
		// 전체객체에 대해서 접근하는 방법
		
		// for each 불가능
		// Iterator 반복자 불가능
		
		// Map의 key는 set과 매우 유사하다.
		// 1. key를 모아서 set자료구조의 형태로 받을 수 있다.
		Set keySet = hm.keySet();
		System.out.println(keySet);
		for(Object key : keySet) {
			System.out.println("키 : " + key + " 값 : " +hm.get(key));
		}
		
		// 2. entrySet() 이용하는 방법
		// 키벨류를 다가지고 있는 set타입
		Set entrySet = hm.entrySet();
		
		Iterator it = entrySet.iterator();
		
		while(it.hasNext()) { // 다음에 가져올 값이 있는지 확인
			Entry entry = (Entry)it.next(); // Entry객체안에 key, value값이 다 담겨져 있다.
			
			String key = (String)entry.getKey();
			Snack value = (Snack)entry.getValue();
			System.out.println("키 : " + key + " 값 : " + value);
		}
	}
}

Set

Set은 저장 순서가 유지되지 않고, 중복 객체도 저장하지 못하게 하는 자료 구조입니다. null도 중복을 허용하지 않기 때문에 1개의 null만 저장할 수 있습니다. 구현 클래스로 HashSet, LinkedHashSet, TreeSet이 있습니다.

HashSet

Set에 객체를 저장할 때 hash함수를 사용하여 처리 속도가

빠릅니다. 동일 객체뿐 아니라 동등 객체도 중복하여 저장하지 않습니다.

LinkedHashSet

HashSet과 거의 동일하지만 Set에 추가되는 순서를 유지한다는 점이 다릅니다.

동일 인스턴스에 대한 기준은?

public boolean equals(Object obj)

Object 클래스의 equals 메소드 호출 결과를 근거로 동일 인스턴스를 판단합니다.

public int hashCode()

그런데 그에 앞서 Object 클래스의 hashCode 메소드 호출 결과가 같아야 합니다.

정리하면, 두 인스턴스가 hashCode 메소드 호출 결과로 반환하는 값이 동일해야 합니다. 그리고 두 인스턴스를 대상으로 equals 메소드의 호출 결과 true가 반환되면 동일 인스턴스로 간주합니다.

Set 사용 예제

package o.collection.set;

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

public class SetRun {

    public static void main(String[] args) {
        // Object에 equals() => 두 객체의 "주소값"을 비교해서 일치하면 true/일치하지 않으면 false
        // Object에 hashCode() => 해당 객체의 "주소값"을 가지고 10진수의 형태로 만들어서 반환
        HashSet hsi = new HashSet();
       
        hsi.add("반갑습니다.");
        hsi.add(new String("반갑습니다"));
        hsi.add(new String("여러분"));
        hsi.add(new String("안녕하세요"));
        hsi.add(new String("여러분"));
       
        System.out.println(hsi); // 저장순서를 유지하지 않는다! 중복된 데이터(동일객체) 보관 불가
        // String에 equals() 오버라이딩 =>"실제 담긴 문자열"을 가지고 동등비교를 진행해서 일치하면 true/ 일치하지 않으면 false
        // String에 hashCode() 오버라이딩 =>"실제 담긴 문자열"을 가지고 10진수의 형태로 만들어서 반환.
       
        HashSet hsi2 = new HashSet();
        hsi2.add(new Student("전제민", 25, 95));
        hsi2.add(new Student("최지원", 42, 75));
        hsi2.add(new Student("김지민", 41, 60));
        hsi2.add(new Student("전제민", 22, 95));
       
        // 동일객체 : 각 객체마다 hashCode결과가 일치하고, equals메소드로 비교시 true가 나올 때
       
        // Student에 equals() 오버라이딩 => "실제 각 필드에 담긴 데이터"들이 다 일치하면 true/일치하지 않으면 false를 반환
        // Student에 hashCode() 오버라이딩 =>"실제 각 필드에 담긴 데이터"들이 일치하면 동일한 10진수 반환
        System.out.println(hsi2);
        // hsi2.get() 인덱스의 개념이 없기 때문에 get을 할 수 없음 -> 한개씩 무작위로 가져올 수 있음.
       
        // HashSet에 담긴 모든 객체들을 순차적으로 접근하는 방법
        // 1. for each문 이용
        System.out.println("=============for each=============");
        for(Object s : hsi2) {
            System.out.println(s);
        }
       
        // 2. Iterator 반복자를 이용해서 순차적 접근 방법
       System.out.println("=============Iterator=============");
        Iterator it = hsi2.iterator();
       
        while(it.hasNext()) { // 다음에 가져올 값이 있는지 확인
            Object obj = it.next();
            System.out.println(obj);
        }
       
        // 3. ArrayList에 담아준 다음 그 ArrayList를 반복적으로 돌아가며 접근 방법
       
        System.out.println("=============ArrayList=============");
        ArrayList list = new ArrayList();
        list.addAll(hsi2);
       
        for(int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
       System.out.println("==========================");
    }
}

위 코드에서 HashSet을 사용하여 데이터를 관리하는 방법을 보여줍니다.
HashSet은 중복된 데이터를 저장하지 않으며, 저장된 순서를 유지하지 않습니다. equals()hashCode() 메소드의 동작을 이해하고 적절히 오버라이딩하여 원하는 객체 비교 기준을 적용할 수 있습니다.
Set에 저장된 객체들을 순차적으로 접근하기 위해 for each문, Iterator, ArrayList를 활용할 수 있습니다.

profile
초보부터 시작하는 개발자 생활

0개의 댓글