[java] 제네릭이란

뿌이·2023년 1월 3일
0

Java 개념

목록 보기
10/19

제네릭이란


생활코딩이 진짜 정리를 너무 잘해놓아서
https://youtu.be/YUinFIexEQ4 이 영상을 보면 이해가 갈 것이다.
꺽새 안에 데이터 타입을 정의할 때 보통 E로 한다고 들었는데 여기서는 T로 되어있다.
뭐가 정답인지는 모르겠다

Person클래스 안에 <T>에 들어갈 것을 String이랑 StringBuilder를 넣어줬기 때문에
info는 
p1.info : String
p2.info : StringBuilder 데이터 타입이 된다.

근데 문제는 아무타입이나 다 받아버리면 타입안전성이 낮아진다는 것이다.

타입 안전성

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

위 코드는 StudentPerson이랑 EmployeePerson 클래스 둘 다
각 클래스형의 info를 가지고 있고, info를 매개변수로 받은다음 info를 정의해주고 있다.
고로 같은 구조이다

예제코드2

package org.opentutorials.javatutorials.generic;
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);
    }
}

그래서 person이라는 클래스로 StudentPerson , EmployeePerson을 대체했다.
class Person을 보면,

public Object info

가 있다.
Object로 했냐면
원래 각각 StudentPerson, EmployeePerson class 에서는
각 클래스형으로 info를 정의해주고 있었기 때문에
두 클래스 형의 부모인 Object로 해주면 모든 클래스형을 품을 수 있어서 Object로 정의했음

그러면 메인 메소드를 보라.

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

info는 '부장'이 된다. 그러면 String형의 '부장' 이 info가 될 것이다.
그 상태에서 '부장'을 형변환 해보자.

EmployeeInfo ei = (EmployeeInfo)p1.info; //'부장'을 EmployeeInfo로 형변환

근데 우리가 코드에서 저장을 하면 java는 바로 컴파일을 실행한다.
근데 컴파일 했을때 빨간줄이 노출되지 않는다. => 에러미노출
이러면 아주 찾기 어려운 문제가 될 수 있다.
위 코드를 톰캣으로 런 하면

Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to 
org.opentutorials.javatutorials.generic.EmployeeInfo
    at org.opentutorials.javatutorials.generic.GenericDemo.main
    (GenericDemo.java:17)

톰캣으로 런 했을때에만 나오는 런타임 오류로 노출된다.
이것은 매우매우매우 위험한 문제이다.

개발자가 저장하자마자 컴파일이 되서 , 컴파일 오류로 노출되어야 에러 날 수 있는 오류라는 것을 인지하며 코드를 작성하는데....
런타임 오류는 심각한 문제로 이어질 수 있기 때문에 ㅠㅠ....
이러한 사용은 위험 그 자체.....!!!!!!

오류는 왜났냐면...

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

EmployeeInfo는 int형만 가질 수 있는데, '부장'이라는 String이 들어가서 이것을 캐스팅할 수 없어서 오류가 나게된다.
이런 classCastingException 과 같은 오류가 날 때에는 타입이 안전하지 않다. 라고 한다.
그러니까 타입을 엄격하게 제한 할 수 있게 코드를 작성해야한다.

이런점 때문에 다른 예시이긴 하지만 타입의 안전성때문에 자바스크립트 대신 타입스크립트를 사용하는거 같다^^

그만큼 객체지향언어 뿐만 아니라, 스크립트언어에서도 타입의 안전성은 굉장히 굉장히 중요하게 다루고 있다는 것을 증명하는 셈이다..

기본 데이터 타입과 제네릭

제네릭을 사용하면서 자꾸 참조형이 아니면 사용할 수 없다고 ㅇㅔ러메시지가 떴는데 그 이유를 알 수 없고 너무답답하고,,
블로그를 보면서도 이해가안되서 제네릭을 공부하게 되엇다 ㅋㅋㅋ
이 블로그를 쓴 이유...

어쨌든..
제네릭에서는 기본데이터 타입을 사용할 수 없다고 한다.

기본 데이터 타입이란?

