728x90
반응형
제네릭
- Generic 은 코드의 타입을 미리 지정하지 않고, 사용하는 시점에 타입을 결정하는 기능이다.
- 타입 안정성 유지 및 유연한 재사용이 가능한 코드 작성 가능.
- 제네릭을 사용할 경우
- O 가 정확한 객체 타입을 가지도록 강제할 수 있음.
- T 가 필드의 타입과 일치해야만 하도록 강제할 수 있음.
- 컴파일 타입에서 타입 검증이 가능해 런타임 오류를 최소화함.
예제
- obj 가 어떤 타입의 객체인지 알 수 없고, value 가 필드 타입과 일치하는지 보장할 수 없음.
- 잘못된 타입이 들어와도 컴파일러가 체크하지 못하고, 런타임에서만 오류가 발생.
- 제네릭을 사용하면, obj 와 value 가 올바른 타입인지 컴파일 시점에 확인할 수 있음.
fun setPrivateField(obj: Any, fieldName: String, value: Any) { ... }
- 둘다 T 로 하면 안됨. obj 와 value 사이의 관계가 불분명해짐.
fun <T> setPrivateField(obj: T, fieldName: String, value: T) // 문제 발생 가능
- 제네릭 타입을 분리해서 사용하면, 올바른 타입 검증이 가능해짐.
- O: Any -> 객체의 타입(Object) / T -> 필드 값의 타입
- O 는 User, Product 같은 클래스 타입을 나타냄.
- T 는 클래스의 특정 필드 타입(String, Int, Boolean) 등을 의미함.
fun <O: Any, T> setPrivateField(obj: O, fieldName: String, value: T)
✔ name
필드는 String
이므로, value
에도 String
만 허용됨.
✔ age
필드는 Int
이므로, value
에도 Int
만 허용됨.
✔ 잘못된 타입(Int
를 String
필드에 넣으려는 경우) 컴파일 오류 발생 🚀
class User(private var name: String, private var age: Int)
fun main() {
val user = User("Alice", 25)
// ✅ 올바른 타입 사용 → 정상 동작
ReflectionUtil.setPrivateField(user, "name", "Bob")
ReflectionUtil.setPrivateField(user, "age", 30)
// ❌ 잘못된 타입 사용 → 컴파일 오류 발생
// ReflectionUtil.setPrivateField(user, "name", 30) // ❌ String 필드에 Int 할당 → 컴파일 오류!
}
제네릭 타입 명명 규칙과 작명 가이드
제네릭 타입 네이밍 관례
일반적으로 제네릭 타입 파라미터는 한 글자로 표현하는 것이 일반적이며, 의미에 따라 특정 문자나 단어를 사용합니다.
제네릭 명 | 의미 | 예시 |
---|---|---|
T | Type (임의의 타입) | fun <T> doSomething(value: T) |
E | Element (컬렉션 요소) | fun <E> List<E>.findElement() |
K, V | Key, Value (Map에서 사용) | fun <K, V> Map<K, V>.getEntry() |
N | Number (숫자 타입) | fun <N : Number> sum(numbers: List<N>) |
R | Return Type (결과 타입) | fun <R> transform(input: String): R |
O | Object (객체의 타입) | fun <O : Any> setObject(obj: O) |
Object vs inline fun
특징 | object |
inline fun |
---|---|---|
상태 유지 | ✅ 가능 (싱글톤이므로 상태 유지 가능) | ❌ 불가능 (호출될 때마다 실행됨) |
메모리 사용 | 한 번만 로드 (싱글톤) | 불필요한 객체 생성 방지 |
성능 최적화 | ✅ 내부적으로 캐싱 가능 | ✅ 실행 시점에서 인라이닝 (성능 최적화) |
정적 메서드 대체 | ✅ 가능 (object.method() ) |
✅ 가능 (inline fun method() ) |
제네릭 타입 검사 | ✅ 가능 | ✅ reified 키워드 사용 시 가능 |
클래스 확장 가능 여부 | ✅ 가능 (클래스 멤버로 사용 가능) | ❌ 확장이 어려움 |
항목 | object ReflectionUtil |
inline fun |
---|---|---|
불필요한 객체 생성 | ❌ (싱글턴 객체 생성) | ✅ (인스턴스 생성 없음) |
코드 최적화 | ✅ (object 는 한 번만 생성됨) |
✅ (호출마다 인라이닝됨) |
성능 | 일반적인 경우 적절 | 자주 호출되면 성능 향상 가능 |
확장성 | ✅ 여러 유틸 함수 포함 가능 | ❌ 단일 함수로만 사용 |
코드 가독성 | ReflectionUtil.setPrivateField() |
setPrivateField() 직접 호출 가능 |
reified 사용 가능 여부 |
❌ 사용 불가능 | ✅ 사용 가능 |
- relified: 제네릭 타입을 런타임에서 확인할 수 있도록 하는 기능.
- 제네릭의 타입 소거(Type Erasure) 문제 해결 가능.
fun <T> printType() {
println(T::class) // ❌ 오류 발생! 제네릭 타입을 런타임에서 알 수 없음
}
inline fun <reified T> printType() {
println(T::class) // ✅ 정상 작동!
}
fun main() {
printType<String>() // 출력: class kotlin.String
printType<Int>() // 출력: class kotlin.Int
}
Object
- 싱글톤(Singleton) 패턴을 간단하게 구현하는 방법.
- 프로그램 전체에서 단 하나의 인스턴스만 생성되도록 보장하는 객체.
- 클래스 선언 없이 즉시 객체를 생성할 수 있음.
- 반복해서 객체를 생성할 필요가 없음.
- 정적 메서드 및 변수가 필요할 때 사용.
- 클래스 상속 가능.
📌 싱글톤(Singleton):
- 하나의 클래스에 오직 하나의 인스턴스만 존재하도록 하는 디자인 패턴.
- 여러 곳에서 동일한 인스턴스를 공유할 때 사용.
- `object`를 사용하면 직접 싱글턴 패턴을 구현할 필요 없이 자동으로 싱글턴이 생성됨.
object Singleton {
val name = "Singleton Instance"
fun printMessage() {
println("This is a Singleton object.")
}
}
fun main() {
println(Singleton.name) // "Singleton Instance"
Singleton.printMessage() // "This is a Singleton object."
}
inline fun
- 객체를 생성하지 않고도 고차 함수나 유틸 함수를 최적화 할 수 있음.
✅ 장점
- 불필요한 객체 생성을 방지
object
를 사용하면 JVM에서 싱글턴 인스턴스를 만들지만,inline fun
을 사용하면 이런 인스턴스조차 필요 없음.setPrivateField
가 자주 호출되는 경우, 불필요한 메모리 사용을 줄일 수 있음.
- 성능 최적화 (
inline
)- 컴파일 시점에 코드가 인라이닝되어 함수 호출의 오버헤드가 줄어듦.
- 특히 고차 함수에서 성능 향상 효과가 큼.
- 가독성 향상
ReflectionUtil.setPrivateField(...)
대신setPrivateField(...)
형태로 직접 호출 가능하여 가독성이 좋아짐.
reified
키워드로O
의 런타임 타입 검사 가능inline fun
이므로reified
를 사용할 수 있어O::class
를 바로 참조 가능- (일반적인
object
에서는O::class
사용 불가능)
❌ 단점
- 코드 중복 증가
inline
으로 인해 호출하는 곳마다 코드가 복사되므로, 코드 크기가 증가할 수 있음.- 특히
setPrivateField
가 자주 사용된다면object
기반이 더 나을 수도 있음.
- 함수 확장이 어렵다
object
는 여러 메서드를 포함할 수 있지만,inline fun
은 단일 함수로만 사용 가능.- 만약
getPrivateField()
같은 함수도 추가하려면,object
가 더 적합할 수 있음.
inline
은 무조건 성능을 개선하는 것이 아님- 함수 크기가 크다면
inline
을 사용하면 오히려 성능이 저하될 수도 있음. setPrivateField
는 비교적 짧은 함수이므로 성능 저하는 크지 않겠지만, 무조건inline
이 좋은 것은 아님.
- 함수 크기가 크다면
MockMvc
- Spring 의 웹 계층(Web Layer) 를 테스트하는 도구
- 단위 테스트, 통합 테스트 둘 다 사용 가능.
유형 | 설명 | MockMvc 사용 방식 |
---|---|---|
단위 테스트 (Unit Test) | 웹 계층(Controller)만 테스트 | @WebMvcTest 사용 |
통합 테스트 (Integration Test) | 실제 애플리케이션을 포함하여 테스트 | @SpringBootTest + @AutoConfigureMockMvc 사용 |
@Autowired constructor
- MockMvc 객체를 자동으로 주입(inject) 하도록 하는 Kotlin 의 생성자 주입 방식
- Spring 이 자동으로 MockMvc 빈(Bean) 을 찾아 자동으로 객체를 주입함
- Java 에서는 보통 필드 주입을 많이 쓰지만, Kotlin 에서는 생성자 주입이 더 권장되는 스타일.
- @Autowired constructor -> lateinit var 로 변경하여 Kotest 에서 인스턴스 생성 가능하도록 수정할 수 있음.
@WebMvcTest(BusinessUserController::class) class AuthAnnotationTest @Autowired constructor( private val mockMvc: MockMvc ) : DescribeSpec({
🚀 @WebMvcTest
의 특징 (단위 테스트)
항목 | @WebMvcTest (단위 테스트) |
@SpringBootTest (통합 테스트) |
---|---|---|
로딩 범위 | Controller 계층만 | 애플리케이션 전체 |
Service 계층 로드 | ❌ 안 됨 (Mock 필요) | ✅ 실제 빈(Bean) 로드 |
Repository 계층 로드 | ❌ 안 됨 | ✅ 실제 DB 연동 가능 |
Spring Context 크기 | ⚡ 매우 가벼움 | 🐢 무거움 |
테스트 속도 | 🚀 빠름 | 🐌 느림 |
📌 단위 테스트를 원한다면?
@WebMvcTest
를 유지하면서 서비스(Service)를@MockBean
으로 주입해서 사용해야 합니다.
728x90
반응형
'Blog > TIL' 카테고리의 다른 글
2025-01-22 (수) (0) | 2025.01.22 |
---|---|
2025-01-21 (화) (1) | 2025.01.21 |
2025-01-16 (목) (0) | 2025.01.16 |
2025-01-15 (수) (0) | 2025.01.15 |
2025-01-14 (화) (0) | 2025.01.14 |
댓글