-
지네릭스(Generics) - 지네릭 소개, 지네릭 클래스백엔드/자바 2021. 5. 21. 11:13
지네릭스란(Generics)란?
다양한 타입의 객체를 다루는 메서드, 컬렉션에 입력 가능한 객체를 제한하게 하는 기능이다.
좀 더 직관적으로 쓰자면 특정 메서드나 컬렉션에 들어가는 자료형을 제한하는 것이다.
지네릭스의 필요성
예를 들어 위와 같이 String 객체만 들어가길 원하는 ArrayList가 있다고 가정해보자.
기존에 잘 모를 때는 지네릭스 없이 위와 같은 형식으로 객체를 생성하고 사용했다.
하지만 이렇게 생성할 경우 코드는 줄지만 반대로 여러가지 문제점 있다.
예를 들어 위와 같은 상황처럼 데이터를 입력받고, 이걸 변수에 저장한 뒤 출력하는 코드를 가정해보자.
컴파일러가 실행(런타임) 전에 확인할 때는 어떠한 오류도 없다. 우선 실행 결과를 보도록 하자.
컴파일러는 문제가 없다고 했지만, 실제로는 런타임과정에서 에러가 난다.
Integer를 String으로 형변환해줄 수 없었기 때문이다.
위에서 문제가 되는 건 크게 3가지다.
첫번째 문자열(String)만 들어가길 원했는데 정수형 자료 10이 들어갔다는 것이다.
두번째는 문자열을 처리할 수 있도록 만든 코드라 정수형 자료가 들어가자 형변환이 제대로 되지 않았다.
마지막은 컴파일러는 위와 같은 내용의 오류를 잡아내지 못했다는 것이다.
일반적으로 컴파일러가 이러한 에러를 잡지 못하면 런타임 에러와 프로그램이 멈출 위험이 높아진다.
에러가 생길거라면 차라리 런타임 이전에 컴파일러가 에러를 체크해내는게 낫다.
지네릭스는 이러한 문제점을 해결하기 위해 존재한다.
지네릭스 사용해보기
지네릭스는 위와 같이 사용하면 된다.
위와 같은 위치에 홑화살괄호 <>를 표시하고 그 안에 자료타입을 입력해주면 된다.
이는 괄호 안에 들어간 자료타입만 해당 컬렉션을 이용할 수 있다는 의미다.
위 그림을 해석하면 stringlist라는 ArrayList는 앞으로 String문자열 객체만 저장 가능하다는 뜻이다.
실제로 테스트를 해보자.
이전에 했던 것과 다르게 숫자가 들어가니까 컴파일러가 에러를 체크해낸다.
이것처럼 지네릭스는 컬렉션, 메서드가 받는 자료타입을 제한하여 컴파일러가 에러를 체크하도록 돕는다.
지네릭스와 다형성
이전 단락에서 지네릭스에 대해서 알아볼 때는 String에 관련된 것만 살펴봤다.
그럼 지네릭스는 다른 자료형에도 사용이 가능할까? 당연하다.
그리고 지네릭스를 통해 다형성에 맞는 즉 객체지향적 코드를 짜는 것도 가능하다.
우선 위의 코드를 보도록 하자.
위의 코드는 Aniaml 클래스와 그걸 상속받은 Bird클래스,
그리고 Bird를 상속받은 Sparrow와 Eagle로 구성됐다.
상속 구성도로 보면 대략 이렇다.
위 코드를 보면 animals 리스트의 경우 Animal 객체만 저장가능할 것 같다.
하지만 animal 리스트는 Animal뿐 아니라 상속관계에 있는 3개 클래스의 모든 객체를 저장할 수 있다.
그래서 최종적으로 Animal, Sparrow, Eagle 세 개의 클래스 객체를 추가로 저장 가능하다.
테스트를 직접 해보자.
위에서 이야기한대로 코드를 짰는데, 컴파일 에러가 없는 것을 볼 수 있다.
이것으로 볼 때 지네릭의 자료타입으로 부모 클래스를 선언하면
그 아래 자손 클래스들의 객체도 저장할 수 있음을 알 수 있다.
이런식으로 지네릭스를 이용하더라도 다형성을 이용해 객체 지향적 코드를 짤 수 있다.
당연한 이야기지만 위와 같이 birds는 Bird 클래스 아래 있는 Sparrow와 Eagle 객체는 저장가능하다.
하지만 부모 클래스인 Animal 클래스는 컴파일 오류가 나는 것을 볼 수 있다.
지네릭스 클래스
이전 단락들에서는 지네릭스를 컬렉션에서만 사용해줬다.
하지만 지네릭스는 List, Map과 같은 컬렉션 뿐 아니라 클래스, 메서드에서도 사용이 가능하다.
이번 단락에서는 지네릭스 클래스에 대해서 알아보도록 하자.
예를 들어 위와 같이 Box라는 이름의 클래스를 만들었다고 가정해보자.
Box 클래스는 내부에 ArrayList를 가지고 있다.
그래서 add()메서드를 통해 객체를 전달해주면 해당 ArrayList에 객체를 저장해둔다.
이때 저장하는 객체의 타입이나 반환되는 객체 타입은 아직 정하지 않았기 때문에 Object로 정했다.
하지만 이렇게 지네릭스 없이 클래스를 짤 경우 앞서 말했듯 오류 가능성이 급격히 높아진다.
물론 미리 입력될 타입을 정해서 Object 대신 다른 자료 타입을 입력하면 오류를 줄일 수 있다.
하지만 그렇게 할 경우 코드의 재활용성이 떨어진다.
만약 Box클래스가 String이라는 객체만 받을 수 있게 미리 설정해놨을 때,
Integer 객체를 받는 Box를 만들고 싶다면 Box라는 클래스를 하나 더 만들어야 하기 때문이다.
그래서 지네릭스가 필요한 것이고 이때 또 한가지 알아야 할 개념이 지네릭스의 타입 매개 변수다.
코드를 직접 보면서 이해해보도록 하자.
위 코드는 이전에 작성한 Box클래스에서 지네릭스를 추가하고 타입 매개변수 T를 추가한 코드다.
우선 용어에 대한 설명부터 해보자.
위와 같이 클래스를 만들 때 클래스 명 옆에 지네릭스를 붙이고 선언하면 이것을 지네릭 클래스라 한다.
뜻은 지네릭스를 사용하는 클래스라는 뜻이다.
그리고 이때 지네릭스에 들어가는 자리의 변수를 타입변수, 타입 매개변수라 한다.
그리고 타입변수로서 들어간 문자는 타입 문자라 한다.
마지막으로 원시타입은 일반 클래스를 의미한다.
정리하면 아래 그림과 같다.
일반적인 클래스 선언에 지네릭을 붙이고 안에 타입 변수를 넣어주면 지네릭 클래스가 선언되는 것이다.
이때 이 T가 뭘 뜻하는지 궁금할 것인다. 근데 T는 임의의 문자로서 어떤 걸 넣어도 상관없다.
이전에 List를 선언하면서 지네릭스를 써준 부분을 기억해보자.
당시에 List는 문자열만 저장하길 원해서, <String> 이런식으로 자료타입을 적어준 것도 기억할 것이다.
지네릭 클래스도 이것과 마찬가지다.
<T>는 타입 변수로 이 클래스로 객체를 생성할 때 해당 객체에 어느 자료형이든 저장 가능하단 뜻이다.
그렇다면 지금까지 이야기한 지네릭스의 자료형 제한 기능이 의미가 없어지는 것 아닌가 의심할 수 있다.
하지만 실제로 해당 클래스에 저장될 객체를 결정하는 것은 클래스 내에서 이뤄지는 것이 아니다.
바로 객체를 생성하고 이용할 때 결정되는 것이다.
이해가 안된다면 이제 이 클래스가 사용되는 예시를 보도록 하자.
위에서 이야기한대로 Box의 객체를 생성할 때 지네릭안에 자료타입을 명시해준 것을 알 수 있다.
그리고 해당 객체에 각각 자료타입에 맞는 자료를 add()메서드로 추가하는 것도 볼 수 있다.
이처럼 타입변수 T는 말 그대로 어떤 클래스든 들어갈 수 있다는 뜻으로 알파벳 어느것이든 사용 가능하다.
다시 코드로 돌아가보자.
클래스 안에서 타입 변수 T가 자료타입을 명시해줘야 하는 부분에 대신 들어간 것들을 확인할 수 있다.
이는 객체를 생성할 때 String 등의 자료타입을 입력하면,
입력된 자료타입이 T가 있는 자리를 대체한다는 뜻이다.
결국 이렇게 지네릭 클래스를 만들면 별도의 클래스 생성을 추가로 할 필요 없이,
객체별로 다른 타입의 자료를 받을 수 있게 만들어준다.
지네릭클래스와 다형성
이전에 List를 통해 지네릭을 이용해줄 때 다형성에 대해 공부한 것을 기억할 것이다.
그리고 당연하게도 지네릭 클래스를 이용해줄 때도 이러한 다형성을 이용한 코드 작성이 가능하다.
예를 들어 위와 같은 클래스가 있다고 가정해보고 이 클래스의 객체들을 Box 객체 안에 넣어보자.
지네릭스로 저장 가능 객체 타입을 Fruit로 제한해도,
Fruit는 Apple와 Grape의 부모 클래스이므로 이상없이 저장할 수 있는 것을 볼 수 있다.
당연히 Apple로 저장 가능 타입을 제한하면 형제인 Grape 클래스의 객체는 저장 불가한 것을 볼 수 있다.
'백엔드 > 자바' 카테고리의 다른 글
지네릭스(Generics) - 와일드카드 (0) 2021.05.21 지네릭스(Generics) - 지네릭 extends, 지네릭의 한계 (0) 2021.05.21 컬렉션 - Collections 클래스와 메서드 (0) 2021.05.20 컬렉션 - Arrays 클래스와 메서드 (0) 2021.05.20 컬렉션 - Comparable와 Comparator (0) 2020.08.17