[Java] 제네릭(Generic)에 대해서

24355 단어 JavaJava

제네릭이란?

제네릭은 클래스 내부에서 사용할 데이터 타입을 클래스 외부에서 지정하는 기법이다.
상속(extends)와 인터페이스(interface)에서도 사용 가능하다.
제네릭은 다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일 시 타입 체크를 해주는 기능이다.

제네릭 타입중에 T로 되어있는 경우가 있는데, 하지만 T라는 데이터 타입은 존재하지 않는다. 이 값은 클래스를 선언하는 부분에서 정해진다.

즉, 인스턴스 생성할때 제네릭안에 선언되는 데이터 타입에 의해서 결정된다.

public class GenericClass<T> {
  	public T info;
}

public class Main {
  	public static void main(String[] args) {
      	GenericClass<String> generic = new GenericClass<String>;
      	GenericClass<Integer> generic = new GenericClass<Integer>;
    }
}

(중요) 클래스를 정의할때 해당 클래스 내부에서 사용하는 데이터 타입을 확정하지 않고 인스턴스를 생성할때 데이터 타입을 지정하는것이 제네릭이다.

제네릭을 사용해야하는 경우

제네릭을 사용함으로써 잘못된 타입이 사용될 수 있는 문제를 컴파일 과정에서 제거할 수 있기 때문이다.

타입 안정성

public class StudentInfo {
  	public int grade;
  	public StudentInfo(int grade) {
      	this.grade = grade;
    }
}

public class EmployeeInfo {
  	public int rank;
  	public EmployeeInfo(int rank) {
      	this.rank = rank;
    }
}

public class Person {
  	public Object info;
  	public Person(Object info) {
      	this.info = info;
    }
}

public class Main {
  	public static void main(String[] args) {
      	Person p = new Person("반장");
      	Employee e = new Employee();
    }
}

위의 코드에 대해 설명하자면 Person의 생성자의 데이터 타입은 Object이다. Object는 모든 객체가 될 수 있다.

EmployeeInfo의 객체가 아니라 String 타입이 와도 컴파일 에러가 안난다. (대신 런타임 에러가 난다.)

컴파일언어의 기본은 모든 에러는 컴파일이 발생할 수 있도록 유도해야 한다.

하지만 런타임은 실제로 애플리케이션이 동작하고 있는 상황이기 때문에 런타임에 발생하는 에러는 항상 심각한 문제를 초래할 수 있기 때문이다.

그래서 위와 같은 코드에 대해서는 타입에 대해 안전하지 않다고 한다.

즉, 모든 타입이 올 수 있기 때문에 타입을 엄격하게 제한 할 수 없게 되는 것이다.

제네릭화

위의 코드를 제네릭화로 변경해본다.

package generic;

public class GenericClass3 {
    public static class StudentInfo {
        public int grade;
        public StudentInfo(int grade) {
            this.grade = grade;
        }
    }

    public static class EmployeeInfo {
        public int rank;
        public EmployeeInfo(int rank) {
            this.rank = rank;
        }
    }

    public static class Person<T> {
        public T info;
        public Person(T info) {
            this.info = info;
        }
    }

    public static void main(String[] args) {
        Person<EmployeeInfo> p = new Person<EmployeeInfo>(new EmployeeInfo(1));
        EmployeeInfo e = p.info ;
        System.out.println(e.rank);
    }
}

알고가야할 부분

  • 컴파일 단계에서 오류가 검출된다.
  • 중복의 제거타입 안전성을 동시에 추구 할 수 있게 된다.

여러개의 제네릭을 필요로 할때

클래스 내에서 여러개의 제네릭을 필요로 할때가 있다. 그럴때 사용하는 방법을 알아본다.

public class GenericClass4 {
    public static class EmployeeInfo {
        public int rank;

        public EmployeeInfo(int rank) {
            this.rank = rank;
        }
    }

    public static class Person<T, X> {
        public T info;
        public X id;

        public Person(T info, X id) {
            this.info = info;
            this.id = id;
        }
    }

    public static void main(String[] args) {
        Integer i = 10;
        EmployeeInfo e = new EmployeeInfo(1);
        Person<EmployeeInfo, Integer> person = new Person<EmployeeInfo, Integer>(e, i);
        System.out.println(person.id.intValue());
    }
}

여러개의 제네릭을 사용할땐 <>안에 ,(콤마) 옆에 바로 써주면 된다.

여기서 사용되는 문자는 원하는거 아무거나 써도 되지만 개발자들끼리의 묵시적인 약속이 있다. (되도록이면 거기에 맞추는게 좋다.)

타입설명
TType
EElement
KKey
VValue
NNnmber

주의해야 할 점이 하나 있는데 제네릭을 사용할때 기본타입 데이터(primitive type)을 사용할 수 없다. 참조 데이터(클래스, 인터페이스,배열)만 사용가능한걸 알고 있자.

기본 자료형을 사용하기 위해선 래퍼클래스를 사용해야함

래퍼클래스의 예) String, Integer, ...

제네릭 메서드 적용 방법

public class EmployeeInfo {
  	public int rank;
  	public EmployeeInfo(int rank) {
      	this.rank = rank;
    }
}

public class Person<T, X> {
  	public T info;
  	public S id;
  
  	public Person(T info, X id) {
      	this.info = info;
      	this.id = id;
    }
  
  	public <U> void printInfo(U info) {
      	// 메서드가 반환형이 없고 타입만 주고 싶을때 <U> 이런식으로 준다.
      	System.out.println(info);
    }
  	
  	public T printInfo(T info) {
      	// 메서드의 리턴값이 있을땐 클래스를 선언할때 사용한 제네릭 타입을 사용해야한다.
       	return info;
    }
}

public class GenericClass {
  	public static void main(String[] args) {
      	EmployeeInfo e = new EmployeeInfo(1);
      	Integer i = 10;
      	Person<EmployeeInfo, Integer> person = new Person<EmployeeInfo, Integer>(e, i);
      	person.printInfo(e);
    }
}

위의 코드를 보면 메서드에 제네릭을 선언한게 보인다 하나의 클래스에 동일하게 제네릭 타입을 두면 더 좋지만 본인이 원하는 대로 타입을 둘 수 있다는 걸 보여주기 위해 코드로 작성해보았다. (반환형이 없는 메서드에 한해서 가능한일)

좋은 웹페이지 즐겨찾기