0Pricing
Scala for Backend Engineering & Functional Programming · Lección

Genéricos y parámetros de tipo

Comprenda cómo escribir clases y métodos genéricos para crear componentes reutilizables con seguridad de tipos.

Genéricos y parámetros de tipo es una lección gratuita de Scala for Backend Engineering & Functional Programming en CoddyKit. Esta es la lección 1 de 3. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de Scala for Backend Engineering & Functional Programming, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de Scala for Backend Engineering & Functional Programming incluye 3 lecciones en total.

¿Qué son los genéricos?

Imagine que necesita crear un contenedor capaz de almacenar cualquier tipo de elemento, como un String, un Int o un objeto personalizado. Sin genéricos, tendría que escribir un contenedor distinto para cada tipo, lo que produciría código repetitivo, o utilizar un tipo muy amplio como Any.

Los genéricos permiten escribir código flexible y reutilizable que funciona con distintos tipos de datos y, al mismo tiempo, ofrece seguridad de tipos en tiempo de compilación. ¡Es como un plano en el que puede incorporar distintos materiales más adelante!

La trampa del tipo `Any`

Usar Any como marcador de posición para los tipos puede causar problemas. Aunque permite almacenar cualquier cosa, se pierde la información del tipo específico. Esto significa que a menudo hay que hacer un cast del elemento para devolverlo a su tipo original, lo que puede provocar errores y fallos en tiempo de ejecución si la conversión es incorrecta.

Intente ejecutar este ejemplo y piense en los posibles 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")
  }
}

Introducción a los parámetros de tipo

Los genéricos resuelven la trampa del tipo Any mediante la introducción de parámetros de tipo. Son marcadores de posición para tipos concretos que se especifican al crear una instancia de una clase o llamar a un método.

En Scala, los parámetros de tipo se definen mediante corchetes ([]) después del nombre de la clase o del método, normalmente con letras mayúsculas individuales como T, A, B, etc. Por ejemplo, class Box[T] declara una clase genérica Box que recibe un parámetro de tipo T.

Construcción de un Box genérico

Vamos a crear una clase genérica sencilla llamada Box que pueda contener cualquier tipo de contenido. Observe cómo se utiliza T en toda la definición de la clase para representar el tipo del contenido.

Esto permite crear cajas para cadenas, enteros o cualquier otro tipo sin escribir clases independientes para cada uno.

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)
  }
}

Seguridad de tipos con genéricos

La mayor ventaja de los genéricos es la seguridad de tipos en tiempo de compilación. Cuando crea un Box[String], el compilador sabe que solo debe contener cadenas. Si intenta introducir un entero o recuperarlo como entero, el compilador detectará el error de inmediato.

Esto evita muchos errores comunes en tiempo de ejecución que se producen con enfoques menos seguros en cuanto a tipos, como usar 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!
  }
}

¡Los genéricos también se aplican a los métodos!

Al igual que las clases, los métodos individuales también pueden ser genéricos. Un método genérico puede aceptar parámetros de tipo que son locales a ese método, lo que le permite operar con distintos tipos sin que toda la clase tenga que ser genérica.

Esto resulta útil cuando tiene una operación específica que puede aplicarse a varios tipos, pero la propia clase no es un contenedor genérico.

Un método `printPair` flexible

A continuación se muestra un ejemplo de un método genérico printPair. Acepta dos parámetros de tipo, A y B, lo que le permite imprimir un par formado por dos tipos cualesquiera. Observe que los parámetros de tipo se declaran justo después del nombre del método.

Esto hace que el método sea muy reutilizable para distintas combinaciones de datos.

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')
  }
}

Inferencia de tipos inteligente

Una de las características más potentes de Scala es la inferencia de tipos. Cuando utiliza clases o métodos genéricos, a menudo no necesita especificar explícitamente los parámetros de tipo. El compilador de Scala puede deducirlos a partir de los argumentos que proporciona.

Esto hace que el código sea más conciso y fácil de leer sin sacrificar la seguridad 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}")
  }
}

Restricción de tipos genéricos mediante límites

A veces, querrá que una clase o un método genérico funcione únicamente con tipos que tengan ciertas capacidades. Por ejemplo, un método que suma números solo debería aceptar tipos numéricos.

Los límites de tipo permiten restringir los tipos que pueden utilizarse como parámetros de tipo. Un límite superior (<:) significa que el parámetro de tipo debe ser un subtipo de un tipo determinado. Por ejemplo, [T <: Animal] significa que T debe ser Animal o un 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()) 
  }
}

Desafío sobre clases genéricas

¡Es hora de comprobar cuánto comprende los genéricos!

Lea atentamente las siguientes afirmaciones y seleccione todas las que sean verdaderas sobre las clases y los métodos genéricos en Scala.

Genéricos: conceptos clave

En esta lección, hemos explorado los genéricos de Scala, una potente característica para escribir código flexible y seguro en cuanto a tipos.

  • Los genéricos permiten que las clases y los métodos operen con distintos tipos.
  • Utilizan parámetros de tipo (por ejemplo, [T]) como marcadores de posición para tipos concretos.
  • Los genéricos proporcionan seguridad de tipos en tiempo de compilación, lo que evita errores en tiempo de ejecución.
  • La inferencia de tipos de Scala suele hacer que el código genérico sea conciso.
  • Los límites de tipo (por ejemplo, <:) pueden restringir los parámetros de tipo a subtipos específicos.

Al utilizar genéricos, puede escribir componentes reutilizables, sólidos y adaptables a diversos tipos de datos, lo que hace que su código de Scala sea más potente y menos propenso a errores.

Preguntas frecuentes

¿La lección «Genéricos y parámetros de tipo» es gratis?

Sí — el texto completo de «Genéricos y parámetros de tipo» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de Scala for Backend Engineering & Functional Programming, actualiza a CoddyKit PRO. El curso de Scala for Backend Engineering & Functional Programming incluye 3 lecciones en total.

¿Qué aprenderé en «Genéricos y parámetros de tipo»?

Comprenda cómo escribir clases y métodos genéricos para crear componentes reutilizables con seguridad de tipos. Practicas Scala for Backend Engineering & Functional Programming con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.

¿Necesito experiencia previa para empezar Scala for Backend Engineering & Functional Programming?

No se requiere experiencia previa. Scala for Backend Engineering & Functional Programming en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 1 de 3.

¿Cuánto tiempo toma la lección «Genéricos y parámetros de tipo»?

La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.

¿Puedo escribir y ejecutar código en esta lección de Scala for Backend Engineering & Functional Programming?

Sí. Cada lección de Scala for Backend Engineering & Functional Programming incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.

Todas las lecciones de este curso

  1. Genéricos y parámetros de tipo
  2. Varianza: covarianza y contravarianza
  3. Clases de tipos e implicits
← Volver a Scala for Backend Engineering & Functional Programming