제네릭메서드나 클래스에 컴파일 시 타입 체크를 해주는 기능으로 컴파일 단계에서 타입 체크를 하기 때문에 객체의 타입 안정성을 높이고 형변환의 번거로움을 줄여준다.

 

타입 안정성을 높인다는 것은 의도치 않은 타입의 객체가 저장되는 것을 막고, 형변환의 오류를 줄여주는 것이다.

 

 

class Box <T> {
    T item;
    
    void setItem(T item) {
        this.item = item;
    }
    
    T getItem() {
        return item;
    }
}

T 를 타입 변수(타입 매개변수)라고 하는데 여기서 T는 'Type'의 T를 뜻하며, 요소의 경우 'Element'의 첫 글자인 'E'로 표현할 수도 있다. 좌표를 보통 x,y로 나타내는 것처럼 꼭 T, E, K 이런게 정해져 있는것은 아니지만 코드를 이해하는데 있어 맞추는 것이 중요하다.

기호의 종류만 다를 뿐 임의의 참조형 타입을 의미한다는 것은 같다.

 

제네릭에서 타입 변수를 사용하지 않는 경우에도 문제가 생기진 않는데 이는 호환성으로 인해 가능하지만 주의하는 것이 좋다고 한다.

 

 

[Java] 제네릭에서 Raw Type 선언 주의 (이펙티브 자바)

'Raw use of parameterized class 'List' 라는 경고를 보고 Raw use가 무엇인지 찾아봤는데 이펙티브 자바에 있는 내용이라 아직 초반부를 보고 있지만 해당 부분을 읽어보았다. 제네릭 타입은 일련의 매개변

treecode.tistory.com

 

Box <String>으로 선언을 하면 타입 매개변수에 String이 입력이 돼서 컴파일 후에 원시 타입인 Box로 바뀌게 되고 제네릭 타입은 제거되며 타입 매개변수의 'T'가 String으로 치환이 되고 적절하게 형변환이 된다. 

 

class FrutBox<T extends Fruit> {
    ArrayList<T> list = new ArrayList<T>();
    ...
}

 

 그냥 T 만 사용하면 모든 타입이 들어올 수 있는데 위와 같이 제네릭 타입에 extends를 사용하면 특정 타입의 자손들만 대입할 수 있도록 제한할 수 있다. (인터페이스의 경우에도 extends)

 

 

제네릭과 와일드 카드 <?>

 

제네릭 타입은 컴파일러가 컴파일할 때만 사용하고 제거하기 때문에 제네릭 타입을 다르게 하는 것으로는 오버로딩이 성립하지 않는다. 이럴 때는 와일드 카드 '?'를 사용하면 되는데 다음과 같이 사용한다.

 

1) <? extends T> 상한 제한, T와 그 자손들만 가능

2) <? super T> 하한 제한, T와 그 조상들만 가능

3) <?> 모든 타입 가능

 

제네릭 타입을 클래스 말고 메서드에 선언한 것을 제네릭 메서드라고 하는데 이번에 글로 정리하게 된 이유이다.

 

 

제네릭 메서드

 

제네릭 메서드는 말 그대로 메서드에 제네릭을 선언하는 것으로 클래스에 정의된 타입 매개변수와 제네릭 메서드에 정의된 타입 매개변수는 서로 별개의 것인데 제네릭 메서드는 제네릭 클래스가 아니더라도 정의할 수 있다.

 

제네릭 메서드에 선언된 타입 매개변수는 해당 메서드 내에서만 지역적으로 사용이 되기 때문에 지역 변수를 선언한 것과 같다고 생각해도 된다. (타입 매개변수는 인스턴스 변수로 간주되기 때문에 원래 static 멤버에는 사용할 수 없지만, 메서드에 제네릭 타입을 선언하고 사용하는 것은 가능하다.)

(-> 그렇구나.. 하긴 했지만 아직 정확히 이해는 안됨)

 

static <T extends Fruit> Juice makeJuice(FruitBox<T> box)

 

제네릭 메서드를 호출할 때는 메서드 앞에 타입을 대입해야 하지만 대부분 컴파일러가 타입을 추정할 수 있기 때문에 생략해도 된다.

 

Juicer.<Fruit>makeJuice(fruitBox);

 

책에 밑줄이 그어져 있는 것으로 보아 이전에도 보긴 했었는데 확실히 보기만 하고 실제로 사용을 안하니 금방 까먹고 이해도 잘 안되는 것 같다.  공부하고 있는 프로젝트의 코드를 다시 보았다.

 

 

해당 메서드는 Api 응답을 만드는 메서드로 매개변수에 어떤 클래스가 들어올 지 몰라서 매개변수 타입을 제네릭 타입 변수 'T'로 선언을 해야 한다. 원래 static 메서드에는 타입 변수 'T' 를 선언할 수 없지만 제네릭 메서드로 선언을 하면 매개변수에 타입 변수를 사용할 수 있다.

 

처음에 T가 3개씩 보여서 제네릭 모른다고 당황했었는데 이렇게 다시 보니 이해가 좀 된다.

 

 