int, String, 등등을 말한다.
참조형은 클래스형이 대표적이다.
그런데 제네릭을 쓰고싶은데, int형, String형을 사용하고 싶을꺼 아니냐>?>??
그럴때에 사용방법을 알려드리려 한다.

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) {
        EmployeeInfo e = new EmployeeInfo(1);
        Integer i = new Integer(10); 
        //기본 데이터타입인 int를 참조형으로 사용하기 위해 Integer로 선언
        //이러한 클래스를 래퍼(wrapper) 클래스라고 함 = Integer
        Person<EmployeeInfo, Integer> p1 = 
        new Person<EmployeeInfo, Integer>(e, i);
        System.out.println(p1.id.intValue());
    }
}

제네릭의 제한

아까 타입안전성에 대해 얘기를 했다.
그런데 제네릭으로 보통 사용하는게 위에서 설명했던것이
어쩌구클래스<T,S> 뭐 이런식이다.
그럼 T랑 S에 뭘 넣어줘도 ㄱㅊ은 느낌이다 ;; 참조형이기만 하면;;

그래서 그것땜에 T랑 S에 넣을 데이터 타입을 제한할 수 있다!!!

extends

package org.opentutorials.javatutorials.generic;
abstract class Info{
    public abstract int getLevel();
}
class EmployeeInfo extends 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>("부장");
    }
}

위 코드를 보면, 밑 코드 때문에
즉 Person의 T는 Info 클래스나 그 자식 외에는 올 수 없다.

extends는 상속(extends)뿐 아니라 구현(implements)의 관계에서도 사용할 수 있다.

class Person<T extends Info>{

extends는 상속(extends)뿐 아니라 구현(implements)의 관계에서도 사용할 수 있다.

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>("부장");
    }
}

제네릭 왜쓰냐??????

제네릭 사용이유 블로그 보면 잘 알 수 잇음

1. 강한 타입체크

내가 블로그에 소개한 것은

class 클래스이름<T>{
}

이런거여서 T에 어떤 자료형이든지 들어갈 수 있고, 이 자료형을 제한하고자 한다면
extends라는 것을 사용해서 상속받은 자료형의 자식까지만 자료형을제한할 수 있다고 소개했다.
하지만 아예 처음부터 래퍼클래스를 이용해서

List<Integer> list = new ArrayList();

이라던지 이렇게 만들 수도 있다.그러면 List 안에 들어가는 자료형은 엄격하게 Integer 형으로 제한하겠다는 뜻이 된다.
이는 컴파일시 , Integer형이 맞는지, 아닌지 바로바로 강한 타입 체크를 해주는 장점이된다.

2.타입 변환을 제거한다.(casting)

제네릭을 사용하지 않는다고 할때에는, 그냥 list만 선언하고 그 안에 들어가는 자료형을 다시 Integer로 캐스팅 해야한다.

List list = new ArrayList();
list.add(1);

// 강제 형 변환 필요 
Integer number = (Integer) list.get(0);

근데 캐스팅 하는게 굉장히 성능에 안좋은 영향을 끼친다고 한다.
그래서 처음부터 엄격하게 타입을 제한할 수 있는 제네릭이 효과적인 코딩을 할 수 있는 방법중 하나라고 생각한다.

어디에 활용하나?

나같은 경우에는 이미 실무에서 활용하고 있다.
나는 최근 프로젝트에서 적지 않은 양의 데이터를 전달해주는 API를 개발했다.
대충 어떤 기능이었냐면..
사용자가 날짜를 선택하면 한달치 1금액, 2금액, 3금액,4금액, 날짜 , 등등 여러 정보를
하루하루의 데이터를 1달치를 전달해야 했다.

그래서 ArrayList<HashMap<String,Object>> 를 사용해서
각각의 ArrayList에 내가 전달하고싶은 날짜 개수만큼 HashMap데이터를 담았고
HashMap안의 key값에는 1금액, 2금액, 3금액,4금액, 날짜 , 등등 여러 정보 를 담아
전달해주었다.

간편한 방법이라고 생각한다.

앞으로도 나는 제네릭을 꾸준히 사용할 것이다.

출처

생활코딩 제네릭 유튜브 및 사이트
코딩트리 블로그

profile
기록이 쌓이면 지식이 된다.

0개의 댓글

관련 채용 정보