2022. 12. 9. 23:13ㆍ자바
1. 제네릭: 하나의 코드를 다양한 타입의 객체에 재사용하는 객체 지향 기법
장점: 컴파일 할 때 타입을 점검-> 실행 도중 발생할 오류 사전 방지
불필요한 타입 변환이 없음-> 프로그램 성능 향상
class Gen<T> {
T ob; //Integer ob;
Gen(T o) {
ob = o; //Integer ob=88; String ob="Generics Test";
}
T getOb() {
return ob;
}
void showType() {
System.out.println("Type of T is " +
ob.getClass().getName()); //오브젝트의 클래스 타입의 이름을 출력
}
}
class GenDemo {
public static void main(String[] args) {
Gen<Integer> iOb;
iOb = new Gen<Integer>(88);
iOb.showType(); //Type of T is java.lang.Integer출력
int v = iOb.getOb(); //오토언박싱 일어남(Integer->int), 88저장
System.out.println("value: " + v);
Gen<String> strOb = new Gen<String>("Generics Test");
strOb.showType();
String str = strOb.getOb();
System.out.println("value: " + str);
}
}
- 자주 사용되는 타입 매개변수 기호
E: 원소(Element)
K: 키 (key)
N: 숫자(Number)
T: 타입(Type)
V: 값(Value)
*제네릭 클래스는 각 타입 인자 별 개별 버전으로 생성x
-> 자바 컴파일러가 타입 매개변수를 타입 인자로 대체
단, 타입 인자가 같아야 호환됨
*제네릭 타입인자로 클래스 타입만 가능(기초타입 x)
*제네릭 클래스 대신 Object를 사용하면 type safety가 보장되지 않음
-> Object 참조 변수가 가리키는 것이 어떤 타입일지 컴파일 시 알 수 없음-> 런타임 에러 발생 가능
class NonGen {
Object ob;
NonGen(Object o) {
ob = o;
}
Object getOb() {
return ob;
}
void showType() {
System.out.println("Type of ob is " +
ob.getClass().getName());
}
}
class NonGenDemo {
public static void main(String[] args) {
NonGen iOb;
int v;
NonGen dOb = new NonGen(1.25);
dOb.showType();
double dob = (Double) dOb.getOb();
//오브젝트 타입을 Double로 타입캐스팅 해줘야 double에 대입 가능
System.out.println("value: " + dob);
iOb = dOb; //여기까진 별문제 없이 잘 됨
v = (Integer) iOb.getOb();
// runtime 에러 발생->Double을 Integer로 타입캐스팅 할 수 없음
}
}
*두개 이상의 타입 매개변수 -> 콤마를 이용해 여러 타입 나열
ex) Gen(T,V)
2. 타입 한정
제네릭 클래스에서 특정 변수의 타입을 한정할 때 사용
예) 리턴 값을 int,float,double등의 숫자로 한정하고 싶을때
class Stats<T> {
T[] nums;
Stats(T[] o) {
nums = o;
}
double average() {
double sum = 0.0;
for (int i = 0; i < nums.length; i++)
sum += nums[i].doubleValue();
// 컴파일 오류남-> 타입이 아직 정해지지 않은 상태라 타입안정성에 위배되기 때문
return sum / nums.length;
}
}
class Stats<T extends Number>
//제네릭 타입을 숫자로 한정지음(Number의 서브클래스들만 제네릭 타입으로 올수 있음)
{
T[] nums; //숫자 타입의 배열 생성
Stats(T[] o) {
nums = o;
}
double average() // 배열들의 평균을 내고 싶은 메서드
{
double sum = 0.0;
for (int i = 0; i < nums.length; i++)
sum += nums[i].doubleValue();//이제 오류 안남
return sum / nums.length;
}
}
class BoundsDemo {
public static void main(String[] args) {
Integer[] inums = { 1, 2, 3, 4, 5 };
Stats<Integer> iob = new Stats<Integer>(inums);
double v = iob.average(); //Integer타입의 배열도 호환 가능
System.out.println("iob average is " + v);
Double[] dnums = { 1.1, 2.2, 3.3, 4.4, 5.5 };
Stats<Double> dob = new Stats<Double>(dnums);
double w = dob.average();
System.out.println("dob average is " + w);
//String 타입은 숫자가 아니므로 컴파일 오류 남
// String[] strs = { "1", "2", "3", "4", "5" };
// Stats<String> strob = new Stats<String>(strs);
// double x = strob.average();
// System.out.println("strob average is " + v);
}
}
* 타입 한정자로 인터페이스도 사용 가능
예) class GEN<T extends MyClass &MyInterface>
단, class T extends MyClass implements MyInterface를 구현해야 저 타입한정자 쓸 수 있음.
3. 와일드카드 인자
class Stats 예제에서 타입이 다른 두 오브젝트의 평균이 같은지 구하는 메서드를 구한다고 가정해보자
class Stats<T extends Number>
{...
boolean isSameAvg(Stats<T> ob) {
if (average() == ob.average())
return true;
return false;
}
}
class BoundsDemo{
...
iob.isSameAvg(dob);//컴파일 에러남->iob의 타입 매개변수가 dob의 타입과 일치해야 하기 때문
타입매개변수 T가 비교하려는 오브젝트의 타입과 일치해야 가능함
class Stats<T extends Number>
{...
boolean isSameAvg(Stats<?> ob) //와일드카드 인자를 사용하면 어느 오브젝트와도 가능
{
if (average() == ob.average())
return true;
return false;
}
}
class BoundsDemo{
...
iob.isSameAvg(dob);//이제 잘됨
-?를 와일드카드 인자라고 함
*와일드카드 인자도 특정 타입으로 한정 가능
<? extends superclass> //슈퍼클래스를 와일드카드의 상한으로 설정
<? super subclass> //서브클래스를 와일드카드의 하한으로 설정
class TwoD {
int x, y;
TwoD(int a, int b) {
x = a;
y = b;
}
}
class ThreeD extends TwoD {
int z;
ThreeD(int a, int b, int c) {
super(a, b);
z = c;
}
}
class FourD extends ThreeD {
int t;
FourD(int a, int b, int c, int d) {
super(a, b, c);
t = d;
}
}
/* 계층적 구조 FouD < ThreeD < TwoD */
class Coords<T extends TwoD> //타입을 TwoD로 한정
{
T[] coords;
Coords(T[] o) {
coords = o;
}
}
class BoundedWildcard {
static void showXY(Coords<?> c)
//Coords는 TwoD로 한정된 클래스라 ok->인자로 모든 서브클래스들 다 가능
{
System.out.println("X Y Coordinates:");
for (int i = 0; i < c.coords.length; i++)
System.out.println(c.coords[i].x + " " +
c.coords[i].y);
}
static void showXYZ(Coords<? extends ThreeD> c)
//extends 키워드로 ThreeD 오브젝트를 상한으로 지정->인자로 ThreeD와 FourD 가능
{
System.out.println("X Y Z Coordinates:");
for (int i = 0; i < c.coords.length; i++)
System.out.println(c.coords[i].x + " " +
c.coords[i].y + " " + c.coords[i].z);
}
static void showAll(Coords<? extends FourD> c)
//FourD 오브젝트를 상한으로 지정->인자로 FourD만 가능
{
System.out.println("X Y Z T Coordinates:");
for (int i = 0; i < c.coords.length; i++)
System.out.println(c.coords[i].x + " " +
c.coords[i].y + " " +
c.coords[i].z + " " +
c.coords[i].t);
}
public static void main(String[] args) {
TwoD[] td = {
new TwoD(0, 0),
new TwoD(7, 9),
new TwoD(18, 4),
new TwoD(-1, -23)
};
Coords<TwoD> tdlocs = new Coords<TwoD>(td); //TwoD 생성
System.out.println("Contents of tdlocs.");
showXY(tdlocs); // TwoD라 ok
// showXYZ(tdlocs); // Error, ThreeD랑 FourD만 가능
// showAll(tdlocs); // Error, FourD만 가능
FourD[] fd = {
new FourD(1, 2, 3, 4),
new FourD(6, 8, 14, 8),
new FourD(22, 9, 4, 9),
new FourD(3, -2, -23, 17)
};
Coords<FourD> fdlocs = new Coords<FourD>(fd);
System.out.println("Contents of fdlocs.");
// 다 FourD 가능
showXY(fdlocs);
showXYZ(fdlocs);
showAll(fdlocs);
}
}
4. 제네릭 메서드
- 메서드의 매개변수나 리턴 값을 제네릭 매개변수로 정의할 수 있음
- 제네릭 메서드는 일반 클래스 내부에서도 정의될 수 있음
- 제네릭 매개변수는 메서드의 리턴 타입 왼쪽에 선언
class GenMethDemo {
// 해당 오브젝트가 배열 안에 있는지 체크하는 메서드
// 메인함수가 static이라 메인함수에서 호출할 수 있도록 static으로 선언
static <T extends Comparable<T>, V extends T> boolean isIn(T x, V[] y)
/*comparable 인터페이스: 같은 타입의 인스턴스를 서로 비교하는 클래스들은 모두
Comparable 인터페이스를 구현한 것이다.*/
{
for (int i = 0; i < y.length; i++)
if (x.equals(y[i]))
return true;
return false;
}
public static void main(String[] args) {
// Use isIn() on Integers.
Integer[] nums = { 1, 2, 3, 4, 5 };
if (isIn(2, nums))
System.out.println("2 is in nums");
if (!isIn(7, nums))
System.out.println("7 is not in nums");
System.out.println();
// Use isIn() on Strings.
String[] strs = { "one", "two", "three", "four", "five" };
if (isIn("two", strs))
System.out.println("two is in strs");
if (!isIn("seven", strs))
System.out.println("seven is not in strs");
// if(isIn("two", nums))
// System.out.println("two is in strs");
// 타입호환이 안되서 컴파일 에러 발생
}
}
5. 제네릭 생성자
생성자의 매개변수도 제네릭으로 선언 가능
생성자 또한 일반 메서드 내부에 선언 가능
class GenCons {
private double val;
<T extends Number> GenCons(T arg) {
val = arg.doubleValue(); // double타입으로 변환
}
void showVal() {
System.out.println("val: " + val);
}
}
class GenConsDemo {
public static void main(String[] args) {
GenCons test = new GenCons(100);
GenCons test2 = new GenCons(123.5F);
test.showVal(); //100.0
test2.showVal(); //123.5
}
}
6. 제네릭 인터페이스
인터페이스의 메서드를 다양한 타입과 호환되도록 할 때
또는 메서드 매개 변수를 한정할 때 사용
// A Min/Max interface.
interface MinMax<T extends Comparable<T>> {
T min();
T max();
}
class MyClass<T extends Comparable<T>> implements MinMax<T> {
T[] vals;
MyClass(T[] o) {
vals = o;
}
public T min() {
T v = vals[0];
for (int i = 1; i < vals.length; i++)
if (vals[i].compareTo(v) < 0)
v = vals[i];
return v;
}
public T max() {
T v = vals[0];
for (int i = 1; i < vals.length; i++)
if (vals[i].compareTo(v) > 0)
v = vals[i];
return v;
}
}
class GenIFDemo {
public static void main(String[] args) {
Integer[] inums = { 3, 6, 2, 8, 6 };
MyClass<Integer> iob = new MyClass<Integer>(inums);
System.out.println("Max value in inums: " + iob.max());
System.out.println("Min value in inums: " + iob.min());
}
}
* 일반 클래스와 제네릭 클래스 간에도 상속 가능
* 자식 클래스는 부모 클래스를 상속 받고 새로운 제네릭 타입을 추가할 수 있음
class Gen2<T, V> extends Gen<T>{
...}
7. 제네릭의 제한사항
1) 타입 매개변수는 오브젝트화 할 수 없음
class Gen<T> {
T ob;
Gen() {
ob = new T();
// error->new키워드로 생성자 만들때 타입이 정해지지 않아서 type safety 벗어남
}
}
2) 제네릭 멤버 변수를 static으로 선언할 수 없고,
static 메서드가 제네릭 멤버 변수 사용 불가( 단, 제네릭 메서드 매개변수는 사용 가능)
(static 메서드 안에서는 원래 static 변수만 접근 가능)
class Wrong<T> {
static T ob; // 에러-> 제네릭 멤버 변수 static 선언 불가
static T getOb() { //에러-> static 메서드가 제네릭 멤버 변수 사용 불가
return ob;
}
}
3) 제네릭 배열 변수 선언은 가능하나, 생성은 불가
class Gen<T extends Number> {
T ob;
T[] vals; // 제네릭 배열 변수 선언 가능
Gen(T o, T[] nums) {
ob = o;
vals = new T[10]; // 컴파일 에러-> 제네릭 배열 생성은 불가
vals = nums; // 대입은 OK
}
}'자바' 카테고리의 다른 글
| 람다 표현식 (2) | 2022.12.10 |
|---|---|
| static 키워드 (1) | 2022.12.09 |
| 기타 자바 키워드 (0) | 2022.12.08 |
| try-with-resources문 (0) | 2022.12.08 |
| 입출력 기본 (0) | 2022.12.07 |