제네릭 소개
Coddy GO 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 107개 중 67번째.
Go 1.18 이전에는 다양한 타입을 위한 재사용 가능한 코드를 작성하려면 함수를 중복해서 작성하거나 타입 단언(type assertions)과 함께 빈 인터페이스 any를 사용해야 했습니다. 제네릭은 완전한 타입 안정성을 유지하면서도 여러 타입에서 작동하는 함수와 타입을 작성할 수 있게 함으로써 이 문제를 해결합니다.
슬라이스에서 최솟값을 찾는 경우를 생각해 봅시다. 제네릭이 없다면, 각 타입마다 별도의 함수가 필요할 것입니다:
func MinInt(values []int) int {
min := values[0]
for _, v := range values {
if v < min {
min = v
}
}
return min
}
func MinFloat64(values []float64) float64 {
min := values[0]
for _, v := range values {
if v < min {
min = v
}
}
return min
}제네릭을 사용하면 대괄호 안의 타입 매개변수를 사용하여 로직을 한 번만 작성할 수 있습니다:
func Min[T int | float64](values []T) T {
min := values[0]
for _, v := range values {
if v < min {
min = v
}
}
return min
}[T int | float64]는 int 또는 float64가 될 수 있는 타입 매개변수 T를 선언합니다. 이제 동일한 함수로 Min([]int{3, 1, 2}) 또는 Min([]float64{3.5, 1.2})를 호출할 수 있습니다. 컴파일러는 컴파일 타임에 타입을 확인하므로, 런타임 오버헤드 없이 안전성을 얻을 수 있습니다.
앞으로의 레슨에서 타입 매개변수, 제약 조건, 그리고 제네릭 구조체에 대해 상세히 살펴볼 것입니다.
챌린지
쉬움제네릭을 사용하여 정수형과 부동 소수점 점수 모두에서 작동하는 점수 분석기를 만들어 봅시다! 슬라이스에서 최댓값을 찾을 수 있는 단일 제네릭 함수를 생성하여, 서로 다른 숫자 유형에 대해 중복 코드를 작성할 필요를 없앨 것입니다.
코드는 두 개의 파일로 구성됩니다:
analyzer.go: 제네릭 분석 함수를 정의합니다.슬라이스에서 최댓값을 찾아 반환하는 제네릭 함수
Max[T int | float64](values []T) T를 만드세요. 이 함수는 타입 매개변수T를 통해int와float64타입 모두에서 작동해야 합니다.또한 슬라이스의 모든 값의 합계를 계산하여 반환하는 제네릭 함수
Sum[T int | float64](values []T) T를 만드세요.main.go: 입력을 읽고 서로 다른 타입을 사용하여 제네릭 함수를 시연합니다.타입 표시자(
int또는float)를 읽은 다음, 개수와 그 개수만큼의 값을 읽습니다. 타입에 따라 값을 파싱하고, 적절한 타입으로Max와Sum을 모두 호출한 후 결과를 출력하세요.정수 입력의 경우, 다음과 같이 출력합니다:
Max (int): [value] Sum (int): [value]부동 소수점 입력의 경우, 소수점 첫째 자리까지 출력합니다:
Max (float): [value] Sum (float): [value]
다음과 같은 입력이 제공됩니다:
- 1행: 타입 표시자 (
int또는float) - 2행: 값의 개수 (정수)
- 다음 행들: 한 줄에 하나의 값
예를 들어, 다음과 같이 주어지면:
int
4
15
8
23
11출력은 다음과 같아야 합니다:
Max (int): 23
Sum (int): 57그리고 다음과 같이 주어지면:
float
3
4.5
9.2
6.8출력은 다음과 같아야 합니다:
Max (float): 9.2
Sum (float): 20.5동일한 제네릭 함수가 정수와 부동 소수점 데이터를 모두 처리하는 방식에 주목하세요. 타입 매개변수 [T int | float64]를 사용하면 로직을 한 번만 작성해도 컴파일러가 각 사용 사례에 맞는 타입 안전한 코드를 생성할 수 있습니다.
치트 시트
제네릭을 사용하면 타입 안전성을 유지하면서 여러 타입에서 작동하는 재사용 가능한 코드를 작성할 수 있습니다. Go 1.18 이전에는 각 타입마다 별도의 함수가 필요하거나 타입 단언(type assertion)과 함께 빈 인터페이스를 사용해야 했습니다.
타입 매개변수는 대괄호 안에 선언되며 제네릭 함수가 허용할 수 있는 타입을 지정합니다:
func Min[T int | float64](values []T) T {
min := values[0]
for _, v := range values {
if v < min {
min = v
}
}
return min
}[T int | float64] 구문은 int 또는 float64가 될 수 있는 타입 매개변수 T를 선언합니다. 파이프 | 연산자는 허용되는 타입들을 나열합니다.
서로 다른 타입을 사용하여 제네릭 함수를 호출할 수 있습니다:
Min([]int{3, 1, 2}) // int와 함께 작동함
Min([]float64{3.5, 1.2}) // float64와 함께 작동함컴파일러는 컴파일 타임에 타입 체크를 수행하여 런타임 오버헤드 없이 안전성을 제공합니다.
직접 해보기
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// 타입 표시자 읽기
typeStr, _ := reader.ReadString('\n')
typeStr = strings.TrimSpace(typeStr)
// 개수 읽기
countStr, _ := reader.ReadString('\n')
count, _ := strconv.Atoi(strings.TrimSpace(countStr))
if typeStr == "int" {
// 정수 값 읽기
values := make([]int, count)
for i := 0; i < count; i++ {
line, _ := reader.ReadString('\n')
values[i], _ = strconv.Atoi(strings.TrimSpace(line))
}
// TODO: 정수 슬라이스로 Max 및 Sum 호출
// TODO: "Max (int): [value]" 및 "Sum (int): [value]" 형식으로 결과 출력
} else if typeStr == "float" {
// 실수 값 읽기
values := make([]float64, count)
for i := 0; i < count; i++ {
line, _ := reader.ReadString('\n')
values[i], _ = strconv.ParseFloat(strings.TrimSpace(line), 64)
}
// TODO: float64 슬라이스로 Max 및 Sum 호출
// TODO: 소수점 첫째 자리까지 "Max (float): [value]" 및 "Sum (float): [value]" 형식으로 결과 출력
}
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.
객체 지향 프로그래밍의 모든 레슨
8에러 처리와 OOP
error 인터페이스사용자 정의 에러 타입에러 래핑 (fmt.Errorf)센티넬 에러errors.Is()와 errors.As()Panic, Defer, Recover요약 - 파일 파서