Java 제네릭

별의개발자커비·2023년 2월 16일
0

Java

목록 보기
49/66
post-thumbnail

1. 제네릭

  • 클래스를 정의 할 때는 info의 데이터 타입을 확정하지 않고 인스턴스를 생성할 때 데이터 타입을 지정하는 기능
class Person<T>{
    public T info; // 1. info의 데이터 타입은 T
    // 근데 아직 T라는 데이터 타입은 뭔지 확정되지 않음.
}
 
public class GenericDemo {
 
    public static void main(String[] args) {
        Person<String> p1 = new Person<String>(); // 2. T는 <> 안 데이터 타입String에 의해서 결정
	// 변수p1의 데이터타입 정의 	// 인스턴스를 생성

// 즉, 제네릭 = 클래스를 정의 할 때는 info의 데이터 타입을 확정하지 않고 
 			// 인스턴스를 생성할 때 데이터 타입을 지정하는 기능
        Person<StringBuilder> p2 = new Person<StringBuilder>();
    }
 
}

2. 제네릭을 사용하는 이유

ver1. 중복제거 전

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
    }
}

ver2. 중복제거 후

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; // 1. 여기에 Person(Object info) 이렇게 info 데이터타입을 object로 설정해준 덕분에
    Person(Object info){ this.info = info; }
}
public class GenericDemo {
    public static void main(String[] args) {
        Person p1 = new Person("부장"); // 2. "부장"이라는 아무String 데이터타입도 들어올 수 있게 되었다 --> 타입이 안전하지 못하다..
        EmployeeInfo ei = (EmployeeInfo)p1.info;
        System.out.println(ei.rank);
    }
}
  • 타입을 안전하게 하면서 중복을 제거하게 할 때 필요한 게, 제네릭

3. 제네릭의 특징 1

ver 3.제네릭화 , 복수의 제네릭

class EmployeeInfo{
    public int rank;
    EmployeeInfo(int rank){ this.rank = rank; }
}
class Person<T, S>{ // 1. 아직 Person<T, S> T,S이 어떤 데이터타입을 말하는 지 안알려줌 = 제네릭 복수도 됨
    public T info; // 3. 근데 T,S 자리에는 참조타입만 가능 (int같은 기본 데이터타입은 안됨)
    public S id;
    Person(T info, S id){ 
        this.info = info; 
        this.id = id;
    }
}
class Main {
    public static void main(String[] args) {
		Integer id = new Integer(1); // 6. 1=>Integer 타입의 'id' 로 대체할 수 있게
        Person<EmployeeInfo, Integer> p1 = new Person<EmployeeInfo, Integer>(new EmployeeInfo(1), id); // 5. 요 마지막 1도 위에서처럼 Interger 타입으로 바꿔주면 끝! --> 1 => id
    // 2. T(데이터타입) = EmployeeInfo / S(데이터타입) = int 알려줬음
  	// 4. 객체가 필요한 맥락인데, 그래서 int를 객체처럼 wrapper(포장) 해줘야함 --> int => Integer로 바꿔쓰기
		System.out.println(p1.id.intvalue()); // intvalue 메소드 (Integer가 갖고있는 메소드) 
	// = wrapper 클래스가 갖고있는 원래의 숫자(1)을 원래 타입(int)으로 돌려주는 것
		}
}

4. 제네릭의 특징 2

제네릭의 생략

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){ // @1. 메소드의 데이터타입도 제네릭<U>으로 미확정해놓을 수 있음
        System.out.println(info);
    }
}

class Main {

    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);
				// 1. 제네릭으로 데이터타입까지 써줬지만 @@를 통해 (e, i)만으로도
				// 데이터타입이 <EmployeeInfo, Integer>라는 것을 사실 자바가 추측할 수 있는 상황
			  Person p1 = new Person(e, i);
				// 2. 이 경우에는 <EmployeeInfo, Integer> 요 제네릭을 생략할 수 있음!
        p1.<EmployeeInfo>printInfo(e);	// @2. 그리고 여기서 데이터타입<EmployeeInfo> 입력해주면 되지만
        p1.printInfo(e); // @3. 역시 @@를 통해 e의 데이터타입EmployeeInfo을 알 수 있으니 이렇게 제네릭 생략 가능!
    }
}

5. 제네릭의 제한

  • 제네릭에 데이터타입을 나중에 지정해줄 수 있으니
  • 오만가지 타입이 다 올 수 있는 상황 => 제한이 필요
abstract class Info{ 
    public abstract int getLevel(); // 2. getLevel()메소드를 을 abstract만 해놓은 클래스다.
}
class EmployeeInfo extends Info{ // 1. 부모클래스 Info로 지정
    public int rank;
    EmployeeInfo(int rank){ this.rank = rank; }
    public int getLevel(){ // 3. getLevel() 로직은 여기
        return this.rank;
    }
}
class Person<T extends Info>{ // 4. <T extends Info>의 뜻은,
// T의 데이터타입 미확정, 근데 제한을 둠, 
// 데이터타입에 Info클래스이거나 그 자식들만 오게 강제하는 것
    public T info;
    Person(T info){ this.info = info; }
}
public class GenericDemo {
    public static void main(String[] args) {
        Person<EmployeeInfo> p1 = new Person<EmployeeInfo>(new EmployeeInfo(1));
        // 5. 제한됐는지 확인: 여기서 Person<EmployeeInfo>니까
        // 4번으로 가서 EmployeeInfo가 Info의 자식이면 통과 (o)
        Person<String> p2 = new Person<String>("부장");
		// 6. 근데 Person<String>에서 String은 Info의 자식이 아니네? -> 컴파일에러

    }
}

  • extends는 상속(extends)뿐 아니라 구현(implements)의 관계에서도 사용할 수 있다.
interface Info{ // 이 extends를 class가 아닌 interface에서 쓸 수도 있음
    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>{ // 여기는 implements로 바꾸면 X
// 제네릭맥락에서는 extends는 상속(x), 부모가 누구냐 (o) 
    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>("부장");
    }
}

0개의 댓글