Genéricos e parâmetros de tipo
Compreenda como escrever classes e métodos genéricos para criar componentes reutilizáveis com segurança de tipos.
Genéricos e parâmetros de tipo é uma aula grátis de Scala for Backend Engineering & Functional Programming no CoddyKit. Esta é a aula 1 de 3. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de Scala for Backend Engineering & Functional Programming, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de Scala for Backend Engineering & Functional Programming inclui 3 aulas no total.
O que são genéricos?
Imagine que você precise criar um contêiner capaz de armazenar qualquer tipo de item, como uma String, um Int ou um objeto personalizado. Sem genéricos, você teria de escrever um contêiner separado para cada tipo, gerando código repetitivo, ou usar um tipo muito abrangente, como Any.
Os genéricos permitem escrever código flexível e reutilizável que funciona com vários tipos de dados, mantendo a segurança de tipos em tempo de compilação. É como uma planta na qual você pode encaixar materiais diferentes mais tarde!
A armadilha do tipo `Any`
Usar Any como espaço reservado para tipos pode causar problemas. Embora isso permita armazenar qualquer coisa, você perde as informações específicas do tipo. Isso significa que muitas vezes é necessário fazer uma conversão de tipo do item de volta para seu tipo original, o que está sujeito a erros e pode causar falhas em tempo de execução se a conversão estiver incorreta.
Experimente executar este exemplo e reflita sobre os possíveis problemas:
class ItemHolder(val item: Any)
object Main {
def main(args: Array[String]): Unit = {
val stringHolder = new ItemHolder("Hello Scala")
val numberHolder = new ItemHolder(123)
// We need to cast, and it's unsafe if we get it wrong
val retrievedString = stringHolder.item.asInstanceOf[String]
println(s"Retrieved String: $retrievedString")
// If we uncomment the line below, it compiles but will crash at runtime!
// val wrongType = numberHolder.item.asInstanceOf[String]
// println(s"Wrong Type: $wrongType")
}
}Apresentando os parâmetros de tipo
Os genéricos resolvem a armadilha do tipo Any ao introduzir parâmetros de tipo. Eles funcionam como espaços reservados para tipos reais que você especifica ao criar uma instância de uma classe ou chamar um método.
Em Scala, os parâmetros de tipo são definidos usando colchetes ([]) depois do nome da classe ou do método, normalmente com letras maiúsculas únicas, como T, A, B etc. Por exemplo, class Box[T] declara uma classe genérica Box que recebe um parâmetro de tipo T.
Criando um Box genérico
Vamos criar uma classe genérica simples chamada Box, capaz de armazenar qualquer tipo de conteúdo. Observe como T é usado em toda a definição da classe para representar o tipo do conteúdo.
Isso nos permite criar caixas para strings, inteiros ou qualquer outro tipo sem escrever classes separadas para cada um.
class Box[T](val content: T) {
def get: T = content
override def toString: String = s"Box($content)"
}
object Main {
def main(args: Array[String]): Unit = {
val stringBox = new Box("Scala is fun!")
val intBox = new Box(42)
val booleanBox = new Box(true)
println(stringBox)
println(intBox)
println(booleanBox)
}
}Segurança de tipos com genéricos
A maior vantagem dos genéricos é a segurança de tipos em tempo de compilação. Quando você cria um Box[String], o compilador sabe que ele deve conter apenas strings. Se você tentar inserir um inteiro nele ou recuperá-lo como um inteiro, o compilador identificará o erro imediatamente.
Isso evita muitos erros comuns em tempo de execução que ocorrem em abordagens menos seguras quanto aos tipos, como o uso de Any.
class Box[T](val content: T) {
def get: T = content
}
object Main {
def main(args: Array[String]): Unit = {
val myStringBox: Box[String] = new Box("CoddyKit")
val myIntBox: Box[Int] = new Box(100)
// This works, type is String
val s: String = myStringBox.get
println(s"String from box: $s")
// This works, type is Int
val i: Int = myIntBox.get
println(s"Int from box: $i")
// The compiler prevents this line from compiling:
// val wrong: String = myIntBox.get // Type mismatch error!
}
}Genéricos também para métodos!
Assim como as classes, métodos individuais também podem ser genéricos. Um método genérico pode receber parâmetros de tipo locais a esse método, permitindo que ele opere sobre tipos diferentes sem que a classe inteira precise ser genérica.
Isso é útil quando você tem uma operação específica que pode ser aplicada a vários tipos, mas a própria classe não é um contêiner genérico.
Um método `printPair` flexível
Aqui está um exemplo de método genérico printPair. Ele recebe dois parâmetros de tipo, A e B, o que permite exibir um par formado por quaisquer dois tipos. Observe como os parâmetros de tipo são declarados logo depois do nome do método.
Isso torna o método altamente reutilizável para diferentes combinações de dados.
object Util {
def printPair[A, B](first: A, second: B): Unit = {
println(s"Pair: ($first, $second)")
println(s"Type of first: ${first.getClass.getName}")
println(s"Type of second: ${second.getClass.getName}")
}
}
object Main {
def main(args: Array[String]): Unit = {
Util.printPair("Hello", 123)
Util.printPair(true, List(1, 2, 3))
Util.printPair(4.5, 'C')
}
}Inferência de tipos inteligente
Um dos recursos poderosos de Scala é a inferência de tipos. Ao usar classes ou métodos genéricos, muitas vezes você não precisa especificar explicitamente os parâmetros de tipo. O compilador de Scala é inteligente o suficiente para deduzi-los com base nos argumentos fornecidos.
Isso torna o código mais conciso e fácil de ler sem sacrificar a segurança de tipos.
class Container[T](val item: T) {
def getItem: T = item
}
object Main {
def main(args: Array[String]): Unit = {
// Scala infers T as String
val stringContainer = new Container("Inferred String")
println(s"Content: ${stringContainer.getItem}, Type: ${stringContainer.getItem.getClass.getName}")
// Scala infers T as Double
val doubleContainer = new Container(3.14)
println(s"Content: ${doubleContainer.getItem}, Type: ${doubleContainer.getItem.getClass.getName}")
// You can also specify explicitly, but often not needed
val explicitIntContainer: Container[Int] = new Container(500)
println(s"Content: ${explicitIntContainer.getItem}, Type: ${explicitIntContainer.getItem.getClass.getName}")
}
}Restringindo tipos genéricos com limites
Às vezes, você quer que uma classe ou um método genérico funcione apenas com tipos que tenham determinadas capacidades. Por exemplo, um método que soma números deve aceitar somente tipos numéricos.
Os limites de tipo permitem restringir os tipos que podem ser usados como parâmetros de tipo. Um limite superior (<:) significa que o parâmetro de tipo deve ser um subtipo de um determinado tipo. Por exemplo, [T <: Animal] significa que T deve ser Animal ou um subtipo de Animal.
class Animal { def speak(): String = "..." }
class Dog extends Animal { override def speak(): String = "Woof!" }
class Cat extends Animal { override def speak(): String = "Meow!" }
// This box can only hold types that are Animal or its subtypes
class AnimalShelter[T <: Animal](val animal: T) {
def getAnimalSound: String = animal.speak()
}
object Main {
def main(args: Array[String]): Unit = {
val dogShelter = new AnimalShelter(new Dog())
println(s"Dog says: ${dogShelter.getAnimalSound}")
val catShelter = new AnimalShelter(new Cat())
println(s"Cat says: ${catShelter.getAnimalSound}")
// This would NOT compile because Car is not an Animal:
// class Car
// val carShelter = new AnimalShelter(new Car())
}
}Desafio: classe genérica
É hora de verificar sua compreensão sobre genéricos!
Leia atentamente as afirmações a seguir e selecione todas as que forem verdadeiras sobre classes e métodos genéricos em Scala.
Genéricos: principais conclusões
Nesta lição, exploramos os genéricos de Scala, um recurso poderoso para escrever código flexível e seguro quanto aos tipos.
- Genéricos permitem que classes e métodos operem sobre tipos diferentes.
- Eles usam parâmetros de tipo (por exemplo,
[T]) como espaços reservados para tipos reais. - Os genéricos fornecem segurança de tipos em tempo de compilação, evitando erros em tempo de execução.
- A inferência de tipos de Scala geralmente torna o código genérico mais conciso.
- Os limites de tipo (por exemplo,
<:) podem restringir os parâmetros de tipo a subtipos específicos.
Ao usar genéricos, você pode escrever componentes reutilizáveis, robustos e adaptáveis a vários tipos de dados, tornando seu código Scala mais poderoso e menos sujeito a erros.
Perguntas Frequentes
A aula “Genéricos e parâmetros de tipo” é grátis?
Sim — o texto completo de “Genéricos e parâmetros de tipo” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de Scala for Backend Engineering & Functional Programming, atualize para CoddyKit PRO. O curso de Scala for Backend Engineering & Functional Programming inclui 3 aulas no total.
O que vou aprender em “Genéricos e parâmetros de tipo”?
Compreenda como escrever classes e métodos genéricos para criar componentes reutilizáveis com segurança de tipos. Você pratica Scala for Backend Engineering & Functional Programming com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.
Preciso ter experiência prévia para começar Scala for Backend Engineering & Functional Programming?
Nenhuma experiência prévia é necessária. Scala for Backend Engineering & Functional Programming no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 1 de 3.
Quanto tempo leva a aula “Genéricos e parâmetros de tipo”?
A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.
Posso escrever e executar código nesta aula de Scala for Backend Engineering & Functional Programming?
Sim. Cada aula de Scala for Backend Engineering & Functional Programming inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.
Todas as aulas deste curso
- Genéricos e parâmetros de tipo
- Variância: covariância e contravariância
- Classes de tipo e implicits