자바(Java) 제네릭(Generic) 설명

2020. 11. 2. 21:47자바(Java)

자바(Java)의 제네릭(Generic)에 대한 정리


▶ 제네릭(Genric)

class Person<T>{

public T info;

}

Person<String> p1 = new Person<String>();

Person<StringBuilder> p2 = new Person<StringBuilder>();



- T는 info라는 필드의 데이터타입이다.

- Person이 인스턴스화될때 다이아몬드안에 구체적인 데이터타입(String)을 언급을하면

  class Person<String>이 되고 info도 String이라는 데이터 타입을 가지게된다.

- 인스턴스를 생성할때 인스턴스변수와 인스턴스는 똑같은 형식을 가지고있어야한다.

- 제네릭은 클래스타입이 미정인 경우 데이터 타입을 인스턴스화 시킬 때 지정하는 것.



▶ 타입 안전성(Type safety)과 제네릭(Generic)이 필요한 이유

package org.opentutorials.javatutorials.generic;

class StudentInfo{

    public int grade;

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

}

class StudentPerson{

    public StudentInfo info;

    StudentPerson(StudentInfo info){ this.info = info; }

}

class EmployeeInfo{

    public int rank;

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

}

class EmployeePerson{

    public EmployeeInfo info;

    EmployeePerson(EmployeeInfo info){ this.info = info; }

}

public class GenericDemo {

    public static void main(String[] args) {

        StudentInfo si = new StudentInfo(2);

        StudentPerson sp = new StudentPerson(si);

        System.out.println(sp.info.grade); // 2

        EmployeeInfo ei = new EmployeeInfo(1);

        EmployeePerson ep = new EmployeePerson(ei);

        System.out.println(ep.info.rank); // 1

    }

}

↓중복을 제거한 코드

class StudentInfo{

    public int grade;

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

}


class EmployeeInfo{

    public int rank;

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

}


class Person{

public Object info;

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

}


