본문 바로가기
Java

Collection, Object, Generic의 관계

by happyhelen 2021. 7. 15.

Collection 이란 데이터를 수집하고 관리해주는 객체이자 서비스로, 

데이터 관리를 용이하게 하고, 공간이 모자라도 자동적으로 공간을 늘려주는 기능이 있어서 유용하다. 

기본적으로 .add() , .remove(), .clear(), .size() 등의 메소드를 제공

 

한가지 자료형으로만 클래스를 만들면 같은 기능을 하더라도 자료형별로 클래스를 각각 만들어야 한다.

그런데 모든 객체를 다루는 범용 자료형인 Object 객체를 사용하면 여러 형식의 데이터를 관리할 때 용이하다.  

 

단, Object 형식으로 모든 객체를 참조할 수 있지만 '값' (ex, int 3)은 참조가 아니라 담는 것이기 때문에 Object에서 참조할 수 없다.

 

그래서 Wrapper 클래스(참조형식) 가 필요한 것이다. 

int -> Integer 이렇게 박스에 담는 작업을 boxing (ex)  Object obj = 3; int 3이 auto boxing되어서 객체로서 obj에서 참조됨)

Integer -> int 이렇게 박스에서 꺼내는 작업을 unBoxing (int x = obj.intValue(); )이라고 한다.

 

Byte, Integer, Short, Long, Double, Float, Character / String 의 Wrapper클래스가 있고 여기서 String은 원래 참조형이어서 boxing 필요없이 바로 참조가 가능하다.

 


 

한편, 아래와같이 정수형 클래스를 만들었을 때, add메소드에 정수형이 아닌 다른 형이 들어오면 에러가 발생한다.

Object의 특징은 1) 특정한 형의 클래스를 여러개 만들필요 없이 Object 하나로 다 커버 가능하다는 것과, 2) 넣을때는 맘대로 넣었지만 꺼낼땐 형변환 필요하다는 것이다.

 

즉, 여러 형식을 일괄적으로 관리할때는 Object 형식으로 만들어서 클래스에 어떤 형식이 들어와도 받아들일 수 있게 하고, 꺼낼때는(값을 사용할 때는) 원하는 형식으로 변환해야하는 단점이 있는 것이다. 아래의 코드를 보자.

 

public class IntList {
	private int[] nums;
	private int index;
	
	// 생성자
	public IntList() {
		nums = new int[3];
		index =0;
	}

	// 메소드
	public void add(int num) {
		nums[index] = num;
		index++;	
	}

	public void clear() {
//		for(int i=0; i<index; i++) 
//			nums[i] = 0;
//		nums = new int[3]; 참조 자체를 바꾸는 방법
		index =0;
	}

	public int size() {
		return index;
	}

	public int get(int turn) {
		if(index <= turn)
			throw new IndexOutOfBoundsException();
		return nums[turn];
	}
}
public class Execution {
	public static void main(String[] args) {
		IntList List = new IntList();
		
		List.add(1);
		List.add(3);
		int size = List.size();
		System.out.printf("size : %d\n", size);
		
		List.clear();
		size = List.size();
		System.out.printf("size : %d\n", size);
		
		List.add(7);
		int num = List.get(0);
		System.out.printf("num : %d\n", num);
		// num = List.get(1); IndexOutOfBoundsException();
	}
}

 

 


 

// c++에서는  Template [    ] -> 컴파일러가 만들게끔 하는 것 이 존재하지만

Java에는 들어올때 다 들어오고, 원하는 형식으로 꺼낼 수 있는 <제네릭>이 있다.

public class GenericList<T> {
	private Object[] nums;
	private int index;
	
	// 생성자
	public GenericList() {
		nums = new Object[3]; // 객체생성 기본은 Object로
		index =0;
	}

	// 메소드
	public void add(T num) { // 들어오는 값 T로
		nums[index] = num;
		index++;	
	}

	public void clear() {
//		for(int i=0; i<index; i++) 
//			nums[i] = 0;
//		nums = new int[3]; 참조 자체를 바꾸는 방법
		index =0;
	}

	public int size() {
		return index;
	}

	public T get(int turn) { // 나갈 때 T로
		if(index <= turn)
			throw new IndexOutOfBoundsException();
		return (T)nums[turn];
	}
}
public class Execution {
	public static void main(String[] args) {
		GenericList<String> List = new GenericList<>();
		
		List.add("hello");
		List.add("bye"); 
		int size = List.size();
		System.out.printf("size : %d\n", size);
		
		List.clear();
		size = List.size();
		System.out.printf("size : %d\n", size);
		
		List.add("goodnight");
		String num = List.get(0); // 제네릭 덕분에 참조형식을 바꿀 필요가 없음
		System.out.printf("num : %d\n", num);
		// num = List.get(1); IndexOutOfBoundsException();
		
	}

}

 

 

++ 

공부를 위해 위의 예시를 활용해 Collections 을 직접 구현해보자

public class variableArray<T> {
	private Object[] nums;
	private int index;
	private int capacity; // 기본 용량
	private int amount; // 늘릴 용량
	
	public variableArray() {
		nums = new Object[capacity]; // 객체생성 기본은 Object로
		index =0;
		amount = 5;
		capacity =3;
	}

	// 메소드
	public void add(T num) { // 들어오는 값 T로
		if(index >= capacity) { // 공간이 모자라면
			Object[] temp = new Object[capacity+amount];
			// 데이터 옮기기
			for(int i=0; i<index; i++) {
				temp[i] = nums[i];
			}
			// 참조 옮기기
			nums = temp;
			// 현재 capacity값을 amount만큼 증가
			capacity += amount;
		}
		
		// 아니면 그냥 add
		nums[index] = num;
		index++;	
	}

	public void clear() {
//		for(int i=0; i<index; i++) 
//			nums[i] = 0;
//		nums = new int[3]; 참조 자체를 바꾸는 방법
		index =0;
	}

	public int size() {
		return index;
	}

	public T get(int turn) { // 나갈 때 T로
		if(index <= turn)
			throw new IndexOutOfBoundsException();
			
		return (T)nums[turn];
	}
}
public class VariableArray_Execution {
	public static void main(String[] args) {
		variableArray<String> List = new variableArray<>();
		
		List.add("hello");
		List.add("bye"); 
		int size = List.size();
		System.out.printf("size : %d\n", size);
		
		List.clear();
		size = List.size();
		System.out.printf("size : %d\n", size);
		
		List.add("goodnight");
		String num = List.get(0); // 제네릭 덕분에 참조형식을 바꿀 필요가 없음
		System.out.printf("num : %d\n", num);
		// num = List.get(1); IndexOutOfBoundsException();
	}
}

 

 

Collection 인터페이스(Set, List, Queue) 에는 add, clear, size, contains 등의 메소드 존재

Map 인터페이스(HashedMap)에는 put, clear, contains 등의 메소드 존재(위와 같은 기능을 함)

 

우선 이 세개를 위주로 공부하자

Set - HashSet : 인덱스 없음, 값이 곧 식별자, 그래서 중복 불가(중복 제거시 요긴)

List - ArrayList : 인덱스 있음, 중복 가능

Map - HashMap : 키(속성)와 값을 담음