제너릭(generic)
https://www.youtube.com/watch?v=DBnqmTSHEOA
- 특정한 클래스에 원하는 객체 타입을 지정하여 지정된 객체만 접근하게 하는 자바 문법.
- 다양한 타입의 객체들을 다루는 메서드나 컬렉션에서 컴파일할 때 컴파일러가 타입을 확인해 주는 기능
==> 다루어질 객체의 타입을 미리 명시함으로써 번거로운 형변환을 줄여 준다는 장점이 있음.
또한, 객체의 타입을 컴파일 시에 체크하여 주기 때문에 객체의 타입 안정성을 높여주는 장점이 있음
- 타입의 안정성을 높여준다는 것은 의도하지 않은 타입의 객체가 저장되는 것을 막고,
저장된 객체를 꺼내올 때 원래의 타입과 다른 타입으로 잘못 형변환되어 발생할 수 있는 오류를 줄여 준다는 것임
제네릭스의 장점
1. 타입 안정성을 제공한다.
2. 타입체크와 형변환을 생략할 수 있으므로 코드가 간결해진다.
- jdk 1.5 에서부터 추가된 기능.
- 클래스 내부에서 사용할 데이터를 외부에서 지정하는 방법.
- 데이터의 명확성과 안정성을 보장해 줌.
==> 다른 데이터 타입의 데이터가 들어올 경우 컴파일 시점에서 error 발생.
- static 키워드는 참조형 타입 변수를 사용할 수 없다. 당연한게, 참조형변수는 인스턴스변수로 간주되고,
static멤버는 모든 객체에 대해 동일하게 동작해야 하는 멤버이므로,
인스턴스변수를 참조할 수 없기 때문
간단히 얘기하면, 다룰 객체의 타입을 미리 명시해줌으로써
번거로운 형 변환을 줄여준다.
예제)
다음은 여러가지 재료를 가지고 프린팅을 해주는 3D 프린터를 제네릭 프로그래밍으로 구현한 예시이다.
오브젝트 클래스 방식으로 구현한 것과, 이를 제너릭 방식으로 구현한 것의 차이를 놓고 비교해보자.
아래는 오브젝트 방식
package Generic;
public class ThreeDPrinter {
// Object는 최상위 클래스
// 모든 클래스는 Object의 형변환이 이뤄진다.
// material라는 변수에 대해서 어떤 타입이 들어오건
// Object로 변환되어서 사용될 수 있다.
private Object material;
public Object getMaterial() {
return material;
}
// Object로 받았기 때문에 material 변수에 wrapping 된다.
public void setMaterial(Object material) {
this.material = material;
}
}
//파우더 재료 선언
package Generic;
public class Powder {
public String toString() {
return "사용 된 재료는 Powder 입니다.";
}
}
//플라스틱 재료 선언
package Generic;
public class Plastic {
public String toString() {
return "사용 된 재료는 Plastic 입니다.";
}
}
package Generic;
public class ThreeDPrinterTest {
public static void main(String[] args) {
ThreeDPrinter printer = new ThreeDPrinter();
//ThreeDPrinter에서 Object로 받았기 때문에 에러가 없다. => 업캐스팅
printer.setMaterial(new Powder());
// Powder powder = printer.getMaterial();
// 위 문장이 오류가 뜨는 이유는
// ThreeDPrinter에서 반환하는 값은 object인데 powder로 받았기 때문
// (Powder)을 붙임으로써 다운캐스팅함
Powder powder = (Powder)printer.getMaterial();
}
}
이렇게 구현한다면 set 을 하는 경우는 자식에서 부모로 업 캐스팅이 자동으로 되지만
get 을 하는 경우에는 Object 클래스에서 각각의 재료 클래스로 다운 캐스팅을 직접 해주어야 하기 때문에 굉장히 번거롭다.
이를 제너릭 방식으로 구현하고자 하면
package Generic;
public class ThreeDPrinter<T> {
private T material;
public T getMeterial() {
return material;
}
public void setMeterial(T material) {
this.material = material;
}
@Override
public String toString() {
return material.toString();
}
}
이렇게 ThreeDPrinter 클래스를 제너릭클래스로 선언해주면 된다.
다이아몬드 연산자와 안에 적절한 알파벳을 써주면 제네릭 클래스를 만들 수 있다.
(원하는 이니셜을 사용하면 된다고 하나, 보통 T, E, V를 많이 쓴다고 한다. 각각 type, element, value의 약자)
내가 사용 할 자료형이 T 위치에 들어가 수행된다고 생각하면 된다.
테스트 클래스에서는
package Generic;
public class ThreeDPrinterTest {
public static void main(String[] args) {
ThreeDPrinter<Powder> printer = new ThreeDPrinter<Powder>();
printer.setMaterial(new Powder());
// 굳이 필요없지만..제너릭 방식을 쓰면 다운캐스팅 하지 않아도
// 컴파일 시 오류가 안 생기는 것을 보여주기 위함
Powder powder = printer.getMaterial();
System.out.println(printer);
ThreeDPrinter<Plastic> printer2 = new ThreeDPrinter<Plastic>();
printer2.setMaterial(new Plastic());
System.out.println(printer2);
}
}
결과
사용 된 재료는 Powder 입니다.
사용 된 재료는 Plastic 입니다.
프린터를 생성 할 때 printer<Powder> 과 같이 T 의 자료형을 정해주면 된다.
생략을 해도 컴파일러가 추측을 해서 정해주지만.. 경고의 주황색 밑줄이 쳐지니 되도록이면 자료형을 정해주자.
그리고 프로그램을 실행시켜보면 하나의 프린터 클래스를 가지고 여러가지 재료를 사용 할 수 있게 되었다.
만약 T타입에 대한 제한을 두고싶다면?
(3D프린터 재료가 될 수 없는 재료..이를테면 물 같은 것들도 재료 클래스로 등록되서 사용한다면 문제가 되니-)
1. 상위클래스 생성
package Generic;
public class Material {}
2. 상위클래스를 상속받는 하위클래스 생성
package Generic;
public class Plastic extends Material{
public String toString() {
return "사용 된 재료는 Plastic 입니다.";
}
}
package Generic;
public class Powder extends Material{
public String toString() {
return "사용 된 재료는 Powder 입니다.";
}
}
3. ThreeDPrinter클래스 선언부에 조건을 하나 걸어준다. 어떻게? 아래와 같이.
public class ThreeDPrinter<T extends Material> {
이렇게 하면 Material 이라는 상위클래스를 상속받지 않은 클래스는 재료가 될 수 없다. (컴파일 에러가 뜸)
'Back-End > Java' 카테고리의 다른 글
[Java] 컬렉션 프레임워크_LinkedList (0) | 2021.09.17 |
---|---|
[JAVA] 컬렉션 프레임워크 개념과 ArrayList (0) | 2021.09.17 |
[JAVA] 싱글톤(singleton) 방식으로 객체 생성하기 (0) | 2021.09.17 |
[Java] 래퍼 클래스(wrapper class) (0) | 2021.09.17 |
[JAVA] 다양한 형태의 클래스 선언 방법과 중첩클래스(inner class) (0) | 2021.09.17 |
댓글