문제의 코드인데 ApiResult를 builder로 생성해서 반환하니 incompatible types 에러가 떠서 어리둥절 했었다. 

 

롬복의 builder()도 제네릭 메서드로 선언이 되어 있는데 제네릭 메서드를 호출할 때는 원래 메서드 앞에 타입 변수를 대입해야하지만 대부분 컴파일러가 타입을 추정할 수 있기 때문에 생략을 하고 사용한다. 하지만 아래와 같이 제네릭 클래스에서 builder()를 사용하는 경우 타입 변수를 생략하면 Object로 반환이 되어서 명시적으로 타입 선언을 해줘야 한다.

 

 

 

 

[참고]

자바의 정석

 

자바 제네릭(Generics) 기초

tecoble.techcourse.co.kr

 

롬복(Lombok)이란 애노테이션 기반으로 코드를 자동 완성해주는 라이브러리로 Getter, Setter, 생성자 코드등을 @Getter, @Setter, @RequiredArgsConstructor 와 같은 애노테이션으로 사용한다.

 

반복되는 코드를 줄여 생산성, 가독성 여러 면에서 엄청 편리해서 기본으로 깔고 가는 라이브러리인데 @Setter나 @Setter외 여러 애노테이션이 포함된 @Data의 경우에는 의도치 않는 값의 변경 등 안정성 면에서 권장하지 않아 사용하지 않았다.

 

 

[Java] Lombok이란? 및 Lombok 활용법

Java 언어를 이용해 개발을 하다 보면 기계적으로 작성해야 하는 코드들이 상당히 많다. 이번에는 기계적인 코드 작성을 자동화하여 코드 다이어트를 해주는 Java 필수 라이브러리 Lombok에 대해 알

mangkyu.tistory.com

 

@RequiredArgsConstructor 애노테이션은 final 또는 @NotNull이 붙은 필드에 대해 생성자를 생성해줘서 의존성 주입을 위해 항상 사용해왔다. (스프링은 특정 빈에 생성자가 하나만 있고, 생성자의 파라미터 타입이 빈으로 등록 가능한 경우 @Autowired 애노테이션이 없어도 의존성 주입이 가능하다.)

 

하지만 @AllArgsConstructor, @RequiredArgsConstructor 를 사용할 때 선언된 필드의 순서를 바꾸는 경우 자동으로 생성되는 생성자의 파라미터 순서도 바뀌어 버리기 때문에 의도치 않은 문제가 발생할 수 있다. 실제로 코드를 짜다 보면 필드 순서가 맘에 안 들어서 바꿀 때가 많은데 이런 위험은 생각을 못하고 있었다.

 

테스트 코드를 작성하면서도 여러모로 빌더 패턴의 유용함을 많이 느끼고 있는데 생성자를 직접 만들고 @Builder 애노테이션을 사용하는 방식으로 코드를 짜는 것이 좋을 것 같다.

 

 

 

java:lombok:pitfall [권남]

 

kwonnam.pe.kr

 

디자인 패턴은 소프트웨어 설계 시 특정 상황에서 자주 만나는 문제를 해결하기 위해 사용할 수 있는 재사용 가능한 솔루션으로 디자인 패턴에서 중요한 것은 각 패턴의 핵심이 담긴 목적 또는 의도이다.

 

1. 패턴을 적용할 상황

2. 해결해야 될 문제

3. 핵심 의도가 무엇인지

 

 

메서드를 추상 메서드(abstaract method)로 선언해서 자식 클래스가 이를 상속 받아 메서드를 구현하도록 하면 클래스 계층구조를 통해 관심을 분리할 수 있다. (DB의 커넥션 연결 등과 SQL, DB 접근 등의 로직을 분리)

 

상속을 통해 기능을 확장하는 방법으로 사용되는 2가지 패턴이 있는데 먼저 템플릿 메서드 패턴(template method pattern)은 부모 클래스에서 자주 변경되거나 확장할 기능을 추상 메서드나 오버라이드 가능한 메서드를 정의해둔 템플릿 메서드를 만들어서 자식 클래스에서 해당 메서드를 구현하도록 하는 패턴이다. (훅(Hook) 메서드 : 부모 클래스에서 디폴트 기능을 정의해두거나 비워뒀다가 자식 클래스에서 오버라이드할 수 있도록 만들어둔 메서드)

팩토리 메서드 패턴(factory method pattern)자식 클래스에서 객체 생성 방법과 클래스를 결정할 수 있도록 미리 정의해둔 메서드(팩토리 메소드)를 통해 객체의 생성을 부모 클래스의 기본 코드에서 독립시키는 패턴이다.

 

관심을 분리한다는 것은 내부 동작에 상관없이 관심을 두지 않고 필요한 기능만 가져다 사용한다는 것이다. 

 

