Data class en Kotlin: por qué no se pueden heredar y cómo evitarlo

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Data class en Kotlin: por qué no se pueden heredar y cómo evitarlo (análisis bajo el capó)

\n\n

¿Alguna vez has escrito data class User(val name: String) y luego has querido hacer data class Admin extends User? ¿Y recibiste un error rojo del compilador? Si es así, no estás solo. La pregunta «Extend data class in Kotlin» está entre las más populares en Stack Overflow (más de 50,000 visitas).

\n\n

Hoy no solo daremos una respuesta preparada. Exploraremos bajo el capó de Kotlin, analizaremos por qué los creadores del lenguaje impusieron esta limitación «drástica» y mostraremos 3 formas legales de evitarla sin romper la arquitectura.

\n\n

El artículo está escrito para el portal educativo PythonLib — aquí nos encanta profundizar, no solo copiar fragmentos de código.

\n\n

¿Por qué no se puede heredar un data class? Echando un vistazo al bytecode

\n\n

Vamos al grano. Escribamos un data class simple y veamos qué genera el compilador:

\n\n
data class User(val name: String, val age: Int)
\n\n

Bajo el capó, Kotlin crea:

\n
    \n
  • equals() — compara todos los campos
  • \n
  • hashCode() — hash basado en todos los campos
  • \n
  • toString() — representación de cadena legible
  • \n
  • copy() — función de copia con modificación de campos
  • \n
  • componentN() — desestructuración
  • \n
\n\n

Ahora imagina que heredas un data class:

\n\n
open data class Base(val x: Int)\ndata class Derived(val x: Int, val y: Int) : Base(x) // ¡Error de compilación!
\n\n

Kotlin muestra: «Inheritance of data classes is not allowed». ¿Por qué? Analicemos.

\n\n

Problema n.º 1: copy() se rompe. La función copy() se genera solo para los campos de la clase actual. Si tienes Derived heredado de Base, entonces copy() en Base no conoce el campo y. Al llamar a base.copy(), perderías los datos del heredero.

\n\n

Problema n.º 2: equals() y hashCode(). Imagina: Base(1) y Derived(1, 2). ¿Deberían ser iguales? Si es así, se rompe la simetría (ya que Derived contiene más campos). Si no, se rompe el contrato de equals/hashCode. Kotlin eligió el camino seguro: prohibir la herencia.

\n\n

Problema n.º 3: componentN(). val (x) = base y val (x, y) = derived — diferentes cantidades de componentes. Con la herencia surgiría confusión.

\n\n

Conclusión: data class está creado para contenedores de datos inmutables, no para jerarquías. Es una decisión consciente del diseño del lenguaje.

\n\n

Forma n.º 1: Composición en lugar de herencia (recomendada)

\n\n

La forma más limpia es no heredar, sino incluir un data class dentro de otro. Esto se llama composición.

\n\n
data class Address(val city: String, val street: String)\n\ndata class User(\n    val name: String,\n    val age: Int,\n    val address: Address // composición\n)\n\nfun main() {\n    val address = Address("Moscú", "Tverskaya")\n    val user = User("Ana", 25, address)\n    println(user) // User(name=Ana, age=25, address=Address(city=Moscú, street=Tverskaya))\n    \n    // La copia funciona perfectamente\n    val user2 = user.copy(age = 26)\n    println(user2) // User(name=Ana, age=26, address=Address(city=Moscú, street=Tverskaya))\n}\n
\n\n

Ventajas: todo funciona «de fábrica», sin sorpresas con equals/hashCode, código transparente.

\n\n

Desventajas: si necesitas muchos niveles de anidamiento, obtienes una «muñeca rusa». Pero esto se resuelve con normalización de datos.

\n\n

Forma n.º 2: Sealed class + data class (para jerarquías estrictas)

\n\n

Si realmente necesitas una jerarquía (por ejemplo, diferentes tipos de usuarios), usa sealed class (o sea

Blogs

Book Recommendations