Introdução a Generics
Parte da seção Programação Orientada a Objetos do Journey de GO da Coddy — lição 67 de 107.
Antes do Go 1.18, escrever código reutilizável para diferentes tipos significava duplicar funções ou usar a interface vazia any com asserções de tipo. Generics resolvem esse problema permitindo que você escreva funções e tipos que funcionam com múltiplos tipos, mantendo total segurança de tipos.
Considere encontrar o valor mínimo em um slice. Sem generics, você precisaria de funções separadas para cada tipo:
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
}Com genéricos, você escreve a lógica uma única vez usando um parâmetro de tipo entre colchetes:
func Min[T int | float64](values []T) T {
min := values[0]
for _, v := range values {
if v < min {
min = v
}
}
return min
}O [T int | float64] declara um parâmetro de tipo T que pode ser tanto int quanto float64. Agora você pode chamar Min([]int{3, 1, 2}) ou Min([]float64{3.5, 1.2}) com a mesma função. O compilador verifica os tipos em tempo de compilação, garantindo segurança sem sobrecarga em tempo de execução.
Nas próximas lições, exploraremos parâmetros de tipo, restrições e structs genéricas em detalhes.
Desafio
FácilVamos construir um analisador de pontuação que funcione com pontuações de números inteiros e de ponto flutuante usando generics! Você criará uma única função genérica que pode encontrar o valor máximo em um slice, eliminando a necessidade de código duplicado para diferentes tipos numéricos.
Você organizará seu código em dois arquivos:
analyzer.go: Defina sua função de análise genérica.Crie uma função genérica
Max[T int | float64](values []T) Tque encontra e retorna o valor máximo em um slice. A função deve funcionar com os tiposintefloat64através do parâmetro de tipoT.Também crie uma função genérica
Sum[T int | float64](values []T) Tque calcula e retorna a soma de todos os valores em um slice.main.go: Leia a entrada e demonstre suas funções genéricas com diferentes tipos.Leia um indicador de tipo (
intoufloat), depois leia uma contagem seguida por essa quantidade de valores. Analise os valores de acordo com o tipo, chame tantoMaxquantoSumcom o tipo apropriado e imprima os resultados.Para entrada de inteiros, imprima:
Max (int): [value] Sum (int): [value]Para entrada de float, imprima com uma casa decimal:
Max (float): [value] Sum (float): [value]
As seguintes entradas serão fornecidas:
- Linha 1: Indicador de tipo (
intoufloat) - Linha 2: Contagem de valores (inteiro)
- Linhas seguintes: Um valor por linha
Por exemplo, dado:
int
4
15
8
23
11Sua saída deve ser:
Max (int): 23
Sum (int): 57E dado:
float
3
4.5
9.2
6.8Sua saída deve ser:
Max (float): 9.2
Sum (float): 20.5Observe como as mesmas funções genéricas lidam com dados inteiros e de ponto flutuante—o parâmetro de tipo [T int | float64] permite que o compilador gere código com segurança de tipo para cada uso, enquanto você escreve a lógica apenas uma vez.
Folha de consulta
Generics permitem que você escreva código reutilizável que funciona com múltiplos tipos, mantendo a segurança de tipos. Antes do Go 1.18, você precisava de funções separadas para cada tipo ou usava a interface vazia com asserções de tipo.
Parâmetros de tipo são declarados em colchetes e especificam quais tipos uma função genérica pode aceitar:
func Min[T int | float64](values []T) T {
min := values[0]
for _, v := range values {
if v < min {
min = v
}
}
return min
}A sintaxe [T int | float64] declara um parâmetro de tipo T que pode ser int ou float64. O operador pipe | lista os tipos permitidos.
Você pode chamar funções genéricas com diferentes tipos:
Min([]int{3, 1, 2}) // Funciona com int
Min([]float64{3.5, 1.2}) // Funciona com float64O compilador realiza a verificação de tipos em tempo de compilação, fornecendo segurança sem sobrecarga em tempo de execução.
Experimente você mesmo
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Ler o indicador de tipo
typeStr, _ := reader.ReadString('\n')
typeStr = strings.TrimSpace(typeStr)
// Ler a contagem
countStr, _ := reader.ReadString('\n')
count, _ := strconv.Atoi(strings.TrimSpace(countStr))
if typeStr == "int" {
// Ler valores inteiros
values := make([]int, count)
for i := 0; i < count; i++ {
line, _ := reader.ReadString('\n')
values[i], _ = strconv.Atoi(strings.TrimSpace(line))
}
// TODO: Chamar Max e Sum com o slice de inteiros
// TODO: Imprimir resultados no formato "Max (int): [value]" e "Sum (int): [value]"
} else if typeStr == "float" {
// Ler valores float
values := make([]float64, count)
for i := 0; i < count; i++ {
line, _ := reader.ReadString('\n')
values[i], _ = strconv.ParseFloat(strings.TrimSpace(line), 64)
}
// TODO: Chamar Max e Sum com o slice de float64
// TODO: Imprimir resultados com uma casa decimal no formato "Max (float): [value]" e "Sum (float): [value]"
}
}
Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Programação Orientada a Objetos
1Fundamentos de OOP em Go
Arquivos ExternosGo Workspace e MódulosPacotes e ImportaçõesNomes Exportados vs Não ExportadosIntrodução a OOP em GoStructs como ClassesDefinindo Métodos em StructsRecebedores de Ponteiro vs ValorInicialização de StructsFunções ConstrutorasRecapitulação - Calculadora Simples4Interfaces
Introdução a InterfacesImplementação ImplícitaInterface como ContratoInterface Vazia (any)Type AssertionType SwitchComposição de InterfacesInterfaces Stringer & ErrorRecapitulação - Calculadora de Formas7Encapsulamento
Campos Exportados vs Não ExportadosEncapsulamento em Nível de PacoteMétodos Getter e SetterOcultação de Informação em GoRecapitulação - Registros de Estudantes10Generics (Go 1.18+)
Introdução a GenericsParâmetros de TipoRestrições de TipoStructs GenéricasSolução Alternativa para Métodos GenéricosRecapitulação - Coleção Genérica2Mergulho Profundo em Tipos e Structs
Tipos Básicos e CompostosDefinições de Tipos PersonalizadosStruct TagsStructs AnônimasStructs AninhadasZero Values e PadrõesRecapitulação - Agenda de Contatos5Composição em vez de Herança
Por que Go não possui HerançaFundamentos de Incorporação de StructsPromoção de MétodosIncorporando Múltiplas StructsIncorporação vs AgregaçãoShadowing de Métodos IncorporadosRecapitulação - Hierarquia de Funcionários8Tratamento de Erros e OOP
A Interface errorTipos de Erro PersonalizadosError Wrapping (fmt.Errorf)Erros Sentinelaerrors.Is() e errors.As()Panic, Defer e RecoverRecapitulação - File Parser3Ponteiros e Memória
Fundamentos de Ponteiros em GoPonteiros para StructsPassagem por Valor vs ReferênciaA Função new()Garbage Collection em GoRecapitulação - Construtor de Lista Encadeada6Polimorfismo em Go
Polimorfismo via InterfacesDuck Typing em GoRegras de Satisfação de InterfaceColeções PolimórficasInjeção de DependênciaRecapitulação - Processador de Pagamentos9Concorrência e OOP
Fundamentos de GoroutinesCanais e ComunicaçãoCanais com Buffer vs. Sem BufferInstrução Selectsync.Mutex e sync.RWMutexsync.WaitGroupDesign de Structs Thread-SafeRecapitulação - Worker Pool