템플릿 메서드 패턴, 팩토리 메서드 패턴을 사용하면 관심 사항이 다른 코드를 분리해내고, 서로 독립적으로 변경, 확장을 할 수 있다. 하지만 상속을 사용하기 때문에 클래스의 다중 상속이 허용되지 않는 자바에서는 비용이 큰 편이고, 결합력이 높기 때문에 관심 사항은 분리했더라도 변경 시 변화의 파급력이 큰 편이다. 그래서 토비의 스프링 책에서는 인터페이스를 사용하여 분리를 하는 방식을 권장하는데 인터페이스를 사용하면 다형성으로 인해 의존관계를 외부에서 주입하고 클래스에서는 인터페이스 타입의 참조 변수를 통해 구현체의 메서드를 사용하면 되기 때문에 완벽하게 분리를 할 수 있다. 구현체가 변경이 되더라도 외부에서 변경된 구현체로 주입을 해주면 된다.

 

이렇게 보면 인터페이스를 사용하면 될 것 같은데 템플릿 메서드 패턴을 왜 쓰는지, 어떤 상황에 쓰이는지 잘 모르겠다. 다른 블로그를 찾아보다보니 템플릿 메서드 패턴과 비슷하면서 상속의 단점을 제거할 수 있는 전략 패턴이 있다고 하는데 인프런 김영한님의 스프링 고급편에도 템플릿 메서드 패턴에 대한 목차가 있어서 강의를 통해 더 깊게 공부해보고 코드로 사용해보면서 정리를 해봐야겠다.

 

 

 

 

[참고]

토비의 스프링

 

Overriding in Java - GeeksforGeeks

A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.

www.geeksforgeeks.org

 

 

팩토리 메서드, 템플릿 메서드 패턴

1. 팩토리 메서드 패턴 팩토리 메서드 패턴(Factory Method Pattern)이란 객체 생성을 하는 클래스를 따로 두는 것입니다. 그래서 공장(Factory)이라는 표현을 쓰나봅니다. 🙂 실질적인 클래스의 구현은

western-sky.tistory.com

 

 

Template Method (템플릿 메서드) 패턴

1. Overview 코드를 작성하다보면 로깅, 예외 처리 등등 반복되어 작성하는 코드가 발생합니다. 이런 경우 코드의 중복을 없애기 위한 패턴 중 하나가 템플릿 메서드 패턴입니다. AbstractClass (추상

bcp0109.tistory.com

 

면접에서 인터페이스에 대해 물어보는 질문에 당연히 알고 있다고 생각했는데 막상 말하려고 하니 제대로 떠오르지가 않아서 아쉬움이 있었다. 앞으로 공부를 할 때는 이걸 왜 사용하는지 어떤 이점이 있는지 이런 부분을 좀 더 생각하고 정리해야겠다.

 

먼저 인터페이스를 알기 위해서는 추상 클래스에 대해 알아야 하는데 추상 클래스(abstract class)란 미완성 클래스로 미완성 상태의 추상 메서드를 포함하고 있는 것이다. 추상 메서드는 abstract 키워드를 사용하며 구현 코드 없이 선언부만 존재한다. 추상적인 클래스(설계도)이기 때문에 자체로 인스턴스를 생성할 수 없고 이를 상속 받은 자식 클래스는 반드시 추상 메서드를 구현해야한다. 

 

추상 메서드를 선언하는 이유는 자식 클래스에게 구현을 강요하기 위함으로 추상 메서드를 구현하지 않으면 에러가 발생하기 때문에 실수를 방지할 수 있고 추상화를 통해 확장성을 넓힐 수 있다. 그리고 추상 클래스를 상속 받은 자식 클래스를 부모 타입의 참조 변수로 참조하면 여러 자식 클래스를 하나의 배열로 다룰 수도 있다.

 

인터페이스란?

 

인터페이스는 일종의 추상 클래스로 추상 클래스보다 추상화 정도가 더 높으며 추상 메서드와 상수로 이루어져 있다. (JDK 1.8부터는 인터페이스에 추상메서드 외 static, default 메서드도 선언이 가능하다.)

 

인터페이스는 다음과 같은 제약이 있다.

 

1. 멤버 변수는 public static final이어야 하며 생략이 가능하다.

2. 메서드는 public abstract이어야 하며, 생략이 가능하다. 

 

인터페이스는 왜 사용하는가?

 

인터페이스를 사용하면 클래스 사이(객체 사이)에 새로운 관계를 맺어 줄 수 있고 다형성을 통해 의존관계를 추상화하여 런타임 시점에 외부에서 객체를 생성하고 주입할 수 있다. 이로 인해 클래스 간에 결합도를 낮출 수 있고 객체 지향 프로그래밍의 설계 원칙인 SOLID 5원칙 중 단일 책임 원칙, 개방 폐쇄 원칙, 의존관계 역전 원칙을 지킬 수 있다.

 

- 단일 책임 원칙(SRP, 하나의 클래스는 하나의 책임만 가진다)

- 개방 폐쇄 원칙(OCP, 변경에는 닫혀있고 확장에는 열려 있어야 한다)

- 의존관계 역전 원칙(DIP, 구체화가 아닌 추상화에 의존해야한다)

 

 

 

 

[참고]

자바의 정석

이것이 자바다

https://tecoble.techcourse.co.kr/post/2021-04-27-dependency-injection/

http://www.tcpschool.com/java/java_polymorphism_interface

+ Recent posts