Clases

Las clases en Kotlin se declaran usando la palabra clave clase:

class Invoice /*...*/

La declaración de la clase consta del nombre de la clase, el encabezado de la clase (especificando sus parámetros de tipo, el constructor principal, etc.) y el cuerpo de la clase, rodeado por llaves. Tanto el encabezado como el cuerpo son opcionales; si la clase no tiene cuerpo, se pueden omitir las llaves.

class Empty

Constructores

Una clase en Kotlin puede tener un constructor primario y uno o mas constructores secundarios. El constructor principal es parte del encabezado de la clase: va después del nombre de la clase (y los parámetros de tipo opcionales).

class Person constructor(firstName: String)/*...*/

Si el constructor principal no tiene anotaciones ni modificadores de visibilidad, el constructor se puede omitir la palabra clave:

classPerson(firstName: String)/*...*/

El constructor principal no puede contener ningún código. El código de inicialización se puede colocar en bloques inicializadores, que tienen el prefijo en eso palabra clave.

Durante la inicialización de una instancia, los bloques inicializadores se ejecutan en el mismo orden en que aparecen en el cuerpo de la clase, intercalados con los inicializadores de propiedad:

//sampleStartclassInitOrderDemo(name: String)val firstProperty ="First property: $name".also(::println)initprintln("First initializer block that prints $name")val secondProperty ="Second property: $name.length".also(::println)initprintln("Second initializer block that prints $name.length")//sampleEndfunmain()InitOrderDemo("hello")

Tenga en cuenta que los parámetros del constructor principal se pueden utilizar en los bloques inicializadores. También se pueden usar en inicializadores de propiedad declarados en el cuerpo de la clase:

classCustomer(name: String)val customerKey = name.toUpperCase()

De hecho, para declarar propiedades e inicializarlas desde el constructor principal, Kotlin tiene una sintaxis concisa:

classPerson(val firstName: String,val lastName: String,var age: Int)/*...*/

Puede usar una coma al final cuando declara propiedades de clase:

