본문 바로가기

JAVA

제네릭 - 객지설 10주차

제네릭

프로그래밍을 쉽게 작성하기 위해 다양한 종류의 데이터를 처리할 수 있는 클래스와 메소드를 작성하는 기법

코드가 중복되지 않음(재사용성) => 마치 상속처럼

*클래스로 이용됨.

*객체를 만들 수 없다 => 클래스를 만드는 모양 틀임.

 

동작 방식

자료형(타입)을 구체적으로 명시X => T와 같은 기호로 적음.

그 후, 객체를 생성할 때 T자리에 구체적인 자료형을 적음. => 자료형을 클래스의 매개변수로 만든다는 뜻

 

+) 제네릭 이전에는 어떻게 동작했을까

다형성을 통해 제네릭 기법 전에도 모든 종류의 객체를 받을 수 있는 클래스 작성이 가능했음. 

객체를 Object 타입으로 받으면 됨. Object클래스는 최상위 클래스이므로, 업캐스팅으로 모든 종류의 객체를 받을 수 있음

*모든 객체는 Object 클래스의 자손임.

//Object 클래스는 모든 클래스의 상위 클래스임.
class Box {
	private Object data;
	
	public void set(Object data) { this.data = data; }
	public Object get() { return data; }
}

public class Test {
    
	public static void main(String[] args) {
		Box b = new Box(); //Box 클래스 객체 생성
		
		b.set("Hello World!");  //업캐스팅 발생 -> 문자열 객체 저장
		String s = (String)b.get(); //다운캐스팅 발생 -> Object 타입을 String 타입으로 형변환
		
		b.set(10); //업캐스팅 발생 -> 정수 객체 저장
		Integer i = (Integer) b.get(); //다운캐스팅 발생 -> Object 타입을 Integer 타입으로 형변환
	}
}

문제점

1. 데이터를 갖고 올 때 항상 형변환이 필요함

2. 문자열을 저장하고 다른 타입 객체로 형변환 할 수 있음 -> 심지어 이클립스에서 빨간 오류가 안 뜨고, 실행해야 컴파일 오류가 남. 즉, 오류 찾기가 어려움.

b.set("Hello World!");
Integer i = (Integer)b.get(); //오류

이러한 문제점을 해결하기 위해 재네릭 기법 등장

 

제네릭 이용 방법

타입 매개 변수 : 타입을 변수로 표시한 것 => 프로그래머가 결정함

*아래 코드의 타입 매개 변수는 T임.

class Box<T> {
	private T data;
	
	public void set(T data) { this.data = data; }
	public T get() { return data; }
}

public class Test {
    
	public static void main(String[] args) {
        Box<String> b1 = new Box<String>(); //타입 매개 변수 값을 T가 아닌 String으로
        
        b1.set("Hello World"); //이미 b는 String 객체이므로, 업캐스팅X
        String s = b1.get(); //형변환 또한 할 필요X
        
        //int는 클래스가 아닌 기초 자료형이므로 사용할 수 없음. 객체 자료형이 와야 함.
        Box<Integer> b2 = new Box<Integer>();
        b2.set("Hello World!"); //b2는 이미 Integer 객체이므로, 문자열을 저장하려고 하면 컴파일 오류 -> 오류 감지 편리
	}
}

 

생성자 호출 시, 타입 인수를 구체적으로 주지 않아도 됨. 컴파일러가 추측함. 그러나 써 주는 것이 가독성이 더 좋음.

Box<String> b1 = new Box<>(); //가능
Box<Integer> b2 - new Box<>(); //가능

 

제네릭 메소드

메소드도 제네릭으로 가능.

class MyArrayAlg {
	public static <T> T getLast(T[] a) {
		return a[a.length - 1];
	}
}

public class Test {
	public static void main(String[] args) {
		String[] language = { "C++", "C#", "JAVA" };
		String last = MyArrayAlg.getLast(language);
		System.out.println(last);
	}
}

MyArrayAlg 클래스 설명

public static : 정적 메소드임을 나타냄. main()에서 MyArrayAlg.(메소드이름)으로 접근 가능함.

<T> : 타입 피라미터의 값(타입 매개변수)

T : 반환 하는 값의 타입

getLast : 메소드 명

T[] a : T[] 배열 a

 

* getLast메소드는 일반 클래스 안에 정의되어 있지만 <T>를 갖고 있으므로 제네릭 메소드임.

* 타입 매개 변수

 - 이 부분이 빠지면 컴파일 오류 발생. JAVA에서 대문자 T라는 이름을 가진 클래스 이름이 없다는 의미로 오류가 남. 

- 위치 : 반환값 앞

- 변수명 : 사용자 마음이지만 일반적으로 사용하는 이름이 있음

E : Element (요소)

K : Key

N : Number

T, S, U, V ... : 첫 번째 Type, 두 번째 Type, 세 번째 Type, 네 번째 Type

V : Value

- 객체화가 될 수 없음 

f1 <String, int> 불가

f2 <String, Integer> 가능

* main에서 제네릭 메소드를 호출 할 때 아래의 코드가 문법적으로 옳은 코드임. 

String last = MyArrayAlg.<String>getLast(language);

getLast는 T자리에 의미 있는 값이 들어가야 함. T자리에 String이 들어감을 명시해 주는 것임. 

하지만 <> 부분이 없어도 컴파일 오류는 안 나고, 잘 작동함. JAVA에서 타입 유추를 하기 때문(type in inference)

* 반드시 제네릭 메소드가 static일 필요는 없음. -> static이 없다면, 객체를 생성해야함.

MyArrayAlg arr = new MyArrayAlg();
String last = arr.<String>getLast(language);

 

+) 피라미터 : 입력변수, 매개변수 ->  메소드의 입력 값을 저장하는 변수

- 단일 피라미터 public static void f1 (TYPE X) { }

- 다중 피라미터 public static void f2 (TYPE X, TYPE Y) { }

 

public class Test {
	public static <T> void printArray(T[] array) {
		for (T element : array) {
			String s = String.format("%s ", element);
			System.out.printf(s);
		}
		System.out.println();
	}
	
	public static void main(String[] args) {
		Integer[] iArray = { 10, 20, 30, 40, 50 };
		Double[] dArray = { 1.1, 1.2, 1.3, 1.4, 1.5 };
		Character[] cArray = { 'K', 'O', 'R', 'E', 'A' };
		
		printArray(iArray);
		printArray(dArray);
		printArray(cArray);
	}
}

//출력 결과
10 20 30 40 50 
1.1 1.2 1.3 1.4 1.5 
K O R E A

 

 

'JAVA' 카테고리의 다른 글

상속과 구성 - 객지설 12주차  (0) 2024.05.27
컬렉션/ArrayList/HashSet - 객지설 11주차  (0) 2024.05.23
예외 처리 - 객지설 7~9주차  (0) 2024.05.10
인터페이스 - 6주차  (0) 2024.05.05
다형성 - 객지설 5주차  (0) 2024.05.04