public class GenericDemo {

public static void main(String[] args) {

Person p1 = new Person("부장");

EmployeeInfo ei =(EmployeeInfo) p1.info;

System.out.println(ei.rank);



- 위의 코드를 보면 중복이 발생한다.

- 모든 클래스들의 공통조상인 object를 사용하면 어떠한 클래스의 인스턴스도 올 수 있다.

- 우리는 Person() 이 괄호안에 StudentInfo와 EmployInfo의 인스턴스가 들어오길 기대하는데

  나중에 시간이 많이흐르고 개발자가 코드가 짠 의도를 기억하지 못할때 단지 코드만보고 

  사람이름넣으면 되구나해서 넣고나면 문법적으로는 오류없이 실행이되지만 의도에 빗나감. 

  이러한 문법적인 오류는 찾기어려운 문제이기때문에 아주 심각한 오류이다.

- 부장이라는 String타입을 EmployeeInfo라고 형변환을 시도했는데 에러메세지가 뜨지않는다.

  이는 컴파일할때 오류가 검출되지않는것인데 실행하면 오류가발생한다.(런타임에러)

=> 이를 타입이 안전하지 않다라고 한다.(자바에서는 허용되지 않는다, Java는 타입에 엄격하다.)

- 우리는 컴파일할 때 오류가 검출되게끔 해야한다.

- 그래서 도입된것이 제네릭이라는 것이다.(타입의 안전성, 코드의 중복성을 제거한 편의성)



▶ 제네릭(Generic)의 복수사용과 기본 데이터 타입의 객체화

package org.opentutorials.javatutorials.generic;

class EmployeeInfo{

    public int rank;

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

}

class Person<T, S>{

    public T info;

    public S id;

    Person(T info, S id){ 

        this.info = info; 

        this.id = id;

    }

}

public class GenericDemo {

    public static void main(String[] args) {

       Integer id = new Integer(1);

       Person<EmployeeInfo, Integer> p1 = new Person<EmployeeInfo, Integer>(new EmployeeInfo(1), id);

       System.out.println(p1.id.intValue()); 

  }

}


- 제네릭을 여러개 사용하려면 위와같이 다이아몬드안에 ,(콤마)로 구분해주면된다.

  (다른 이름을 가져야함)

- T,S에는 기본데이터타입은 올 수 없다.(참조형만 올 수 있음)

- 기본데이터타입은 자바에서 객체가 아니다. 즉, 제네릭을 사용하려면 기본 데이터타입을

  객체인것처럼 만들어야하는데 그러한 클래스를 랩퍼클래스(Wrapper Class)라고한다.

- int형식의 데이터타입이었던 숫자 1이 Wrapper Class인 Integer에 생성자로 들어가서 숫자 1을

  의미하는 하나의 객체 인스턴스를 만들었다. -> 더 이상 id는 기본 데이터타입이아님.

- Wrapper Class에 있는 메소드중 intValue();를 호출하면 Wrapper Class가 담고있는 원래의 숫자

  즉, Int 타입의 데이터타입으로 돌려주는 메소드이다.(원래값가져오기)



▶ 제네릭(Generic)의 생략과 메소드(Method)에 적용하기

package org.opentutorials.javatutorials.generic;

class EmployeeInfo{

    public int rank;

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

}

class Person<T, S>{

    public T info;

    public S id;

    Person(T info, S id){ 

        this.info = info;

        this.id = id;

    }

    public <U> void printInfo(U info){

        System.out.println(info);

    }

}

public class GenericDemo {

    public static void main(String[] args) {

        EmployeeInfo e = new EmployeeInfo(1);

        Integer i = new Integer(10);

        Person<EmployeeInfo, Integer> p1 = new Person<EmployeeInfo, Integer>(e, i);

        p1.<EmployeeInfo>printInfo(e);

        p1.printInfo(e);

    }

}



- Person<EmployeeInfo, Integer> p1 = new Person<EmployeeInfo, Integer>(e, i);는

  Info와 id의 매개변수타입을 지정해준다고 볼 수 있는데 자바는 생성자의 매개변수(e,i)를 통해

  e와 i가 각각 EmployInfo와 Integer라는것을 알 수 있다. 이러한 경우에는 제네릭을 생략하여

  Person p1 = new Person(e, i); 이런식으로써도 자바는 알아볼 수 있다.

- 제네릭은 클래스레벨에서만 사용하는것이아닌 메소드레벨에서도 사용할 수 있다.

- 메소드에서 Info라는 매개변수타입을 확정하지않고 싶을 때 <U>를 사용한다.

- p1.<EmployInfo>printInfo(e);를 사용하면 <EmployInfo>라는 데이터타입이 <U>로 들어가서

  info의 데이터타입은 EmployInfo가 된다. 이때, 매개변수 info에 어떠한 값이 들어왔느냐에따라

  U를 추정할 수 있기때문에 <EmployInfo>는 생략이가능하다. 즉, p1.printInfo(e);로 쓸 수 있다.



▶제네릭(Generic)의 제한

package org.opentutorials.javatutorials.generic;

interface Info{

    int getLevel();

}

class EmployeeInfo implements Info{

    public int rank;

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

    public int getLevel(){

        return this.rank;

    }

}

class Person<T extends Info>{

    public T info;

    Person(T info){ this.info = info; }

}

public class GenericDemo {

    public static void main(String[] args) {

        Person p1 = new Person(new EmployeeInfo(1));

        Person<String> p2 = new Person<String>("부장");

    }

}


- 제네릭은 클래스타입이 내부적으로 미정인 경우 데이터 타입을 인스턴스화 시킬 때 지정하는 것.

  그렇다보니, 제네릭으로 아무거나 들어올 수 있는것이 문제가 됨. -> extends

- EmployInfo의 부모클래스는 추상클래스인 Info이다.

- T extends Info의 뜻은 Info라는 클래스의 자식클래스들만 T에 데이터타입으로 들어올 수 있다.

  따라서 Person<String> p2 = new Person<String>("부장"); 에서 

  String은 Info의 자식이아니므로 컴파일 에러가 발생하게 된다.

- extends를 Class로만 사용해야하는것이아닌 Interface로 사용해도된다. -> implements

- extends는 제네릭에서 사용할 때 '상속한다'가 아닌 '부모가 무엇이다'로 사용된다.

  따라서 Interface로 바꾸더라도 <T extends Info>는 유지된다.

- extends와 반대로 super라는 것도있는데 부모를제한하는것이다.



ref)이번 글은 유튜브 생활코딩님의 강의영상을 참고하였습니다.