classPerson(val firstName: String,val lastName: String,var age: Int,// trailing comma)/*...*/

De la misma manera que las propiedades regulares, las propiedades declaradas en el constructor primario pueden ser mutables (var) o de solo lectura (val).

Si el constructor tiene anotaciones o modificadores de visibilidad, el constructor La palabra clave es obligatoria y los modificadores van antes de ella:

class Customer public@Injectconstructor(name: String)/*...*/

Para obtener más detalles, consulte Modificadores de visibilidad.

Constructores secundarios

La clase también puede declarar constructores secundarios, que tienen el prefijo constructor:

class Person var children: MutableList<Person>= mutableListOf<>()constructor(parent: Person)
        parent.children.add(this)

Si la clase tiene un constructor principal, cada constructor secundario debe delegar en el constructor principal, ya sea directa o indirectamente a través de otro (s) constructor (es) secundario (s). La delegación a otro constructor de la misma clase se realiza mediante la esta palabra clave:

classPerson(val name: String)var children: MutableList<Person>= mutableListOf<>()constructor(name: String, parent: Person):this(name)
        parent.children.add(this)

Tenga en cuenta que el código de los bloques inicializadores se convierte efectivamente en parte del constructor principal. La delegación al constructor primario ocurre como la primera declaración de un constructor secundario, por lo que el código en todos los bloques inicializadores e inicializadores de propiedades se ejecuta antes del cuerpo del constructor secundario. Incluso si la clase no tiene un constructor principal, la delegación aún ocurre implícitamente y los bloques inicializadores aún se ejecutan:

//sampleStartclass Constructors initprintln("Init block")constructor(i: Int)println("Constructor")//sampleEndfunmain()Constructors(1)

Si una clase no abstracta no declara ningún constructor (primario o secundario), tendrá un constructor primario generado sin argumentos. La visibilidad del constructor será pública. Si no desea que su clase tenga un constructor público, debe declarar un constructor primario vacío con visibilidad no predeterminada:

class DontCreateMe privateconstructor()/*...*/

NOTA: En la JVM, si todos los parámetros del constructor principal tienen valores predeterminados, el compilador generará un constructor sin parámetros adicional que utilizará los valores predeterminados. Esto facilita el uso de Kotlin con bibliotecas como Jackson o JPA que crean instancias de clases a través de constructores sin parámetros.

classCustomer(val customerName: String ="")

Creando instancias de clases

Para crear una instancia de una clase, llamamos al constructor como si fuera una función regular:

val invoice =Invoice()val customer =Customer("Joe Smith")

Tenga en cuenta que Kotlin no tiene nuevo palabra clave.

La creación de instancias de clases internas anidadas, internas y anónimas se describe en Clases anidadas.

Miembros de la clase

Las clases pueden contener:

  • Constructores y bloques inicializadores
  • Funciones
  • Propiedades
  • Clases anidadas e internas
  • Declaraciones de objeto

Herencia

Todas las clases en Kotlin tienen una superclase común Any, esa es la superclase predeterminada para una clase sin supertipos declarados:

class Example // Implicitly inherits from Any

Any tiene tres métodos: equals(), hashCode() y toString(). Por lo tanto, se definen para todas las clases de Kotlin.

Por defecto, las clases de Kotlin son definitivas: no se pueden heredar. Para hacer que una clase sea heredable, márquela con el open palabra clave.

openclass Base //Class is open for inheritance

Para declarar un supertipo explícito, coloque el tipo después de dos puntos en el encabezado de la clase:

openclassBase(p: Int)classDerived(p: Int):Base(p)

Si la clase derivada tiene un constructor principal, la clase base puede (y debe) inicializarse allí mismo, utilizando los parámetros del constructor principal.

Si la clase derivada no tiene un constructor primario, entonces cada constructor secundario tiene que inicializar el tipo base usando el súper palabra clave, o delegar en otro constructor que haga eso. Tenga en cuenta que en este caso diferentes constructores secundarios pueden llamar a diferentes constructores del tipo base:

class MyView : View constructor(ctx: Context):super(ctx)constructor(ctx: Context, attrs: AttributeSet):super(ctx, attrs)

Métodos primordiales

Como mencionamos antes, nos limitamos a hacer las cosas explícitas en Kotlin. Entonces, Kotlin requiere modificadores explícitos para miembros reemplazables (los llamamos abierto) y para anulaciones:

openclass Shape openfundraw()/*...*/funfill()/*...*/classCircle():Shape()overridefundraw()/*...*/

los anular se requiere un modificador para Circle.draw(). Si faltaba, el compilador se quejaría. Si no hay abierto modificador en una función, como Shape.fill(), declarar un método con la misma firma en una subclase es ilegal, ya sea con anular o sin ella. los abierto El modificador no tiene efecto cuando se agrega a miembros de una clase final (es decir, una clase sin abierto modificador).

Un miembro marcado anular está abierto en sí mismo, es decir, puede anularse en subclases. Si desea prohibir la re-invalidación, use final:

openclassRectangle():Shape()finaloverridefundraw()/*...*/

Propiedades primordiales

La invalidación de propiedades funciona de manera similar a la invalidación de métodos; Las propiedades declaradas en una superclase que luego se vuelven a declarar en una clase derivada deben ir precedidas de anulary deben tener un tipo compatible. Cada propiedad declarada puede ser anulada por una propiedad con un inicializador o por una propiedad con un get método.

openclass Shape openval vertexCount: Int =0class Rectangle :Shape()overrideval vertexCount =4

También puede anular un val propiedad con un var propiedad, pero no al revés. Esto está permitido porque un val propiedad esencialmente declara un get método, y anularlo como un var además declara un set método en la clase derivada.

Tenga en cuenta que puede utilizar el anular palabra clave como parte de la declaración de propiedad en un constructor primario.

interface Shape val vertexCount: Int
classRectangle(overrideval vertexCount: Int =4): Shape // Always has 4 verticesclass Polygon : Shape overridevar vertexCount: Int =0// Can be set to any number later

Orden de inicialización de clases derivadas

Durante la construcción de una nueva instancia de una clase derivada, la inicialización de la clase base se realiza como el primer paso (precedida solo por la evaluación de los argumentos para el constructor de la clase base) y, por lo tanto, ocurre antes de que se ejecute la lógica de inicialización de la clase derivada.

//sampleStartopenclassBase(val name: String)initprintln("Initializing Base")openval size: Int = 
        name.length.alsoprintln("Initializing size in Base: $it")classDerived(
    name: String,val lastName: String,):Base(name.capitalize().alsoprintln("Argument for Base: $it"))initprintln("Initializing Derived")overrideval size: Int =(super.size + lastName.length).alsoprintln("Initializing size in Derived: $it")//sampleEndfunmain()println("Constructing Derived("hello", "world")")val d =Derived("hello","world")

Significa que, en el momento de la ejecución del constructor de la clase base, las propiedades declaradas o anuladas en la clase derivada aún no se han inicializado. Si alguna de esas propiedades se usa en la lógica de inicialización de la clase base (ya sea directa o indirectamente, a través de otro abierto implementación de miembro), puede conducir a un comportamiento incorrecto o una falla en el tiempo de ejecución. Al diseñar una clase base, debe evitar usar abierto miembros en los constructores, inicializadores de propiedad y en eso bloques.

Llamar a la implementación de la superclase

El código en una clase derivada puede llamar a sus funciones de superclase e implementaciones de accesos de propiedad utilizando el súper palabra clave:

openclass Rectangle openfundraw()println("Drawing a rectangle")val borderColor: String get()="black"class FilledRectangle :Rectangle()overridefundraw()super.draw()println("Filling the rectangle")val fillColor: String get()=super.borderColor

Dentro de una clase interna, acceder a la superclase de la clase externa se realiza con el súper palabra clave calificada con el nombre de la clase externa: [email protected]:

class FilledRectangle:Rectangle()fundraw()/* ... */val borderColor: String get()="black"innerclass Filler funfill()/* ... */fundrawAndFill()super@FilledRectangle.draw()// Calls Rectangle's implementation of draw()fill()println("Drawn a filled rectangle with color $super@FilledRectangle.borderColor")// Uses Rectangle's implementation of borderColor's get()

Reglas primordiales

En Kotlin, la herencia de implementaciones está regulada por la siguiente regla: si una clase hereda múltiples implementaciones del mismo miembro de sus superclases inmediatas, debe anular este miembro y proporcionar su propia implementación (quizás, usando una de las heredadas). Para denotar el supertipo del que se toma la implementación heredada, usamos súper calificado por el nombre de supertipo entre paréntesis angulares, p. ej. super:

openclass Rectangle openfundraw()/* ... */interface Polygon fundraw()/* ... */// interface members are 'open' by defaultclassSquare():Rectangle(), Polygon // The compiler requires draw() to be overridden:overridefundraw()super<Rectangle>.draw()// call to Rectangle.draw()super<Polygon>.draw()// call to Polygon.draw()

Está bien heredar de ambos Rectangle y Polygon, pero ambos tienen sus implementaciones de draw(), entonces tenemos que anular draw() en Square y proporcionar su propia implementación que elimine la ambigüedad.

Clases abstractas

Una clase y algunos de sus miembros pueden declararse abstracto. Un miembro abstracto no tiene una implementación en su clase. Tenga en cuenta que no es necesario anotar una clase o función abstracta con open; no hace falta decirlo.

Podemos anular un miembro abierto no abstracto con uno abstracto

openclass Polygon openfundraw()abstractclass Rectangle :Polygon()abstractoverridefundraw()

Objetos complementarios

Si necesita escribir una función que se pueda llamar sin tener una instancia de clase, pero necesita acceso a los componentes internos de una clase (por ejemplo, un método de fábrica), puede escribirla como miembro de una declaración de objeto dentro de esa clase.

Incluso más específicamente, si declaras un objeto complementario dentro de tu clase, puedes acceder a sus miembros usando solo el nombre de la clase como calificador.