Documentos de Académico
Documentos de Profesional
Documentos de Cultura
7 clases de datos
Tratando con Datos
Esa función copy()
funcionó perfectamente.
Soy como tú pero más alto.
Nadie quiere pasarse la vida reinventando la rueda.
La mayoría de las aplicaciones incluyen clases cuyo objetivo principal es almacenar datos, por lo que
para facilitar su vida de codificación, los desarrolladores de Kotlin propusieron el concepto de una
clase de datos. Aquí, aprenderá cómo las clases de datos le permiten escribir código que es más
limpio y más conciso de lo que nunca soñó que fuera posible. Explorará las funciones de utilidad de la
clase de datos y descubrirá cómo desestructurar un objeto de datos en sus componentes. En el
camino, descubrirá cómo los valores de parámetros predeterminados pueden hacer que su código sea
más flexible y le presentaremos a Any, la madre de todas las superclases.
este es un nuevo capitulo 191
Machine Translated by Google
detrás de escena del operador ==
== llama a una función llamada igual
Como ya sabe, puede usar el operador == para verificar la igualdad.
Detrás de escena, cada vez que usa el operador ==, llama a una función
llamada igual. Cada objeto tiene una función de igualdad, y la
implementación de esta función determina cómo se comportará el
operador ==.
De forma predeterminada, la función equals comprueba la igualdad
comprobando si dos variables contienen referencias al mismo objeto subyacente.
Para ver cómo funciona esto, supongamos que tenemos dos variables
de Wolf llamadas w1 y w2. Si w1 y w2 tienen referencias al mismo Wolf
objeto, comparándolos con el operador == se evaluará como verdadero:
val w1 = Lobo() w1 y w2 se refieren al
valor w2 = w1 mismo objeto, por lo que
w1 == w2 es verdadero.
//w1 == w2 es verdadero
ÁRBITRO
w1 Lobo
ÁRBITRO
valle lobo
w2
valle lobo
Sin embargo, si w1 y w2 tienen referencias a objetos Wolf separados,
compararlos con el operador == se evaluará como falso, incluso si los
objetos tienen valores de propiedad idénticos.
val w1 = Lobo() w1 y w2 se refieren a
val w2 = Lobo() objetos diferentes, por lo
que w1 == w2 es falso.
//w1 == w2 es falso
ÁRBITRO ÁRBITRO
Lobo Lobo
w1 w2
valle lobo valle lobo
Como dijimos anteriormente, cada objeto que crea automáticamente
incluye una función de igualdad. Pero, ¿de dónde viene esta
función?
192 Capítulo 7
Machine Translated by Google
clases de datos
equals se hereda de una superclase llamada Any
Cada objeto tiene una función llamada igual porque su clase
hereda la función de una clase llamada Cualquiera. Class Any es
la madre de todas las clases: la superclase definitiva de todo. Cada
Cada clase es una subclase
clase que definas es una subclase de Any sin que tengas que
decirlo. Entonces, si escribe el código para una clase llamada
de la clase Any y hereda su
myClass que se ve así:
comportamiento. Cada clase
clase MiClase { ESUn tipo de Any sin que
...
}
tengas que decirlo.
detrás de escena, el compilador lo convierte automáticamente en esto:
Cualquier
clase MiClase : Cualquiera() {
...
El compilador secretamente convierte
}
a cada clase en una subclase de Any.
Mi clase
La importancia de ser Any
Tener Any como la superclase definitiva tiene dos beneficios clave:
¥ Asegura que cada clase herede un comportamiento común.
La clase Any define un comportamiento importante en el que se basa el sistema
y, como cada clase es una subclase de Any, este comportamiento lo heredan
todos los objetos que crea. La clase Any define una función llamada equals,
por ejemplo, lo que significa que cada objeto hereda automáticamente esta
función.
¥ Significa que puedes usar polimorfismo con cualquier objeto.
Cada clase es una subclase de Any, por lo que cada objeto que crea tiene
Any como su último supertipo. Esto significa que puede crear una función
con cualquier parámetro, o cualquier tipo de devolución, para que funcione
con todos los tipos de objetos. También significa que puede crear matrices
polimórficas para contener objetos de cualquier tipo utilizando un código como este:
El compilador detecta que
cada objeto de la matriz
val myArray = arrayOf(Coche(), Guitarra(), Jirafa())
tiene un supertipo común
de Cualquiera, por lo que
Echemos un vistazo más de cerca al comportamiento común heredado de
crea una matriz de tipo Array<Any>.
la clase Any.
estas aqui 4 193
Machine Translated by Google
Cualquier
El comportamiento común definido por Any Cualquier
La clase Any define varias funciones que son heredadas por cada clase. igual()
hashCode()
Estos son los que más nos interesan, junto con un ejemplo de su
toString()
comportamiento predeterminado: ...
¥ equals(any: Any): Boolean Te dice si
dos objetos se consideran “iguales”. De forma predeterminada, devuelve
verdadero si se usa para probar el mismo objeto y falso si se usa para TuClaseAqui
probar objetos separados. Detrás de escena, la función de igualdad se llama
cada vez que usa el operador ==.
equals devuelve verdadero
porque w1 y w2 contienen
¥ hashCode(): Int referencias al mismo objeto.
Devuelve un valor de código hash para el objeto. Ciertas estructuras de Es lo mismo que probar si w1 == w2.
datos los utilizan a menudo para almacenar y recuperar valores de
manera más eficiente.
val w = Lobo()
println(w.hashCode())
Este es el valor
Por defecto, la
523429237
del código hash de w.
función de igualdad
¥
comprueba si dos
toString(): String Devuelve
un mensaje de cadena que representa el objeto. Por defecto, este es
el nombre de la clase y algún otro número que rara vez nos importa. objetos son el mismo
objeto subyacente.
val w = Lobo()
println(w.toString())
lobo@1f32e575 La función igual
define el comportamiento
La clase Any proporciona una implementación predeterminada para cada una
de las funciones anteriores, y cada clase hereda estas implementaciones. Sin del operador ==.
embargo, pueden anularse si desea cambiar el comportamiento predeterminado
de cualquiera de estas funciones.
194 Capítulo 7
Machine Translated by Google
clases de datos
Podríamos querer iguales para
comprobar si dos objetos son equivalentes
Hay algunas situaciones en las que es posible que desee cambiar la
implementación de la función de igualdad para cambiar el
comportamiento del operador ==.
Suponga, por ejemplo, que tiene una clase llamada Receta que le
permite crear objetos que contienen datos de recetas. En esta
situación, puede considerar que dos objetos Recipe son iguales (o
equivalentes) si contienen detalles de la misma receta. Entonces, si la
clase Receta se define con dos propiedades denominadas título y es
vegetariana usando un código como este:
Receta
Receta de clase (título de val: cadena, val es vegetariano: booleano) { el
} titulo es vegetariano
es posible que desee que el operador == se evalúe como verdadero
si se usa para comparar dos objetos Receta que tienen propiedades
title y isVegetarian coincidentes:
val r1 = Receta("Pollo Bhuna", false)
val r2 = Receta("Pollo Bhuna", falso)
ÁRBITRO título: “Pollo Bhuna” es
vegetariano: falso
r1
Receta Estos dos objetos tienen valores de propiedad
val Receta
coincidentes, por lo que es posible que
deseemos que el operador == se evalúe como verdadero.
ÁRBITRO título: “Pollo Bhuna” es
vegetariano: falso
r2
Receta
val Receta
Si bien podría cambiar el comportamiento del operador ==
escribiendo código adicional para anular la función de igualdad, los
desarrolladores de Kotlin idearon un mejor enfoque: idearon el
concepto de una clase de datos. Averigüemos cuál es uno de estos y
cómo crear uno.
estas aqui 4 195
Machine Translated by Google
clases de datos
Una clase de datos le permite crear objetos de datos
Una clase de datos es aquella que le permite crear objetos cuyo
objetivo principal es almacenar datos. Incluye características que son
útiles cuando se trata de datos, como una nueva implementación de la
función de igualdad que comprueba si dos objetos de datos tienen los
mismos valores de propiedad. Esto se debe a que si dos objetos
almacenan los mismos datos, pueden considerarse iguales.
Una clase de datos se define anteponiendo una definición de clase
normal con la palabra clave de datos . El siguiente código, por ejemplo,
cambia la clase Receta que creamos anteriormente en una clase de datos:
El prefijo de datos
convierte una clase
clase de datos Receta (título de valor: cadena, valor es vegetariano: booleano) {
normal en una clase de datos. }
(Datos)
Receta
Cómo crear objetos a partir de una clase de datos
el
Crea objetos a partir de una clase de datos de la misma manera que titulo es vegetariano
crea objetos a partir de una clase normal: llamando a su constructor.
El siguiente código, por ejemplo, crea un nuevo objeto de datos de
Receta y lo asigna a una nueva variable llamada r1:
val r1 = Receta("Pollo Bhuna", false)
Las clases de datos anulan automáticamente su función de igualdad
para cambiar el comportamiento del operador == de modo que
compruebe la igualdad de objetos en función de los valores de las
propiedades de cada objeto. Si, por ejemplo, crea dos objetos Receta
que contienen valores de propiedad idénticos, comparar los dos
objetos con el operador == se evaluará como verdadero, porque título: “Pollo Bhuna” es
contienen los mismos datos: vegetariano: falso
ÁRBITRO
val r1 = Receta("Pollo Bhuna", false) Receta
r1
val r2 = Receta("Pollo Bhuna", falso)
//r1 == r2 es cierto val Receta
r1 y r2 son título: “Pollo Bhuna”
es vegetariano: falso
Además de proporcionar una nueva considerado "igual" ya que
ÁRBITRO
implementación de la función equals los dos objetos de receta
Receta
que hereda de la superclase Any, las contienen los mismos datos. r2
clases de datos también anulan las funciones
hashCode y toString. val Receta
Echemos un vistazo a cómo se
implementan.
196 Capítulo 7
Machine Translated by Google
clases de datos
Las clases de datos anulan su comportamiento heredado
Una clase de datos necesita que sus objetos funcionen bien con los
datos, por lo que proporciona automáticamente las siguientes
implementaciones para las funciones equals, hashCode y toString
que hereda de la superclase Any:
Los objetos de
La función equals compara valores de propiedad datos se
Cuando define una clase de datos, su función de igualdad (y, por lo tanto, el operador
==) continúa devolviendo verdadero si se usa para probar el mismo objeto. consideran iguales
Pero también devuelve verdadero si los objetos tienen valores idénticos para las
propiedades definidas en su constructor: si sus propiedades
val r1 = Receta("Pollo Bhuna", false)
tienen los mismos valores.
val r2 = Receta("Pollo Bhuna", falso)
println(r1.equals(r2))
verdadero
Los objetos iguales devuelven el mismo valor hashCode Puede pensar en un código hash
como una etiqueta en un balde.
Si dos objetos de datos se consideran iguales (en otras palabras, tienen valores
Los objetos que se consideran
de propiedad idénticos), la función hashCode devuelve el mismo valor para cada iguales se colocan en el mismo cubo y el
objeto: código hash le dice al sistema dónde
val r1 = Receta("Pollo Bhuna", false) buscarlos.
Los objetos iguales DEBEN tener el
val r2 = Receta("Pollo Bhuna", falso) mismo valor de código hash ya que
println(r1.hashCode()) el sistema depende de esto.
println(r2.hashCode()) Encontrará más información sobre
241131113 esto en el Capítulo 9.
241131113
toString devuelve el valor de cada propiedad
Finalmente, la función toString ya no devuelve el nombre de la clase seguido de un
número. En su lugar, devuelve una cadena útil que contiene el valor de cada propiedad
definida en el constructor de la clase de datos:
val r1 = Receta("Pollo Bhuna", false)
println(r1.toString())
Receta(título=Pollo Bhuna, esVegetariano=falso)
Además de anular las funciones que hereda de la superclase Any, una
clase de datos también proporciona características adicionales que lo
ayudan a manejar los datos de manera más efectiva, como la capacidad
de copiar un objeto de datos. Veamos cómo funciona esto.
estas aqui 4 197
Machine Translated by Google
función de copia
Copie objetos de datos utilizando la función de copia
Si desea crear una nueva copia de un objeto de datos, alterando algunas
de sus propiedades pero dejando el resto intacto, puede hacerlo utilizando la
La función de copia le
función de copia . Para usar, llame a la función en el objeto que desea copiar,
pasando los nombres de las propiedades que desea modificar junto con sus
permite copiar un objeto de
nuevos valores.
Suponga que tiene un objeto Receta llamado r1 que se define mediante
datos, alterando algunas
un código como este:
de sus propiedades. El
val r1 = Receta ("Curry tailandés", falso) objeto original permanece intacto.
ÁRBITRO
título: “Curry tailandés”
es vegetariano: falso
r1
Receta
val Receta
Si quisiera crear una copia del objeto Receta, alterando el valor de su propiedad
isVegetarian a verdadero, podría hacerlo usando la función de copia así:
val r1 = Receta ("Curry tailandés", falso) Esto copia el objeto de r1, cambiando el valor
val r2 = r1.copy(esVegetariano = verdadero) de la propiedad isVegetarian a verdadero.
ÁRBITRO ÁRBITRO
título: “Curry tailandés” título: “Curry tailandés”
es vegetariano: falso es vegetariano: verdadero
r1 r2
Receta Receta
val Receta val Receta
Es como decir "tome una copia del objeto de r1, cambie el valor de su propiedad
isVegetarian a verdadero y asigne el nuevo objeto a una variable llamada r2".
Crea una nueva copia del objeto y deja intacto el objeto original.
Además de la función de copia, las clases de datos también
proporcionan un conjunto de funciones que le permiten dividir un
objeto de datos en los valores de propiedad de sus componentes en
un proceso llamado desestructuración. Veamos cómo.
198 Capítulo 7
Machine Translated by Google
clases de datos
Las clases de datos definen funciones de componenteN...
Cuando define una clase de datos, el compilador agrega automáticamente
un conjunto de funciones a la clase que puede usar como una forma
alternativa de acceder a los valores de propiedad de su objeto. Estas se
conocen como funciones de componenteN, donde N representa el número de
título: “Pollo Bhuna” es
la propiedad cuyo valor desea recuperar (en orden de declaración).
vegetariano: falso
Para ver cómo funcionan las funciones de componenteN, suponga que tiene el ÁRBITRO
siguiente objeto Receta: Receta
r
val r = Receta("Pollo Bhuna", falso)
val Receta
Si desea recuperar el valor de la primera propiedad del objeto (su propiedad
de título), puede hacerlo llamando a la función componente1() del objeto de
“Pollo Bhuna”
esta manera:
componente1() devuelve
ÁRBITRO
la referencia contenida por
val titulo = r.componente1()
la primera propiedad Cadena
título
Esto hace lo mismo que el código: definida en el constructor
de la clase de datos.
val título = r.título valor cadena
pero es más genérico. Entonces, ¿por qué es tan útil que una clase de datos
tenga funciones genéricas de ComponentN?
...que te permiten desestructurar objetos de datos
La desestructuración de un
Tener funciones de componenteN genéricas es útil ya que proporciona una
forma rápida de dividir un objeto de datos en sus valores de propiedad de
objeto de datos lo divide en
componente, o de desestructurarlo .
Suponga, por ejemplo, que desea tomar los valores de propiedad de un objeto sus partes componentes.
Receta y asignar cada valor de propiedad a una variable separada. En lugar de
usar el código:
val título = r.título
val vegetariano = r.esvegetariano “Pollo Bhuna”
para procesar explícitamente cada propiedad a su vez, puede usar el ÁRBITRO
pero es más conciso.
199
estas aqui 4
Machine Translated by Google
== contra ===
Las clases de datos suenan muy bien, pero
me preguntaba... ¿Existe una forma definitiva de
verificar si dos variables se refieren al mismo objeto
subyacente? Parece que no puede confiar en el operador ==
porque su comportamiento depende de cómo se haya
implementado la función de igualdad, y esto puede variar de
una clase a otra.
El operador === siempre le permite verificar si dos variables se
refieren al mismo objeto subyacente.
Si desea verificar si dos variables se refieren al mismo objeto
subyacente, independientemente de su tipo, debe usar el operador ===
en lugar de ==. Esto se debe a que el operador === siempre se evalúa como
verdadero si (y solo si) las dos variables contienen una referencia al mismo
objeto subyacente. Esto significa que si, por ejemplo, tiene dos variables
llamadas x e y, y el código:
x === y
se evalúa como verdadero, entonces sabe que las variables x e y
deben hacer referencia al mismo objeto subyacente:
ÁRBITRO
== comprueba la
X
equivalencia de objetos.
ÁRBITRO
=== comprueba la y
identidad del objeto.
A diferencia del operador ==, el operador === no depende de la función
de igualdad para su comportamiento. El operador === siempre se
comporta de esta manera independientemente del tipo de clase.
Ahora que ha visto cómo crear y usar clases de datos, creemos un
proyecto para el código de Receta.
200 Capítulo 7
Machine Translated by Google
clases de datos
Crear el proyecto Recetas
Cree un nuevo proyecto de Kotlin que se dirija a la JVM y asígnele el nombre
"Recetas". Luego cree un nuevo archivo de Kotlin llamado Recipes.kt resaltando la
carpeta src , haciendo clic en el menú Archivo y eligiendo Nuevo → Archivo/clase
de Kotlin. Cuando se le solicite, nombre el archivo "Recetas" y elija Archivo en la
opción Tipo.
Agregaremos una nueva clase de datos llamada Receta al proyecto y crearemos
Hemos omitido los {} porque
algunos objetos de datos de Receta. Aquí está el código: actualice su versión de
Recipes.kt para que coincida con la nuestra: nuestra clase de datos no tiene cuerpo.
(Datos)
clase de datos Receta (título de valor: cadena, valor es vegetariano: booleano) Receta
título
diversión principal(argumentos: Array<String>) { es vegetariano
val r1 = Receta ("Curry tailandés", falso)
Cree una copia de r1,
val r2 = Receta ("Curry tailandés", falso)
modificando su propiedad de título.
val r3 = r1.copy(título = "Pollo Bhuna")
println("código hash r1: ${r1.hashCode()}")
println("código hash r2: ${r2.hashCode()}")
Recetas
println("código hash r3: ${r3.hashCode()}")
println("r1 a la Cadena: ${r1. a la Cadena()}")
origen
println("r1 == r2? ${r1 == r2}")
println("r1 === r2? ${r1 === r2}")
Recetas.kt
println("r1 == r3? ${r1 == r3}") Desestructurar r1.
val (título, vegetariano) = r1
println("título es $título y vegetariano es $vegetariano")
Prueba de conducción
Cuando ejecuta su código, el siguiente texto se imprime en la ventana de salida del IDE:
código hash r1: 135497891
código hash r2: 135497891
código hash r3: 241131113
r1 toString: Receta (título = curry tailandés, es vegetariano = falso) r1
== r2? verdadero
r1 == r2 es verdadero porque sus objetos tienen valores coincidentes.
r1 === r2? FALSO
r1 == r3? FALSO Como se refieren a objetos separados, r1 === r2 es falso.
el título es curry tailandés y vegetariano es falso
estas aqui 4 201
Machine Translated by Google
sin preguntas tontas
P: Ya veo. Y dices que datos
P: Eso suena complicado. P: ¿ Por qué las clases de datos incluyen un
¿Las clases anulan automáticamente
estas funciones? función de copiar ?
R: Sin duda, es más fácil crear una base de datos
class, y el uso de una clase de datos
R: Sí. Cuando defines un dato R: Las clases de datos generalmente se definen
significa que tendrá un código más
class, el compilador anula en secreto usando propiedades val para que sean
limpio que es más conciso. Sin
las funciones equals, hashCode y inmutables. Tener una función de copia es
embargo , si desea anular las
toString que la clase hereda para que una buena alternativa a tener objetos de datos
funciones equals, hashCode y toString , que se pueden modificar, ya que le permite crear
sean más apropiadas para objetos cuyo
puede hacer que el IDE genere la mayor
objetivo principal es almacenar datos. fácilmente otra versión del objeto con valores de
parte del código por usted. propiedad modificados.
P: ¿ Puedo anular estas funciones?
Para que el IDE genere implementaciones
sin crear una clase de datos? P: ¿Puedo declarar que una clase de datos es
para las funciones equals, hashCode o
¿abstracto? O abierto?
toString , comience escribiendo la definición
R: Sí, exactamente de la misma manera que usted de clase básica, incluidas las propiedades.
anular funciones de cualquier otra clase: Luego, asegúrese de que su cursor de texto R: No. Las clases de datos no se pueden declarar.
al proporcionar una implementación para abstracto o abierto, por lo que no puede usar una
esté en la clase, vaya al menú Código y
las funciones en el cuerpo de su clase. clase de datos como una superclase. Sin embargo,
seleccione la opción Generar. Finalmente,
las clases de datos pueden implementar interfaces
elija la función para la que desea generar
código. y, desde Kotlin 1.1, también pueden heredar de
otras clases.
202 Capítulo 7
Machine Translated by Google
clases de datos
A continuación se muestra un breve programa de Kotlin. Falta un bloque
del programa. Su desafío es hacer coincidir el bloque de código candidato
(a la izquierda), con el resultado que vería si se insertara el bloque. Se
utilizarán todas las líneas de salida y algunas líneas de salida se pueden
utilizar más de una vez. Dibuje líneas que conecten los bloques de código
Mezclado candidatos con su salida correspondiente.
Mensajes
clase de datos Película (título de valor: Cadena, año de valor: Cadena)
canción de clase (título de valor: cadena, artista de valor: cadena)
diversión principal(argumentos: Array<String>) {
var m1 = Película ("Pantera Negra", "2018")
var m2 = Película ("Mundo Jurásico", "2015")
var m3 = Película ("Mundo Jurásico", "2015")
var s1 = Canción ("Love Cats", "The Cure")
var s2 = Canción ("Caballos salvajes", "Los Rolling Stones")
var s3 = Canción ("Love Cats", "The Cure")
El código de
candidato va aquí.
Candidatos: Salida posible:
imprimirln(m2 == m3)
imprimirln(s1 == s3)
Relaciona
verdadero
cada var m4 = m1.copia()
candidato
println(m1 == m4)
con uno de
los posibles resultados.
var m5 = m1.copia()
imprimirln(m1 === m5)
FALSO
var m6 = m2
m2 = m3
imprimirln(m3 == m6)
estas aqui 4 203
Machine Translated by Google
solución de mensajes mixtos
A continuación se muestra un breve programa de Kotlin. Falta un
bloque del programa. Su desafío es hacer coincidir el bloque de código
candidato (a la izquierda), con el resultado que vería si se insertara el
bloque. Se utilizarán todas las líneas de salida y algunas líneas de salida
se pueden utilizar más de una vez. Dibuje líneas que conecten los
Mezclado bloques de código candidatos con su salida correspondiente.
Mensajes
Solución
clase de datos Película (título de valor: Cadena, año de valor: Cadena)
canción de clase (título de valor: cadena, artista de valor: cadena)
diversión principal(argumentos: Array<String>) {
var m1 = Película ("Pantera Negra", "2018")
var m2 = Película ("Mundo Jurásico", "2015")
var m3 = Película ("Mundo Jurásico", "2015")
var s1 = Canción ("Love Cats", "The Cure")
var s2 = Canción ("Caballos salvajes", "Los Rolling Stones")
var s3 = Canción ("Love Cats", "The Cure")
El código de
candidato va aquí.
m2 == m3 es
Candidatos: Salida posible:
verdadero porque
m1 y m2 son objetos
imprimirln(m2 == m3)
de datos.
imprimirln(s1 == s3)
m4 y m1 tienen
verdadero
valores de propiedad var m4 = m1.copia()
coincidentes, por lo
que m1 == m4 es
imprimirln(m1 == m4)
verdadero.
var m5 = m1.copia()
m1 y m5 son
println(m1 === m5)
objetos separados,
FALSO
por lo que m1 ===
var m6 = m2
m5 es falso.
m2 = m3
imprimirln(m3 == m6)
204 Capítulo 7
Machine Translated by Google
clases de datos
Las funciones generadas solo
usan propiedades definidas en el constructor
Hasta ahora, ha visto cómo definir una clase de datos y agregar propiedades a su
constructor. El siguiente código, por ejemplo, define una clase de datos denominada
Receta con propiedades denominadas título y es vegetariano: (Datos)
Receta
clase de datos Receta (título de valor: cadena, valor es vegetariano: booleano) {
el
}
titulo es vegetariano
Al igual que cualquier otro tipo de clase, también puede agregar propiedades y funciones
a una clase de datos incluyéndolas en el cuerpo de la clase. Pero hay una gran captura.
Cuando el compilador genera implementaciones para funciones de clase de datos,
como anular la función de igualdad y crear una función de copia, solo incluye las
propiedades definidas en el constructor principal. Entonces, si agrega propiedades a
una clase de datos definiéndolas en el cuerpo de la clase, no se incluirán en ninguna
de las funciones generadas.
(Datos)
Suponga, por ejemplo, que agrega una nueva propiedad mainIngredient al cuerpo de
Receta
la clase de datos de Receta de esta manera:
título
clase de datos Receta (título de valor: cadena, valor es vegetariano: booleano) { esIngrediente
"" principal vegetariano
var ingrediente principal =
}
Como la propiedad mainIngredient se ha definido en el cuerpo principal
de la clase en lugar del constructor, funciones como equals la ignoran. Esto
significa que si crea dos objetos Receta usando un código como este: título: “Curry tailandés”
es vegetariano: falso
ingrediente principal: “pollo”
ÁRBITRO
val r1 = Receta ("curry tailandés", falso)
Receta
r1.ingredienteprincipal = "Pollo" r1
val r2 = Receta ("curry tailandés", falso)
val Receta título: “Curry tailandés”
r2.ingredienteprincipal = "Pato" es vegetariano: falso
println(r1 == r2) // se evalúa como verdadero ingrediente principal: “pato”
ÁRBITRO
Receta
el operador == solo mirará el título y las propiedades r2
r1 == r2 es verdadero porque
isVegetarian para determinar si los dos objetos son iguales porque solo
estas propiedades se han definido en el constructor de la clase de datos. val Receta r1 y r2 tienen un título
Si los dos objetos tienen valores diferentes para la propiedad coincidente y propiedades vegetarianas.
mainIngredient (como en el ejemplo anterior), la función equals no El operador == ignora la
observará esta propiedad al considerar si dos objetos son iguales. propiedad mainIngredient
porque no se ha definido en el
constructor.
Pero, ¿qué sucede si su clase de datos tiene muchas propiedades que
desea incluir en las funciones generadas por la clase de datos?
estas aqui 4 205
Machine Translated by Google
valores de parámetros predeterminados
La inicialización de muchas propiedades
puede dar lugar a un código engorroso
Como acaba de aprender, cualquier propiedad que desee incluir en las funciones
generadas por una clase de datos debe definirse en su constructor principal. Pero si
tiene muchas de esas propiedades, su código puede volverse difícil de manejar
rápidamente. Cada vez que crea un nuevo objeto, debe especificar un valor para
cada una de sus propiedades, por lo que si tiene una clase de datos de receta que
se ve así:
(Datos)
clase de datos Receta (título de valor: Cadena, Receta
val ingrediente principal: Cadena, título
principalIngrediente
val es vegetariano: booleano,
esdificultad
dificultad val: Cadena) { vegetariana
}
su código para crear un objeto Receta se verá así:
val r = Receta ("Curry tailandés", "Pollo", falso, "Fácil")
Esto puede no parecer tan malo si su clase de datos tiene una pequeña
cantidad de propiedades, pero imagine que necesita especificar los valores de
10, 20 o incluso 50 propiedades cada vez que necesita crear un nuevo objeto.
Su código se volvería rápidamente mucho más difícil de administrar. Cada clase de datos debe
tener un constructor principal,
Entonces, ¿qué puedes hacer en este tipo de situación?
que debe definir al menos un
¡Valores de parámetros predeterminados al rescate!
parámetro.
Si su constructor define muchas propiedades, puede simplificar las llamadas
asignando un valor o expresión predeterminados a una o más definiciones de Cada parámetro debe
propiedad en el constructor. Así es como, por ejemplo, asignaría valores
predeterminados a las propiedades isVegetarian y dificultad en el constructor tener el prefijo val o var.
de la clase Receta:
isVegetarian tiene un
(Datos)
clase de datos Receta (título de valor: Cadena,
valor predeterminado de falso. Receta
val ingrediente principal: Cadena,
título
val es vegetariano: booleano = falso, el ingrediente
dificultad val: String = "Fácil") { principal es la
dificultad vegetariana
}
la dificultad tiene
un valor
Veamos qué diferencia hace esto en la forma en que creamos nuevos
predeterminado de "Fácil".
objetos de Receta.
206 Capítulo 7
Machine Translated by Google
clases de datos
Cómo usar los valores predeterminados de un constructor
Cuando tiene un constructor que usa valores predeterminados, hay
dos formas principales de llamarlo: pasando valores en orden de
declaración y usando argumentos con nombre. Veamos cómo funcionan
ambos enfoques.
1. Pasar valores en orden de declaración
Este enfoque es el mismo que ya ha estado usando, excepto que no
necesita proporcionar valores para ningún argumento que ya tenga
valores predeterminados.
Supongamos, por ejemplo, que queremos crear un objeto Receta
de espaguetis a la boloñesa para una receta que no es vegetariana y es No hemos especificado valores para los
fácil de hacer. Podemos crear este objeto especificando los valores de las valores de propiedad isVegetarian y dificultad, por lo
dos primeras propiedades en el constructor usando el siguiente código: que el objeto usa sus valores predeterminados.
val r = Receta("Espaguetis a la boloñesa", "Ternera") título: “Espaguetis a la boloñesa”
ingrediente principal: “Carne de
El código anterior asigna valores de "Espaguetis a la boloñesa"
res” es vegetariano: falsa dificultad:
y "Carne de res"
al título
principal. Lyuego
las puropiedades del
pingrediente
sa los valores redeterminados ÁRBITRO
“Fácil”
especificados en el constructor para las propiedades restantes. Receta
r
Puede usar este enfoque para anular los valores de propiedad
val Receta
si no desea usar los valores predeterminados. Si quisiera crear
Asigna a isVegetarian un valor de
un objeto Receta para una versión vegetariana de espaguetis a
verdadero y usa el valor predeterminado
la boloñesa, por ejemplo, podría usar lo siguiente:
para la propiedad de dificultad.
val r = Receta("Espaguetis a la boloñesa", "Tofu", verdadero)
título: “Spaghetti Bolognese”
ingrediente principal: “Tofu”
Esto asigna valores de "Espagueti a la boloñesa", "Tofu" y
isVegetarian: verdadera dificultad:
verdadero a las primeras tres propiedades definidas en el ÁRBITRO
“Fácil”
constructor de Recetas, y usa el valor predeterminado de "Fácil"
Receta
para la propiedad de dificultad final. r
Tenga en cuenta que para utilizar este enfoque, debe pasar
val Receta
los valores en el orden en que se declaran. Por ejemplo, no puede
omitir el valor de la propiedad isVegetarian si desea anular el
valor de la propiedad de dificultad que viene después. El siguiente
código, por ejemplo, no es válido:
Este código no se compilará,
ya que el compilador espera
val r = Receta("Espaguetis a la boloñesa", "Ternera", "Moderado") que el tercer argumento sea
un valor booleano.
Ahora que ha visto cómo funciona pasar valores en orden de declaración,
veamos cómo usar argumentos con nombre en su lugar.
207
estas aqui 4
Machine Translated by Google
argumentos con nombre
2. Usar argumentos con nombre
Debe pasar un valor
Llamar a un constructor usando argumentos con nombre le permite indicar
explícitamente qué propiedad debe asignarse qué valor, sin tener que ceñirse al para cada argumento
orden en que se definen las propiedades.
que no tenga un valor
Supongamos, por ejemplo, que queremos crear un objeto Receta de
espaguetis a la boloñesa que especifique los valores de las propiedades
predeterminado asignado
title y mainIngredient, tal como lo hicimos anteriormente. Para hacer esto
usando argumentos con nombre, usaría el siguiente código:
o su código no se compilará.
val r = Receta(título = "Espaguetis a la boloñesa",
Esto especifica el nombre de cada
ingrediente principal = "Carne de res") propiedad y el valor que debe tener.
El código anterior asigna valores de "Espaguetis a la boloñesa" y
"Carne de res" aLuego
l título
uysa
las
propiedades
los del ingrediente
valores predeterminados principal.
especificados título: “Espaguetis a la boloñesa”
ingrediente principal: “Carne de
en el constructor para las propiedades restantes
res” es vegetariano: falsa dificultad:
ÁRBITRO
“Fácil”
Tenga en cuenta que debido a que estamos usando argumentos Receta
r
con nombre, el orden en que especificamos los argumentos no importa.
El siguiente código, por ejemplo, hace lo mismo que el código anterior
val Receta
y es igualmente válido:
Con argumentos con nombre, el
val r = Receta (ingrediente principal = "Carne de res",
orden en que especifica el valor de
title = "Espaguetis a la boloñesa") cada propiedad no importa.
La gran ventaja de usar argumentos con nombre es que solo
necesita incluir argumentos que no tengan un valor
predeterminado, o cuyo valor predeterminado desee anular. Si
quisiera anular el valor de la propiedad de dificultad, por ejemplo,
podría hacerlo usando un código como este:
val r = Receta(título = "Espaguetis a la boloñesa",
ingrediente principal = "Carne de vacuno", título: “Espaguetis a la boloñesa”
ingrediente principal: “Carne de
dificultad = "Moderado")
res” es vegetariano: falso dificultad:
El uso de valores de parámetros predeterminados y argumentos con
ÁRBITRO
“Moderado”
Receta
nombre no solo se aplica a los constructores de clases de datos; también r
puede usarlos con constructores o funciones de clase normales. Le
mostraremos cómo usar valores predeterminados con funciones después de val Receta
un pequeño desvío.
208 Capítulo 7
Machine Translated by Google
clases de datos
Constructores secundarios
Al igual que en otros lenguajes como Java, las clases en Kotlin te permiten
definir uno o más constructores secundarios. Los constructores secundarios Aunque los constructores secundarios
son constructores adicionales que le permiten pasar diferentes combinaciones no se usan mucho en Kotlinville,
de parámetros para crear objetos. Sin embargo, la mayoría de las veces no es pensamos en brindarle una
necesario utilizarlos, ya que tener valores de parámetros predeterminados es
descripción general rápida para que
muy flexible. sepa cómo se ven.
Aquí hay un ejemplo de una clase llamada Mushroom que define dos
constructores: un constructor principal definido en el encabezado de la
clase y un constructor secundario definido en el cuerpo de la clase: Constructor primario.
clase Champiñón (tamaño de val: Int, val isMagic: Boolean) {
constructor(isMagic_param: Boolean) : this(0, isMagic_param) {
Constructor
secundario. //Código que se ejecuta cuando se llama al constructor secundario
Cada constructor secundario comienza con la palabra clave constructor y
va seguido del conjunto de parámetros que se usan para llamarlo.
Entonces, en el ejemplo anterior, el código:
constructor(isMagic_param: Boolean)
crea un constructor secundario con un parámetro booleano.
Si la clase tiene un constructor primario, cada constructor secundario debe delegar
Esto llama al constructor principal de
en él. El siguiente constructor, por ejemplo, llama al constructor principal de la
la clase actual. Pasa al constructor
clase Mushroom (usando la palabra clave this), pasándole un valor de 0 para la
principal un valor de 0 para el tamaño y
propiedad de tamaño y el valor del parámetro isMagic_param para el parámetro
el valor de isMagic_param para el
isMagic:
parámetro isMagic.
constructor(isMagic_param: Boolean) : this(0, isMagic_param)
Puede definir código adicional que el constructor secundario debe ejecutar
cuando se llama en el cuerpo del constructor secundario:
constructor(isMagic_param: Boolean) : this(0, isMagic_param) {
//Código que se ejecuta cuando se llama al constructor secundario
tamaño: 0
Finalmente, una vez que haya definido un constructor secundario, ÁRBITRO
es magia: cierto
puede usarlo para crear objetos usando un código como este:
metro
Champiñón
val m = Hongo (verdadero)
Champiñón
estas aqui 4 209
Machine Translated by Google
valores predeterminados
Las funciones también pueden usar valores predeterminados
Supongamos que tenemos una función llamada findRecipes que
busca recetas según un conjunto de criterios:
fun findRecipes(título: Cadena,
ingrediente: Cuerda,
es vegetariano: booleano,
dificultad: Cadena) : Array<Receta> {
//Código para encontrar recetas
}
Cada vez que llamamos a la función, debemos pasarle valores para los
cuatro parámetros para que el código se compile así:
val recetas = findRecipes("Curry tailandés", "", false, "")
Podemos flexibilizar la función asignando a cada parámetro un valor
predeterminado. Si lo hace, significa que ya no tenemos que pasar los
cuatro valores a la función para que se compile, solo los que queremos
anular: Esta es la misma función que la
fun findRecipes(título: String = "", anterior, pero esta vez le hemos dado a
cada parámetro un valor predeterminado.
ingrediente: Cadena = "",
es vegetariano: booleano = falso,
dificultad: Cadena = "") : Array<Receta> {
//Código para encontrar recetas
}
Entonces, si quisiéramos pasar a la función un valor de "curry tailandés"
para el parámetro del título y aceptar los valores predeterminados para el
resto, podríamos usar el código:
val recetas = findRecipes("Curry tailandés") Ambos llaman a la función findRecipes,
usando un valor de "curry tailandés"
Y si quisiéramos pasar el valor del parámetro usando argumentos con
para el argumento del título.
nombre, podríamos usar lo siguiente en su lugar:
val recetas = findRecipes(título = "curry tailandés")
El uso de valores predeterminados significa que puede escribir funciones
que son mucho más flexibles. Pero hay momentos en los que es posible que
desee escribir una nueva versión de la función sobrecargándola .
210 Capítulo 7
Machine Translated by Google
clases de datos
Sobrecarga de una función
La sobrecarga de funciones es cuando tiene dos o más funciones con el mismo
nombre pero con diferentes listas de argumentos.
Supongamos que tiene una función llamada addNumbers que se parece a esto:
diversión sumaNúmeros(a: Int, b: Int) : Int {
devolver a + b
Una función
}
La función tiene dos argumentos Int, por lo que solo puede pasarle valores sobrecargada es
Int. Si quisiera usarlo para sumar dos Dobles, tendría que convertir estos valores a
solo una función
Ints antes de pasarlos a la función.
diferente que tiene
Sin embargo, puede hacer la vida mucho más fácil para la persona que llama
sobrecargando la función con una versión que acepta Doubles en su lugar, así: el mismo nombre
diversión sumaNúmeros(a: Doble, b: Doble) : Doble { de función con
devolver a + b Esta es una versión sobrecargada de la
} misma función que usa Doubles en lugar de Ints. diferentes argumentos.
Esto significa que si llama a la función addNumbers usando el código:
Una función
añadirNúmeros(2, 5) sobrecargada
entonces el sistema detectará que los parámetros 2 y 5 son Ints y llamará a la versión NO es lo mismo
Int de la función. Sin embargo, si llama a la función addNumbers usando:
que una función anulada.
añadirNúmeros(1.6, 7.3)
entonces el sistema llamará a la versión Doble de la función en su lugar, ya que los
parámetros son ambos Dobles.
Qué hacer y qué no hacer con la sobrecarga de funciones:
¥ Los tipos de devolución pueden ser diferentes.
Puede cambiar el tipo de devolución de una función sobrecargada, siempre que las listas
de argumentos sean diferentes.
¥ No puede cambiar SOLO el tipo de devolución.
Si solo el tipo de devolución es diferente, no es una sobrecarga válida: el compilador
asumirá que está tratando de anular la función. E incluso eso no será legal a menos
que el tipo de valor devuelto sea un subtipo del tipo de valor devuelto declarado en la
superclase. Para sobrecargar una función, DEBE cambiar la lista de argumentos,
aunque puede cambiar el tipo de retorno a cualquier cosa.
estas aqui 4 211
Machine Translated by Google
código de actualización
Actualicemos el proyecto Recetas
Ahora que ha aprendido a usar valores de parámetros predeterminados y funciones
de sobrecarga, actualicemos el código en el proyecto Recetas.
Actualice su versión del código en el archivo Recipes.kt para que coincida con
el nuestro a continuación (nuestros cambios están en negrita):
clase de datos Receta (título de valor: Cadena,
(Datos)
Asigne valores
Agregue nuevas propiedades val ingrediente principal: Cadena, Receta
predeterminados
de ingrediente principal y dificultad. val es vegetariano: booleano = falso, título
a las propiedades
dificultad val: String = "Fácil") { isVegetarian y el ingrediente
dificultad. principal es la
} Este es un ejemplo de una clase con un constructor
dificultad vegetariana
secundario, solo para que puedas ver uno en acción.
clase Champiñón (tamaño de val: Int, val isMagic: Boolean) {
constructor(isMagic_param: Boolean) : this(0, isMagic_param) {
//Código que se ejecuta cuando se llama al constructor secundario
Champiñón
}
Este es un ejemplo de una función que el
}
utiliza valores de parámetros predeterminados. tamaño es mágico
fun findRecipes(título: String = "",
ingrediente: Cadena = "",
es vegetariano: booleano = falso,
dificultad: Cadena = "") : Array<Receta> {
//Código para encontrar recetas
return arrayOf(Receta(título, ingrediente, es vegetariano, dificultad))
diversión sumaNúmeros(a: Int, b: Int) : Int { Recetas
devolver a + b
} Estas son funciones sobrecargadas. origen
diversión sumaNúmeros(a: Doble, b: Doble) : Doble { Recetas.kt
devolver a + b
212 Capítulo 7
Machine Translated by Google
clases de datos
El código continuó... Hemos cambiado el constructor principal de la receta, por lo que debemos
cambiar la forma en que se llama para que el código se compile.
diversión principal(argumentos: Array<String>) {
val r1 = Receta ("Curry tailandés", "Pollo" falso)
val r2 = Receta (título = "Curry tailandés", ingrediente principal = "Pollo" falso)
val r3 = r1.copy(título = "Pollo Bhuna") println("Código hash r1:
${r1.hashCode()}") println("Código hash r2: ${r2.hashCode()}")
println( "código hash r3: ${r3.hashCode()}") println("r1 toString: Recetas
${r1.toString()}") println("r1 == r2? ${r1 == r2}") println( "r1 ===
origen
r2? ${r1 === r2}") println("r1 == r3? ${r1 == r3}") val (título,
ingrediente principal, vegetariano, dificultad) = r1 println ( " título
es $título y vegetariano es $vegetariano") Incluya las nuevas propiedades de
Recetas.kt
Recipe cuando desestructuramos r1.
Cree un hongo llamando a su constructor principal.
val m1 = Hongo(6, falso)
println("el tamaño de m1 es ${m1.size} y isMagic es ${m1.isMagic}")
val m2 = Hongo (verdadero)
Crea un Mushroom llamando a su constructor secundario.
println("el tamaño en m2 es ${m2.size} y isMagic es ${m2.isMagic}")
Llame a la versión Int de addNumbers.
println(agregarNúmeros(2, 5))
println(agregarNúmeros(1.6, 7.3))
Llame a la versión doble de addNumbers.
}
Prueba de conducción
Cuando ejecuta su código, el siguiente texto se imprime en la ventana
de salida del IDE:
código hash r1: 295805076
código hash r2: 295805076
código hash r3: 1459025056 r1
toString: receta (título = curry tailandés, ingrediente principal = pollo, es vegetariano = falso, dificultad = fácil) r1 == r2?
cierto r1 === r2? falso r1 == r3? el título falso es Thai Curry y vegetariano es falso m1 el tamaño es 6 e isMagic es falso
m2 el tamaño es 0 e isMagic es verdadero 7 8.9
estas aqui 4 213
Machine Translated by Google
sin preguntas tontas
P: ¿Puede una clase de datos incluir funciones? P: Quiero que los programadores de Java puedan usar mi Kotlin
clases, pero Java no tiene ningún concepto de valores de parámetros predeterminados.
¿Puedo seguir usando valores de parámetros predeterminados en mis clases de Kotlin?
R: Sí. Usted define funciones de clase de datos exactamente de la misma manera
que defina funciones en una clase que no sea de datos: agregándolas al
cuerpo de la clase. R: Puedes. Cuando llamas a un constructor o función de Kotlin desde
Java, solo asegúrese de que el código Java especifique un valor para cada
parámetro, incluso si tiene un valor de parámetro predeterminado.
P: Los valores de los parámetros predeterminados parecen muy flexibles.
Si planea hacer muchas llamadas Java a su constructor o función de
R: ¡Lo son! Puede usarlos en constructores de clases (incluidos
Kotlin, un enfoque alternativo es anotar cada función o constructor que
constructores de clases de datos) y funciones, e incluso puede tener un
usa valores de parámetros predeterminados con @JvmOverloads. Esto
valor de parámetro predeterminado que sea una expresión. Esto significa que
le dice al compilador que cree automáticamente versiones sobrecargadas
puede escribir código que sea flexible, pero muy conciso.
que se pueden llamar más fácilmente desde Java.
P: Dijiste que usar valores de parámetros predeterminados principalmente
soluciona la necesidad de escribir constructores secundarios.
Aquí hay un ejemplo de cómo usa @JvmOverloads con una
¿Hay alguna situación en la que aún pueda necesitarlos? función:
R: La situación más común es si necesita extender una clase @JvmOverloads fun myFun(str: String = ""){
en un marco (como Android) que tiene múltiples constructores. //El código de la función va aquí
}
Puede obtener más información sobre el uso de constructores secundarios en
Documentación en línea de Kotlin: Y aquí hay un ejemplo de cómo lo usa con una clase que tiene un
constructor principal:
https://kotlinlang.org/docs/reference/classes.html
clase Foo @JvmOverloads constructor(i: Int = 0){
//Código de clase se encuentra aquí
}
Tenga en cuenta que para anotar el constructor principal
con @JvmOverloads, también debe prefijar el constructor con la
palabra clave constructor . La mayoría de las veces, esta palabra
clave es opcional.
214 Capítulo 7
Machine Translated by Google
clases de datos
SER el compilador
Aquí hay dos archivos Kotlin completos.
Su trabajo es jugar como si fuera el
compilador y determinar si cada uno
de estos archivos se
compilará. Si no se
compilan, ¿cómo los
arreglaría?
estudiante de la clase de datos (val firstName: String, val lastName: String,
val casa: Cadena, val año: Int = 1)
diversión principal(argumentos: Array<String>) {
valor s1 = Estudiante("Ron", "Weasley", "Gryffindor")
val s2 = Estudiante("Draco", "Malfoy", casa = "Slytherin")
val s3 = s1.copy(firstName = "Fred", año = 3)
val s4 = s3.copy(firstName = "George")
valor matriz = matrizDe(s1, s2, s3, s4)
for ((nombre, apellido, casa, año) en matriz) {
println("$firstName $lastName está en $house year $year")
}
}
estudiante de la clase de datos (val firstName: String, val lastName: String,
val casa: Cadena, val año: Int = 1)
diversión principal(argumentos: Array<String>) {
valor s1 = Estudiante("Ron", "Weasley", "Gryffindor")
val s2 = Estudiante(apellido = "Malfoy", nombre = "Draco", año = 1)
val s3 = s1.copy(firstName = "Fred")
s3.año = 3
val s4 = s3.copy(firstName = "George")
valor matriz = matrizDe(s1, s2, s3, s4)
for (s in array) { println("$
{s.firstName} ${s.lastName} está en ${s.house} año ${s.year}")
}
}
estas aqui 4 215
Machine Translated by Google
ser la solución del compilador
SER la solución del compilador
Aquí hay dos archivos Kotlin completos.
Su trabajo es jugar como si fuera el
compilador y determinar si cada uno
de estos archivos se
compilará. Si no se
compilan, ¿cómo los
arreglaría?
estudiante de la clase de datos (val firstName: String, val lastName: String,
val casa: Cadena, val año: Int = 1)
Esto se compilará y ejecutará
con éxito. Imprime los valores
diversión principal(argumentos: Array<String>) {
de propiedad de nombre,
valor s1 = Estudiante("Ron", "Weasley", "Gryffindor")
val s2 = Estudiante("Draco", "Malfoy", casa = "Slytherin") apellido, casa y año para cada
val s3 = s1.copy(firstName = "Fred", año = 3) estudiante.
val s4 = s3.copy(firstName = "George")
Esta línea desestructura
valor matriz = matrizDe(s1, s2, s3, s4)
cada objeto Student en la matriz.
for ((nombre, apellido, casa, año) en matriz) {
println("$firstName $lastName está en $house year $year")
}
}
estudiante de la clase de datos (val firstName: String, val lastName: String,
val casa: Cadena, val año: Int = 1)
diversión principal(argumentos: Array<String>) {
valor s1 = Estudiante("Ron", "Weasley", "Gryffindor")
val s2 = Estudiante(apellido = "Malfoy", nombre = "Draco", año = 1, casa = "Slytherin") = 3) val s3 = s1.copy(primerNombre =
"Fred", año s3.año = 3
Esto no se compilará ya que se requiere un valor para la
val s4 = s3.copy(firstName = "George")
propiedad de la casa de s2, y como el año se define usando
valor matriz = matrizDe(s1, s2, s3, s4) val, su valor solo se puede establecer cuando se inicializa.
for (s in array) { println("$
{s.firstName} ${s.lastName} está en ${s.house} año ${s.year}")
}
}
216 Capítulo 7
Machine Translated by Google
clases de datos
Tu caja de herramientas de Kotlin
Puede descargar
CAPÍTULO
7 Tienes el Capítulo 7 en tu haber y ahora tienes
clases de datos añadidas y por defecto
el código
completo del
capítulo desde https://
tinyurl.com/HFKotlin.
valores de parámetros a su caja de herramientas.
El comportamiento del operador == está determinado por la Las funciones de componenteN le permiten desestructurar
implementación de la función equals. objetos de datos en sus valores de propiedad de
componente.
Cada clase hereda una función equals, hashCode y
toString de la clase Any porque cada clase es una Una clase de datos genera sus funciones
subclase de Any. Estas funciones se pueden anular. considerando las propiedades definidas en su constructor
primario.
La función equals te dice si dos objetos se consideran Los constructores y las funciones pueden tener
"iguales". De forma predeterminada, devuelve verdadero si valores de parámetros predeterminados. Puede llamar a
se usa para probar el mismo objeto subyacente y falso si se un constructor o función pasando valores de parámetros
usa para probar objetos separados. en orden de declaración o usando argumentos con nombre.
El operador === le permite verificar si dos variables se Las clases pueden tener constructores secundarios.
refieren al mismo objeto subyacente independientemente
Una función sobrecargada es una función diferente
del tipo de objeto.
que tiene el mismo nombre de función.
Una clase de datos le permite crear objetos cuyo Una función sobrecargada debe tener diferentes
objetivo principal es almacenar datos. Anula argumentos, pero puede tener un tipo de retorno diferente.
automáticamente las funciones equals, hashCode y
toString, e incluye funciones de copia y componenteN.
La clase de datos es igual a la función verifica la Reglas para clases
igualdad al observar los valores de propiedad de cada
objeto. Si dos objetos de datos contienen los mismos
un
de cdonstructor
atos * Debe
primario.
haber
datos, la función de igualdad devuelve verdadero.
más
* El p
constructor
arámetros.principal debe definir uno o
La función de copia le permite crear una nueva copia de un
objeto de datos, modificando algunas de sus propiedades.
El objeto original permanece intacto. * Cada parámetro debe estar marcado como
valor o var.
*Las clases de datos no deben estar abiertas o
abstracto.
estas aqui 4 217
Machine Translated by Google
Machine Translated by Google
8 nulos y excepciones
Seguro y Sonido
¡Ay, Elvis! Sé que mi
código está a salvo contigo.
Todo el mundo quiere escribir código que sea seguro.
Y la buena noticia es que Kotlin fue diseñado teniendo en cuenta la seguridad del código.
Comenzaremos mostrándole cómo el uso de Kotlin de tipos anulables significa que casi nunca
experimentará una NullPointerException durante toda su estadía en Kotlinville. Descubrirás cómo hacer
llamadas seguras y cómo el operador Elvis de Kotlin evita que te emociones. Y cuando hayamos
terminado con nulos, descubrirá cómo lanzar y capturar excepciones como un profesional.
este es un nuevo capitulo 219
Machine Translated by Google
eliminando referencias
¿Cómo se eliminan las
referencias a objetos de las variables?
Como ya sabe, si desea definir una nueva variable Wolf y asignarle una
referencia de objeto Wolf, puede hacerlo usando un código como este:
var w = Lobo()
El compilador detecta que desea asignar un objeto Wolf a la variable w,
por lo que infiere que la variable debe tener un tipo de Wolf:
Como está asignando un objeto Wolf a
una variable, el compilador infiere que el
ÁRBITRO
tipo de variable también debe ser Wolf.
w Lobo
var lobo
Una vez que el compilador conoce el tipo de variable, se asegura de
que solo pueda contener referencias a objetos Wolf, incluidos los
subtipos de Wolf. Entonces, si la variable se define usando var, puede
actualizar su valor para que contenga una referencia a un objeto Wolf
completamente diferente usando, por ejemplo:
w = Lobo()
Eliminar la referencia
a este objeto Wolf...
ÁRBITRO
Lobo
w
var lobo ... y asigne una referencia
a este en su lugar.
Lobo
Pero, ¿qué sucede si desea actualizar la variable para que no contenga
ninguna referencia a ningún objeto? ¿Cómo elimina una referencia de
objeto de una variable una vez que se ha asignado una?
220 Capítulo 8
Machine Translated by Google
nulos y excepciones
Eliminar una referencia de objeto usando nulo
Si desea eliminar una referencia a un objeto de una variable, puede hacerlo
asignándole un valor nulo: El significado de nulo
w = nulo
Cuando establece una variable
en nulo, es como desprogramar un
Un valor nulo significa que la variable no se refiere a un objeto: la variable
control remoto. Tiene un control
aún existe, pero no apunta a nada
remoto (la variable), pero ningún
Pero hay una gran captura. De forma predeterminada, los tipos en Kotlin no televisor en el otro extremo (el objeto).
aceptarán valores nulos. Si desea que una variable contenga valores nulos,
debe declarar explícitamente que su tipo admite valores nulos. Una referencia nula tiene bits que representan
"nulo", pero no sabemos ni nos importa cuáles
¿Por qué tener tipos anulables? son esos bits. El sistema maneja esto
automáticamente por nosotros.
Un tipo anulable es aquel que permite valores nulos. A diferencia de
otros lenguajes de programación, Kotlin rastrea valores que pueden ser
nulos para evitar que realices acciones no válidas en ellos. Realizar acciones
no válidas en valores nulos es la causa más común de problemas de tiempo
de ejecución en otros lenguajes como Java, y puede hacer que su aplicación Si intenta realizar una operación no válida en un
se bloquee cuando menos lo espera. Sin embargo, estos problemas rara vez valor nulo en Java, se enfrentará a una
NullPointerException grande y gorda. Una excepción
ocurren en Kotlin debido a su uso inteligente de tipos anulables.
es una advertencia que le dice que acaba de suceder
algo excepcionalmente malo. Veremos las excepciones
Usted declara que un tipo admite valores NULL agregando un signo de con más detalle más adelante en este capítulo.
interrogación (?) al final del tipo. Para crear una variable Wolf anulable y
asignarle un nuevo objeto Wolf, por ejemplo, usaría el código:
var w: ¿Lobo? = Lobo() w es un Wolf?, lo que significa que puede
contener referencias a objetos Wolf y nulo.
ÁRBITRO
w
Un tipo anulable es
Lobo
var Lobo?
aquel que puede
Y si quisiera eliminar la referencia Wolf de la variable, usaría:
contener valores nulos
Establecer w en
w = nulo nulo elimina la
referencia al objeto Wolf. además de su tipo base.
ÁRBITRO ¿Un pato? variable, por
w ejemplo, aceptará
var Lobo? Lobo objetos Duck y null.
Entonces, ¿dónde puedes usar tipos anulables?
221
estas aqui 4
Machine Translated by Google
dónde usar tipos anulables
Puede usar un tipo que acepta valores NULL en cualquier lugar
donde pueda usar un tipo que no acepta valores NULL
Cada tipo que defina se puede convertir en una versión anulable de ese tipo simplemente
agregando un ? hasta el final de la misma. Puede usar tipos que aceptan valores NULL en
los mismos lugares en los que usaría tipos antiguos que no aceptan valores NULL:
¥ Al definir variables y propiedades.
Cualquier variable o propiedad puede aceptar valores NULL, pero debe definirla
explícitamente como tal declarando su tipo, incluido el ?. El compilador no puede
inferir cuándo un tipo es anulable y, de forma predeterminada, siempre creará un
tipo no anulable. Entonces, si desea crear una variable de cadena anulable llamada
str e instanciarla con un valor de "Pizza", ¿debe declarar que tiene un tipo de
cadena? como esto:
var str: ¿Cadena? = "Pizza"
Tenga en cuenta que las variables y las propiedades se pueden instanciar con
nulo. El siguiente código, por ejemplo, compila e imprime el texto "null":
var str: ¿Cadena? = nulo
Esto es diferente a decir
“”
println(cadena) var str: String? = es un
“”
objeto String que no contiene caracteres,
¥ Al definir parámetros. mientras que null no es un objeto String.
Puede declarar cualquier tipo de parámetro de función o constructor como anulable.
El siguiente código, por ejemplo, define una función llamada printInt que toma un
parámetro de tipo Int? (un Int anulable):
divertido printInt(x: Int?) {
imprimir(x)
}
Cuando define una función (o constructor) con un parámetro que acepta valores NULL,
aún debe proporcionar un valor para ese parámetro cuando llama a la función, incluso si
ese valor es NULL. Al igual que con los tipos de parámetros que no aceptan valores NULL,
no puede omitir un parámetro a menos que se le haya asignado un valor predeterminado.
¥ Al definir los tipos de devolución de funciones.
Una función puede tener un tipo de devolución anulable. La siguiente función, por
ejemplo, tiene un tipo de devolución Long?:
La función debe devolver un valor que sea Long o nulo.
resultado divertido() : ¿ Largo? {
//¿Código para calcular y devolver un Long?
}
También puede crear matrices de tipos anulables. Veamos cómo.
222 Capítulo 8
Machine Translated by Google
nulos y excepciones
Cómo crear una matriz de tipos anulables
Una matriz de tipos anulables es aquella cuyos elementos son anulables.
El siguiente código, por ejemplo, crea una matriz denominada myArray
que contiene String?s (cadenas que aceptan valores NULL):
Un Array<String?> puede
var myArray: Array<String?> = arrayOf("Hola", "Hola") contener cadenas y valores nulos.
Sin embargo, el compilador puede inferir que la matriz debe contener
tipos anulables si la matriz se inicializa con uno o más elementos nulos.
Entonces, cuando el compilador ve el siguiente código:
var myArray = arrayOf("Hola", "Hola", nulo)
detecta que la matriz puede contener una combinación de
cadenas y valores nulos, e infiere que la matriz debe tener un tipo
de Array<String?>:
"Hola" "Hola"
Cadena Cadena
ÁRBITRO nulo
ÁRBITRO ÁRBITRO ÁRBITRO
El tercer elemento se ha inicializado con un valor
mi
Formación
nulo. Como la matriz contiene cadenas y valores
nulos, crea una matriz que puede contener cadenas.
var Matriz<Cadena?>
0 1 2
Ahora que ha aprendido a definir tipos que aceptan valores NULL, veamos
cómo hacer referencia a las funciones y propiedades de su objeto.
P: ¿Qué sucede si inicializo una variable con un valor nulo, P: Usted dijo en el capítulo anterior que cada objeto es un
y dejar que el compilador infiera el tipo de variable? Por ejemplo: subclase de Any. ¿ Puede una variable cuyo tipo es Cualquiera contener
valores nulos?
var x = nulo
R: No. Si desea que una variable contenga referencias a cualquier tipo de
R: El compilador ve que la variable debe poder contener objeto y valores nulos, su tipo debe ser Cualquiera?. Por ejemplo:
valores nulos, pero como no tiene información sobre ningún otro
tipo de objeto que pueda necesitar, crea una variable que solo puede var z: ¿Alguno?
contener un valor nulo . Probablemente esto no sea lo que desea,
por lo que si va a inicializar una variable con un valor nulo, asegúrese
de especificar su tipo.
estas aqui 4 223
Machine Translated by Google
acceder a miembros de tipo anulable
Cómo acceder a las funciones y propiedades de un tipo anulable
Suponga que tiene una variable cuyo tipo admite valores NULL y desea acceder
a las propiedades y funciones de su objeto. No puede realizar llamadas a
funciones o consultar las propiedades de un valor nulo, ya que no tiene ninguna.
Para evitar que realice operaciones que no son válidas, el compilador insiste en
que verifique que la variable no sea nula antes de darle acceso a cualquier función
o propiedad.
¿Imagina que tienes un lobo? variable a la que se le ha asignado una
referencia a un nuevo objeto Wolf como este:
var w: ¿Lobo? = Lobo()
Para acceder a las funciones y propiedades del objeto subyacente, primero
debe establecer que el valor de la variable no es nulo. Una forma de lograr
esto es verificar el valor de la variable dentro de un if. El siguiente código, por
ejemplo, verifica que el valor de w no sea nulo y luego llama a la función eat del comer()
objeto:
ÁRBITRO
si (w != nulo) {
w
comer() El compilador sabe que w no es nulo, por
} lo que puede llamar a la función eat(). var Lobo? Lobo
Puede utilizar este enfoque para crear condiciones más complejas. El
siguiente código, por ejemplo, verifica que el valor de la variable w no sea nulo
y luego llama a su función comer cuando su propiedad hambre es menor que 5:
if (w != null && w.hambre < 5) {
comer()
El lado derecho de && solo se ejecuta si el lado izquierdo es verdadero, por lo que
}
aquí, el compilador sabe que w no puede ser nulo y le permite llamar a w.hunger.
Sin embargo, hay algunas situaciones en las que este tipo de código aún puede fallar.
Si la variable w se usa para definir una propiedad var en una clase, por ejemplo, es
posible que se le haya asignado un valor nulo entre la comprobación de nulo y su
uso, por lo que el siguiente código no se compilará:
clase Mi Lobo {
var w: ¿Lobo? = Lobo()
diversión myFunction() {
si (w != nulo){
comer()
} Esto no se compilará porque el compilador no puede
} garantizar que algún otro código no actualice la propiedad w
entre la comprobación de que no es nulo y su uso.
}
Afortunadamente, existe un enfoque más seguro que evita este tipo de problema.
224 Capítulo 8
Machine Translated by Google
nulos y excepciones
Mantén las cosas seguras con llamadas seguras
Si desea acceder a las propiedades y funciones de un tipo que acepta valores ?. es el operador de
NULL, un enfoque alternativo es usar una llamada segura. Una llamada segura le
permite acceder a funciones y propiedades en una sola operación sin tener que llamadas seguras. Le
realizar una verificación nula por separado.
Para ver cómo funcionan las llamadas seguras, imagina que tienes un Wolf.
permite acceder de
propiedad (como antes) que contiene una referencia a un objeto Wolf así:
forma segura a las
var w: ¿Lobo? = Lobo()
funciones y propiedades
Para hacer una llamada segura a la función de comer de Wolf, usaría el siguiente
código: de un tipo anulable.
w?.comer() El ?. significa que solo se llama a eat() si w no es nulo.
Esto solo llamará a la función comer de Wolf cuando w no sea nulo. Es como decir "si w
no es nulo, llame a comer".
De manera similar, el siguiente código hace una llamada segura a la propiedad hambre de w:
w?.hambre
Si w no es nulo, la expresión devuelve una referencia al valor de la propiedad
hambre. Sin embargo, si w es nulo, el valor de la expresión completa se evalúa
como nulo. Estos son los dos escenarios:
A Escenario A: w no es nulo.
La variable w contiene una referencia a un objeto Wolf y el valor de su propiedad
hambre es 10. El código w?.hunger se evalúa como 10.
w?.hambre
//Devuelve 10 ÁRBITRO
hambre: 10
w
var Lobo? Lobo
B Escenario B: w es nulo.
La variable w contiene un valor nulo, no Wolf, por lo que la expresión completa se
evalúa como nula.
w?.hambre
Soy nulo, por lo que no tengo
//Devuelve nulo
ÁRBITRO acceso a las propiedades de Wolf.
var Lobo?
estas aqui 4 225
Machine Translated by Google
la cadena
Puedes encadenar llamadas seguras juntas
Otra ventaja de usar llamadas seguras es que puede encadenarlas
para formar expresiones poderosas pero concisas.
Suponga que tiene una clase llamada MyWolf que tiene un solo
lobo. propiedad llamada w. Aquí está la definición de clase:
clase Mi Lobo {
var w: ¿Lobo? = Lobo()
Supongamos también que tienes un MyWolf? variable llamada myWolf
así:
var mi Lobo: Mi Lobo? = Mi Lobo()
Si desea obtener el valor de la propiedad hambre para Wolf de la
variable myWolf, puede hacerlo usando un código como este:
mi lobo?.w?.hambre Si myWolf no es nulo y w no es nulo, obtenga hambre. De lo contrario, utilice nulo.
Es como decir “Si myWolf o w es nulo, devuelve un valor nulo.
De lo contrario, devolver el valor de la propiedad hambre de
w ”. La expresión devuelve el valor de la propiedad hambre si (y solo si)
myWolf y w no son nulos. Si myWolf o w son nulos, la expresión completa
se evalúa como nula.
Qué sucede cuando se evalúa una cadena de llamadas segura
Analicemos lo que sucede cuando el sistema evalúa la cadena de
llamadas seguras:
mi lobo?.w?.hambre
1 El sistema primero verifica que myWolf no sea nulo.
Si myWolf es nulo, la expresión completa se evalúa como nula. Si myWolf no es
nulo (como en este ejemplo), el sistema continúa con la siguiente parte de la
expresión.
mi lobo?.w?.hambre
ÁRBITRO
Mi lobo
var Mi Lobo? Mi lobo
226 Capítulo 8
Machine Translated by Google
nulos y excepciones
La historia continúa...
2 Luego, el sistema verifica que la propiedad w de myWolf no sea nula.
Siempre que myWolf no sea nulo, el sistema pasa a la siguiente parte de la
expresión, la w? parte.
Si w es nulo, la expresión completa se evalúa como nula. Si w no es nulo, como
en este ejemplo, el sistema pasa a la siguiente parte de la expresión.
mi lobo?.w?.hambre
ÁRBITRO
ÁRBITRO
w
var Lobo? Lobo
Mi lobo
var Mi Lobo? Mi lobo
3 Si w no es nulo, devuelve el valor de la propiedad de hambre de w.
Siempre que ni la variable myWolf ni su propiedad w sean nulas, la expresión
devuelve el valor de la propiedad hambre de w. En este ejemplo, la expresión
se evalúa como 10.
mi lobo?.w?.hambre
ÁRBITRO
hambre: 10
ÁRBITRO
w
var Lobo? Lobo
Mi lobo
var Mi Lobo? Mi lobo
Entonces, como puede ver, las llamadas seguras se pueden encadenar
para formar expresiones concisas que son muy poderosas pero seguras.
Pero ese no es el final de la historia.
estas aqui 4 227
Machine Translated by Google
llamadas seguras
Puedes usar llamadas seguras para asignar valores...
Como era de esperar, puede usar llamadas seguras para asignar un valor a una
variable o propiedad. Si tienes un lobo? variable llamada w, por ejemplo, puede
asignar el valor de su propiedad hambre a una nueva variable llamada x usando un
Si la propiedad hambre de w es 10,
código como este:
var x = w?.hunger crea un Int?
variable con un valor de 10.
var x = w?.hambre
Es como decir "Si w es nulo, establezca x en nulo, de lo contrario, establezca x
en el valor de la propiedad de hambre de w ". Como la expresión: ÁRBITRO
10
w?.hambre X
Int?.
...y asigna valores a las llamadas seguras
También puede usar una llamada segura en el lado izquierdo de una variable
o asignación de propiedad.
Suponga, por ejemplo, que desea asignar un valor de 6 a la propiedad de hambre
de w, siempre que w no sea nulo. Puedes lograr esto usando el código:
Si w no es nulo,
w?.hunger = 6
w?.hambre = 6 establece la propiedad de hambre de w en 6.
El código verifica el valor de w, y si no es nulo, el código asigna un valor de 6 a la ÁRBITRO
propiedad hambre. Sin embargo, si w es nulo, el código no hace nada. hambre: 10 6
w
mi lobo?.w?.hambre = 2
Es como decir "si myWolf no es nulo, y el valor de la propiedad w de myWolf no
es nulo, entonces asigne un valor de 2 a la propiedad de hambre de w ":
ÁRBITRO
hambre: 2
ÁRBITRO
w
Lobo el hambre se establece en 2 solo
var Lobo?
Mi lobo si myWolf y w no son nulos.
var Mi Lobo? Mi lobo
Ahora que sabe cómo hacer llamadas seguras a tipos que aceptan valores NULL,
pruebe el siguiente ejercicio.
228 Capítulo 8
Machine Translated by Google
nulos y excepciones
SER el compilador
Cada uno de los archivos de Kotlin en
esta página representa un archivo fuente Esta es la salida requerida.
completo. Su trabajo es jugar como si fuera Misty: ¡Miau!
el compilador y determinar si Calcetines: ¡Miau!
cada uno de estos archivos
compilará y producirá el
resultado de la derecha. ¿Si no, porque no?
A clase Gato(var nombre: String? = "") { divertido Miau() B class Cat(var nombre: String? = null) {
{ println("¡Miau!") } divertido Miau() { println("¡Miau!") }
} }
diversión principal(argumentos: Array<String>) { diversión principal(argumentos: Array<String>) {
var myCats = arrayOf(Cat("Misty"), var myCats = arrayOf(Cat("Misty"),
nulo, Gato (nulo),
Gato("Calcetines")) Gato("Calcetines"))
para (gato en myCats) { para (gato en myCats) {
if (gato!= nulo) { print("$ print("${gato.nombre}: ")
{gato.nombre}: ") gato.Miau() gato.Miau()
}
} }
}
}
C class Cat(var nombre: String? = null) { D clase Gato(var nombre: String = "") {
divertido Miau() { println("¡Miau!") } divertido Miau() { println("¡Miau!") }
} }
diversión principal(argumentos: Array<String>) { diversión principal(argumentos: Array<String>) {
var myCats = arrayOf(Cat("Misty"), var myCats = arrayOf(Cat("Misty"),
nulo, Gato (nulo),
Gato ("Calcetines")) Gato ("Calcetines"))
for (gato en myCats) { print("$ para (gato en myCats) {
{gato?.nombre}: ") if (gato!= nulo) { print("$
¿gato?. Miau() {gato?.nombre}: ") gato?.Miau()
}
} }
}
}
estas aqui 4 229
Machine Translated by Google
ser la solución del compilador
SER la solución del compilador
Cada uno de los archivos de Kotlin en
esta página representa un archivo fuente Esta es la salida requerida.
completo. Su trabajo es jugar como si fuera Misty: ¡Miau!
el compilador y determinar si Calcetines: ¡Miau!
cada uno de estos archivos
compilará y producirá el
resultado de la derecha. ¿Si no, porque no?
A clase Gato(var nombre: String? = "") { divertido Miau() B class Gato(var nombre: String? = null) { divertido Miau()
{ println("¡Miau!") } { println("¡Miau!") }
} }
diversión principal(argumentos: Array<String>) { diversión principal(argumentos: Array<String>) {
var myCats = arrayOf(Cat("Misty"), var myCats = arrayOf(Cat("Misty"),
nulo, Gato (nulo),
Gato("Calcetines")) Gato("Calcetines"))
para (gato en myCats) { para (gato en myCats) {
if (gato!= nulo) { print("$ print("${gato.nombre}: ")
{gato.nombre}: ") gato.Miau() gato.Miau()
}
Esto compila, pero el resultado es
} }
Esto compila y produce la incorrecto (el segundo Gato con un
}
} salida correcta. nombre nulo también Meows).
C class Cat(var nombre: String? = null) { D clase Gato(var nombre: String = "") {
divertido Miau() { println("¡Miau!") } divertido Miau() { println("¡Miau!") }
} }
diversión principal(argumentos: Array<String>) { diversión principal(argumentos: Array<String>) {
var myCats = arrayOf(Cat("Misty"), var myCats = arrayOf(Cat("Misty"),
nulo, Gato (nulo),
Gato ("Calcetines")) Gato ("Calcetines"))
for (gato en myCats) { print("$ para (gato en myCats) {
{gato?.nombre}: ") si (gato! = nulo) {
¿gato?. Miau() print("${gato?.nombre}: ") gato?.Miau()
}
Esto compila, pero el resultado es
} }
incorrecto (se imprime nulo para el } Esto no se compila porque Cat no
} puede tener un nombre nulo.
segundo elemento en la matriz myCats).
230 Capítulo 8
Machine Translated by Google
nulos y excepciones
Use let para ejecutar código si los valores no son nulos
Cuando usa tipos anulables, es posible que desee ejecutar código si (y solo
si) un valor particular no es nulo. Si tienes un lobo? variable llamada w, por
ejemplo, es posible que desee imprimir el valor de la propiedad de hambre de w
siempre que w no sea nulo.
Una opción para realizar este tipo de tareas es usar el código:
si (w != nulo ) {
println(con.hambre)
Esto puede suceder si, por ejemplo, w define una propiedad
Pero si el compilador no puede garantizar que la variable w no cambie var en una clase y desea usar su propiedad hambre en una
entre la comprobación de nulos y su uso, el código no se compilará. función separada. Es la misma situación que vio anteriormente
en el capítulo cuando presentamos la necesidad de llamadas
seguras.
Un enfoque alternativo que funcionará en todas las situaciones es usar el
código:
w?.let {
println(es.hambre) Si w no es nulo, imprimamos su hambre.
}
Es como decir “si w no es nulo, imprimamos su hambre”. Vamos a caminar
a través de esto.
?.let le permite
La palabra clave let utilizada junto con el operador de llamada segura ?. le dice
al compilador que desea realizar alguna acción cuando el valor en el que está
ejecutar código para
operando no es nulo. Entonces el siguiente código:
un valor que no es nulo.
w?.let {
// Codigo para hacer algo
solo ejecutará el código en su cuerpo si w no es nulo.
Una vez que haya establecido que el valor no es nulo, puede consultarlo en el
cuerpo del let usándolo . Entonces, en el siguiente ejemplo de código, se refiere
ÁRBITRO
a una versión no anulable de la variable w, lo que le permite acceder directamente
hambre: 6
a su propiedad de hambre: w
w?.let { Lobo
Puede usar "it" para acceder var Lobo?
println(es.hambre) directamente a las funciones y
ÁRBITRO
“it” es una versión no
} propiedades de Wolf.
anulable de w que hace
él
Veamos un par de ejemplos más de cuándo usar let puede ser útil. referencia al mismo objeto Wolf.
Puede referirse a "eso"
var lobo
dentro del cuerpo let's.
231
estas aqui 4
Machine Translated by Google
usando let
Usando let con elementos de matriz
let también se puede usar para realizar acciones usando los elementos no
nulos de una matriz. Puede usar el siguiente código, por ejemplo, para
recorrer una matriz de cadenas e imprimir cada elemento que no sea nulo:
var array = arrayOf("Hola", "Hola", nulo)
para (elemento en la matriz) {
artículo?.let {
imprimir (es) Esta línea solo se ejecuta para elementos no nulos en la matriz
Uso de let para simplificar expresiones
let es particularmente útil en situaciones en las que desea realizar acciones
en el valor de retorno de una función que puede ser nulo.
Debe usar
Supongamos que tiene una función llamada getAlphaWolf que tiene un
llaves para
tipo de retorno Wolf. como esto:
indicar el
divertido getAlphaWolf() : ¿Lobo? {
cuerpo let.
devolver lobo()
} Si omite los { }, su código no se
compilará.
Si quisieras obtener una referencia al valor de retorno de la función y
llamar a su función eat si no es nula, podrías hacerlo (en la mayoría de
las situaciones) usando el siguiente código:
var alfa = getAlphaWolf()
si (alfa! = nulo) {
alfa.comer()
Sin embargo, si tuviera que reescribir el código usando let, ya no
necesitaría crear una variable separada en la que almacenar el valor de
retorno de la función. En su lugar, podrías usar:
getAlphaWolf()?.let { Usar let es más conciso. También es seguro, por
eso.comer() lo que puede usarlo en todas las situaciones.
Es como decir “coge el lobo alfa, y si no es nulo, que se lo coma”.
232 Capítulo 8
Machine Translated by Google
nulos y excepciones
En lugar de usar una expresión if...
Otra cosa que puede querer hacer cuando tiene tipos anulables es usar una
expresión if que especifique un valor alternativo para algo que es nulo.
Supongamos que tienes un Lobo? variable llamada w, como antes, y desea
utilizar una expresión que devuelva el valor de la propiedad de hambre de w si
w no es nulo, pero el valor predeterminado es 1 si w es nulo. En la mayoría de
las situaciones, la siguiente expresión funcionará:
if (w != null) w.hambre else 1
Pero como antes, si el compilador cree que existe la posibilidad de que la
variable w se haya actualizado entre la verificación de valores nulos y su uso,
el código no se compilará porque el compilador considera que no es seguro.
[Nota del editor: ¿Elvis? ¿Esto es una
Afortunadamente existe una alternativa: el operador Elvis.
broma? Devolver al remitente.]
...puedes usar el operador Elvis más seguro
Muchas
El operador de Elvis ?: es una alternativa segura a una expresión if. Se llama gracias.
el operador de Elvis porque cuando lo inclinas de lado, se parece un poco a ?:
Elvis.
Aquí hay un ejemplo de una expresión que usa un operador de Elvis:
w?.hambre?: 1 Este es el operador de Elvis.
El operador de Elvis primero verifica el valor a su izquierda, en este caso:
w?.hambre
Si este valor no es nulo, el operador de Elvis lo devuelve. Sin embargo, si el El operador de
valor de la izquierda es nulo, el operador de Elvis devuelve el valor de la Elvis?: es una versión
derecha (en este caso, 1). Entonces el código
w?.hambre?: 1 segura de una expresión
es como decir “si w no es nulo y su propiedad de hambre no es nula, if. Devuelve el valor a su
devolver el valor de la propiedad de hambre , de lo contrario devolver 1”.
Hace lo mismo que el código: izquierda si no es nulo.
if (w?.hunger != null) w.hunger else 1 De lo contrario,
pero debido a que es una alternativa más segura, puede usarlo en cualquier lugar.
devuelve el valor a su derecha.
En las últimas páginas, ha visto cómo acceder a las propiedades y
funciones de un tipo anulable mediante llamadas seguras, y cómo usar let y el
operador Elvis en lugar de declaraciones y expresiones if.
Solo hay una opción más que queremos mencionar que puede usar para
verificar valores nulos: el operador de afirmación no nulo.
estas aqui 4 233
Machine Translated by Google
NullPointerExceptions intencionales
El !! el operador lanza deliberadamente una NullPointerException
El operador de aserción no nulo, o !!, es diferente a los otros métodos
para tratar con nulos que hemos visto en las últimas páginas. En lugar de
asegurarse de que su código sea seguro al manejar cualquier valor nulo, el
operador de aserción no nulo lanza deliberadamente una NullPointerException
si algo resulta ser nulo.
Supongamos, como antes, que tienes un Lobo? variable llamada w, y desea
asignar el valor de su propiedad hambre a una nueva variable llamada x si w o
hambre no es nulo. Para hacer esto usando una aserción no nula, usaría el
siguiente código:
var x = w!!.hambre Aquí el !! hace la afirmación de que w no es nulo.
Si w y el hambre no son nulos, como se afirma, el valor de la propiedad
del hambre se asigna a x. Pero si w o hambre es nulo, se lanzará una
NullPointerException, se mostrará un mensaje en la ventana de salida del IDE y
la aplicación dejará de ejecutarse.
El mensaje que se muestra en la ventana de resultados le brinda
información sobre NullPointerException, incluido un seguimiento de pila
que le brinda la ubicación de la afirmación no nula que la causó. El
siguiente resultado, por ejemplo, le dice que NullPointerException se
lanzó desde la función principal en la línea 45 en el archivo App.kt: Aquí está NullPointerException, con un
seguimiento de pila que le indica dónde ocurrió.
Excepción en el hilo "principal" kotlin.KotlinNullPointerException
en AppKt.principal(App.kt:45) La excepción ocurrió en la línea 45.
El siguiente resultado, por otro lado, le dice que NullPointerException
se lanzó desde una función llamada myFunction en la clase MyWolf en la
línea 98 del archivo App.kt. Esta función fue llamada desde la función principal
en la línea 67 del mismo archivo:
Excepción en el hilo "principal" kotlin.KotlinNullPointerException
en MyWolf.myFunction(App.kt:98) en
AppKt.main(App.kt:67)
Por lo tanto, las aserciones no nulas son útiles si desea probar suposiciones
sobre su código, ya que le permiten identificar problemas.
Como ha visto, el compilador de Kotlin hace todo lo posible para asegurarse de
que su código se ejecute sin errores, pero todavía hay situaciones en las que
es útil saber cómo generar excepciones y manejar las que surjan.
Veremos las excepciones después de mostrarle el código completo para un
nuevo proyecto que trata con valores nulos.
234 Capítulo 8
Machine Translated by Google
nulos y excepciones
Crear el proyecto de valores nulos
Cree un nuevo proyecto de Kotlin que se dirija a la JVM y asigne al
proyecto el nombre "Valores nulos". Luego cree un nuevo archivo de
Kotlin llamado App.kt resaltando la carpeta src , haciendo clic en el menú
Archivo y eligiendo Nuevo → Archivo/Clase de Kotlin. Cuando se le solicite,
nombre el archivo "Aplicación" y elija Archivo en la opción Tipo.
Agregaremos varias clases y funciones al proyecto, y una función
principal que las usa, para que pueda explorar cómo funcionan los valores
nulos. Aquí está el código: actualice su versión de App.kt para que coincida
Estamos usando una versión
con la nuestra:
reducida de la clase Wolf que
usamos en capítulos anteriores
Crea la clase Lobo. para mantener el código simple.
clase lobo {
Mi lobo Lobo
var hambre = 10
lobo comida para
comida val = "carne"
el hambre
miFunción()
comer()
comer divertido() {
println("El Lobo esta comiendo $comida")
Cree la clase MyWolf.
clase Mi Lobo {
var lobo: Lobo? = Lobo()
Valores nulos
diversión myFunction() {
¿lobo?.comer() origen
}
aplicación.kt
Cree la función getAlphaWolf.
divertido getAlphaWolf() : ¿Lobo? {
devolver lobo()
El código continúa en
la página siguiente.
estas aqui 4 235
Machine Translated by Google
prueba de manejo
El código continuó...
diversión principal(argumentos: Array<String>) {
Mi lobo Lobo
var w: ¿Lobo? = Lobo()
lobo comida para
el hambre
si (w != nulo) {
miFunción()
comer() comer()
var x = w?.hunger println("El
valor de x es $x")
Use el operador Elvis para establecer
var y = w?.hunger ?: 1 println("El valor
y en el valor del hambre si w no es nulo.
de y es $y")
Si w es nulo, establece y en 1.
var myWolf = MyWolf()
myWolf?.wolf?.hunger = 8 println("El
valor de myWolf?.wolf?.hunger es ${myWolf?.wolf?.hunger}")
var myArray = arrayOf("Hola", "Hola", nulo) for (elemento en myArray)
Valores nulos
{
item?.let { println(it) } Esto imprime los elementos
} no nulos en la matriz. origen
getAlphaWolf()?.let { it.eat() }
aplicación.kt
w = nulo
Esto arrojará una NullPointerException ya que w es nulo.
var z = w!!.hambre
}
Prueba de conducción
Cuando ejecutamos el código, el siguiente texto se imprime en la ventana
de salida del IDE:
El lobo está comiendo carne.
el valor de x es 10
El valor de y es 10 El
valor de myWolf?.wolf?.hunger es 8 Hola
Hola
El lobo está comiendo carne
Excepción en el hilo "principal" kotlin.KotlinNullPointerException en
AppKt.main(App.kt:55)
236 Capítulo 8
Machine Translated by Google
nulos y excepciones
Rompecabezas de piscina
Su trabajo es tomar fragmentos de código
de la piscina y colocarlos pato de clase (altura de val: = nulo) {
en las líneas en blanco del código.
graznido divertido() {
No puede usar el mismo fragmento
de código más de una vez y no println("¡Cuac! ¡Cuac!")
necesitará usar todos los fragmentos }
de código. Su objetivo es crear dos
}
clases llamadas
Pato y Mis Patos. MyDucks debe
contener una matriz de patos class MisPatos(var misPatos: Array< >) {
anulables e incluir funciones para
graznido divertido() {
hacer que cada pato grazne y
para (pato en myDucks) {
devolver la altura total de todos los patos.
{
.curandero()
diversión totalDuckHeight(): Int {
var h: =
para (pato en myDucks) {
h altura del pato 0
volver h
Nota: ¡cada cosa del grupo }
solo se puede usar una vez!
pato
. +=
¿En t? En t
0 . él
Pato ? demás
¿En t?
pato
= nulo ?: ?.
?.
¿Pato? hacer
En t
dejar
estas aqui 4 237
Machine Translated by Google
solución de rompecabezas de piscina
Esto es Int?, no Int, ya que
Solución de rompecabezas de piscina debe aceptar un valor nulo.
Su trabajo es tomar fragmentos de código
de la piscina y colocarlos pato de clase (altura de val: ¿En t? = nulo) {
en las líneas en blanco del código.
graznido divertido() {
No puede usar el mismo
fragmento de código más de una vez println("¡Cuac! ¡Cuac!")
y no necesitará usar todos los }
fragmentos de código. Su objetivo es myDucks es una matriz
} de patos anulables.
crear dos clases llamadas
Pato y Mis Patos. MyDucks debe
contener una matriz de patos class MisPatos(var misPatos: Array< Pato? >) {
anulables e incluir funciones para
graznido divertido() {
hacer que cada pato grazne y
devolver la altura total de todos los patos. para (pato en myDucks) {
pato ?. dejar {
Aquí, estamos usando let para hacer que cada pato
él .curandero()
grazne, pero podríamos haber usado pato?.cuack() en su lugar.
}
diversión totalDuckHeight(): Int {
totalDuckHeight() devuelve un Int, por var h: = 0
En t
lo que h debe ser un Int, no un Int?.
para (pato en myDucks) {
Si el pato y su altura no son nulos, suma la altura del pato a h += ?. altura del pato ?: 0
h. De lo contrario, agregue 0 a h en su lugar. }
volver h
No necesitabas usar
estos fragmentos.
.
En t
.
Pato ? demás
¿En t?
pato
= nulo
hacer
238 Capítulo 8
Machine Translated by Google
nulos y excepciones
Se lanza una excepción en circunstancias excepcionales.
Como dijimos anteriormente, una excepción es un tipo de advertencia
sobre situaciones excepcionales que aparecen en tiempo de ejecución. Es
una forma de que el código diga "Algo malo pasó, fallé".
Suponga, por ejemplo, que tiene una función llamada myFunction
que convierte un parámetro String en un Int y lo imprime:
fun myFunction(str: String) {
val x = str.toInt()
imprimir(x)
println("miFuncion ha finalizado")
Si pasa una Cadena como "5" a myFunction, el código convertirá con éxito la
Cadena en un Int e imprimirá el valor 5, junto con el texto "myFunction ha
finalizado". Sin embargo, si le pasa a la función una cadena que no se puede
convertir en un entero, como "Soy un nombre, no un número", el código dejará
de ejecutarse y mostrará un mensaje de excepción como este:
¡Ay!
Excepción en el subproceso "principal" java.lang.NumberFormatException: para la cadena de entrada: "Soy un nombre, no un número"
en java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
en java.lang.Integer.parseInt(Integer.java:580) El seguimiento de la pila
de excepciones menciona
en java.lang.Integer.parseInt(Integer.java:615)
Java porque estamos
en AppKt.myFunction(App.kt:119) en
ejecutando nuestro código en la JVM.
AppKt.main(App.kt:3)
Puede detectar las excepciones que se lanzan
Cuando se lanza una excepción, tiene dos opciones para tratarla:
¥ Puede dejar la excepción en paz.
Esto mostrará un mensaje en la ventana de salida y detendrá su aplicación
(como se indicó anteriormente).
¥ Puede capturar la excepción y manejarla.
Si sabe que puede obtener una excepción cuando ejecuta determinadas líneas de
código, puede prepararse para ello y posiblemente recuperarse de lo que sea que lo
haya causado.
Ha visto lo que sucede cuando deja las excepciones en paz, así que veamos
cómo las detecta.
estas aqui 4 239
Machine Translated by Google
tratar de atrapar
Detectar excepciones usando un intento/captura
Voy a PROBAR esta
Las excepciones se detectan envolviendo el código de riesgo en un bloque cosa arriesgada y me
de prueba/captura . Un bloque try/catch le dice al compilador que usted ATRAPARÉ si fallo.
sabe que podría suceder algo excepcional en el código que desea ejecutar
y que está preparado para manejarlo. Al compilador no le importa cómo lo
manejes; sólo le importa que digas que te estás ocupando de ello.
Así es como se ve un bloque de prueba/captura:
fun myFunction(str: String) {
Aquí está el intento... intentar {
val x = str.toInt()
imprimir(x)
... y aquí } captura (e: NumberFormatException) {
está el truco. println("Lamentable")
println("miFuncion ha finalizado")
La parte try del bloque try/catch contiene el código arriesgado que podría
causar una excepción. En el ejemplo anterior, este es el código:
intentar {
val x = str.toInt()
imprimir(x)
La parte de captura del bloque especifica la excepción que desea capturar e
incluye el código que desea ejecutar si la detecta. Entonces, si nuestro
código arriesgado arroja una NumberFormatException, la atraparemos e
imprimiremos un mensaje significativo como este:
captura (e: NumberFormatException) {
println("Lamentable")
Esta línea solo se ejecutará si se detecta una excepción.
}
Cualquier código que sigue al bloque catch se ejecuta, en este caso el código:
println("miFuncion ha finalizado")
240 Capítulo 8
Machine Translated by Google
nulos y excepciones
Use finalmente para las
cosas que quiere hacer sin importar qué
Si tiene un código de limpieza importante que desea ejecutar
independientemente de una excepción, puede colocarlo en un bloque finalmente .
El bloque finalmente es opcional, pero está garantizado que se ejecutará
pase lo que pase.
Para ver cómo funciona esto, supón que quieres hornear algo
experimental que podría salir mal.
Empiezas por encender el horno.
probar/atrapar/
Si lo que intentas cocinar tiene éxito, tienes que apagar el horno.
finalmente control de flujo
Si lo que intenta es un completo fracaso, debe apagar el horno.
¥ Si el bloque try falla (una
excepción): el control de flujo se
Tienes que apagar el horno pase lo que pase, por lo que el código para mueve inmediatamente al bloque
catch . Cuando se completa el bloque
apagar el horno pertenece a un bloque finalmente:
catch , se ejecuta finalmente el bloque. Cuando
intentar { finaliza el bloque , continúa el resto del código.
encender el horno ()
x.hornear()
} catch (e: Excepción para hornear) {
¥ Si el bloque de prueba tiene éxito (sin
println("Experimento de horneado fallido") excepción): el control de flujo salta el bloque
} finalmente { de captura y se mueve al bloque de finalización .
Siempre queremos llamar
apagar el horno () a turnOvenOff(), por lo que
Cuando finaliza el bloque , continúa el resto del
} pertenece al bloque finalmente. código.
Sin finalmente, tienes que poner la llamada a la función turnOvenOff
¥ Si el bloque try o catch tiene una declaración
tanto en el intento como en la captura porque tienes que apagar el
de retorno, finalmente se ejecutará: el flujo
horno pase lo que pase. Un bloque finalmente te permite poner todo tu salta al bloque finalmente y luego vuelve al
código de limpieza importante en un solo lugar, en lugar de duplicarlo así:
retorno.
intentar {
encender el horno ()
x.hornear()
apagar el horno ()
} catch (e: Excepción para hornear) {
println("Experimento de horneado fallido")
apagar el horno ()
estas aqui 4 241
Machine Translated by Google
jerarquía de excepción
Una excepción es un objeto de tipo Excepción
¡Soy
Cada excepción es un objeto de tipo Excepción. Es la superclase
de todas las excepciones, por lo que todo tipo de excepción hereda de excepcional!
ella. En la JVM, por ejemplo, cada excepción tiene una función llamada
printStackTrace que puede usar para imprimir el seguimiento de la pila
de la excepción usando un código como este:
intentar { printStackTrace() es una función que está disponible
// Haz algo arriesgado para todas las excepciones que se ejecutan en la JVM.
Excepción
Si no puede recuperarse de una excepción, use
} catch (e: Excepción) {
printStackTrace() para ayudarlo a localizar la causa del problema.
e.printStackTrace()
//Otro código que se ejecuta cuando obtiene una excepción
}
Throwable es la superclase de Exception.
Hay muchos tipos diferentes de excepción, cada uno de los cuales es un
subtipo de excepción. Algunos de los más comunes (o famosos) son:
arrojable
causa
mensaje
¥ NullPointerException Se
lanza cuando intenta realizar operaciones en un valor nulo. Como ha
visto, las NullPointerExceptions están casi extintas en Kotlinville.
¥ ClassCastException Excepción
Obtendrá esto si intenta convertir un objeto en un tipo incorrecto, como
convertir un lobo en un árbol.
¥ IllegalArgumentException Puede
lanzar esto si se ha pasado un argumento ilegal.
¥ IllegalStateException Use Otra excepción
esto si algún objeto tiene un estado que no es válido.
También puede crear sus propios tipos de excepción definiendo una
nueva clase con Exception como su superclase. El siguiente código,
por ejemplo, define un nuevo tipo de excepción llamado Cada excepción es una
AnimalException: subclase de Excepción,
clase AnimalException : Excepción() { } incluidas todas las
mencionadas en esta página.
Definir sus propios tipos de excepción a veces puede ser útil si desea
generar excepciones deliberadamente en su propio código.
Veremos cómo se hace esto después de una pequeña desviación.
242 Capítulo 8
Machine Translated by Google
nulos y excepciones
Lanzamientos seguros de cerca
Como aprendió en el Capítulo 6, en la mayoría de las circunstancias, el compilador
realizará una conversión inteligente cada vez que use el operador is. En el siguiente
código, por ejemplo, el compilador verifica si la variable r contiene un objeto Wolf, por lo que
(interfaz)
puede convertir inteligentemente la variable de Roamable a Wolf: itinerante
val r: Roamable = Wolf()
si (r es lobo) {
comer() Aquí, r ha sido moldeado inteligentemente a un Lobo.
En algunas situaciones, el compilador no puede realizar una conversión inteligente, ya que
Animal
la variable puede cambiar entre la comprobación de su tipo y su uso. El siguiente código,
por ejemplo, no se compilará porque el compilador no puede estar seguro de que la hambre
propiedad r siga siendo Wolf después de verificarla:
comer()
clase MiRoamable {
var r: Roamable = Wolf()
Lobo
diversión myFunction() {
si (r es lobo) {
comer() Esto no compilará, porque el compilador comer()
} no puede garantizar que r todavía contenga una
} referencia a un objeto Wolf.
Viste en el Capítulo 6 que puedes lidiar con esto usando la palabra clave as para convertir
explícitamente a r como un lobo de esta manera:
Esto se compilará, pero si r ya no contiene
si (r es lobo) {
una referencia a un objeto Wolf, obtendrá
val lobo = r como lobo
una excepción en tiempo de ejecución.
lobo.comer()
}
Pero si a r se le asigna un valor de algún otro tipo entre la verificación de tipo y la
¿como? le
conversión, el sistema generará una ClassCastException.
permite realizar un
La alternativa segura es realizar un lanzamiento seguro usando el as? operador usando
un código como este: lanzamiento explícito
val lobo = r como? Lobo seguro. Si la
Esto proyecta r como un lobo si r tiene un objeto de ese tipo y devuelve nulo si no lo tiene. conversión falla, devuelve nulo.
Esto le evita obtener una ClassCastException si sus suposiciones sobre el tipo de variable
son incorrectas.
243
estas aqui 4
Machine Translated by Google
como lanzar excepciones
Puede lanzar excepciones explícitamente
A veces puede ser útil lanzar deliberadamente excepciones en su
propio código. Si tiene una función llamada setWorkRatePercentage,
por ejemplo, es posible que desee lanzar una excepción
IllegalArgumentException si alguien intenta establecer un porcentaje que sea
menor que 0 o mayor que 100. Hacerlo obliga a la persona que llama a abordar
el problema, en lugar de confiar en la función. para decidir qué hacer.
Lanzas una excepción usando la palabra clave throw . Así es como, por
ejemplo, obtendría la función setWorkRatePercentage para lanzar una
IllegalArgumentException:
diversión setWorkRatePercentage(x: Int) { Esto lanza una IllegalArgumentException si x no
está en el rango 0..100
si (x! en 0..100) {
throw IllegalArgumentException("El porcentaje no está en el rango 0..100: $x")
}
//Más código que se ejecuta si el argumento es válido
}
Luego podría capturar la excepción usando un código como este:
intentar {
setWorkRatePercentage(110) La función setWorkRatePercentage() no puede
hacer que nadie trabaje al 110%, por lo que la
} catch(e: excepción de argumento ilegal) {
persona que llama tiene que lidiar con el problema.
//Código para manejar la excepción
}
Reglas de excepción
¥ No puedes tener una captura o finalmente sin ¥ Un intento debe ser seguido por una captura o un
intentarlo. No es legal ya finalmente.
que no hay intento. Legal porque hay un prueba { callRiskyCode() }
callRiskyCode()
¥ No se puede poner código entre el intento y la captura,
¥ Un intento puede tener varios bloques catch .
o la captura y el finalmente.
244 Capítulo 8
Machine Translated by Google
nulos y excepciones
probar y tirar son ambas expresiones
A diferencia de otros lenguajes como Java, try y throw son expresiones, por lo
que pueden tener valores de retorno.
Cómo usar try como una expresión
El valor de retorno de un intento es la última expresión en el intento o la última
expresión en la captura (el bloque finalmente, si existe, no afecta el valor de
Esto es como decir "Intente
retorno). Considere el siguiente código, por ejemplo:
asignar str.toInt() al resultado,
val result = try { str.toInt() } catch (e: Exception) { null }
pero si no puede, establezca
el resultado en nulo".
El código crea una variable denominada resultado de tipo Int?. El bloque try
intenta convertir el valor de una variable String llamada str en un Int. Si esto
tiene éxito, asigna el valor Int al resultado. Sin embargo, si el bloque de prueba
falla, asigna nulo al resultado en su lugar:
Cómo usar throw como una expresión
throw también es una expresión, por lo que puede, por ejemplo, usarla con el
Operador de Elvis usando un código como este:
val h = w?. ¿hambre?: throw AnimalException()
Si w y el hambre no son nulos, el código anterior asigna el valor de la
propiedad hambre de w a una nueva variable llamada h. Sin embargo, si w o
el hambre son nulos, lanza una AnimalException.
tiene valores, entonces, ¿una variable de alcanzó. Puede, digamos, usarlo como el tipo de
tipo Nothing? solo puede contener un valor nulo . retorno de una función que nunca regresa:
El siguiente código, por ejemplo, crea
una variable denominada x de tipo fun fail(): Nada {
Nothing? eso solo puede ser nulo: lanzar BadException()
}
var x = nulo
El compilador sabe que el código detiene
la ejecución después de llamar a fail() .
estas aqui 4 245
Machine Translated by Google
afilar
Mira el código de la izquierda. ¿Cuál crees que será la salida cuando se
ejecute? ¿Qué crees que sería si el código de la línea 2 se cambiara por el
siguiente?:
prueba val: Cadena = "Sí"
Escribe tus respuestas en los recuadros de la derecha.
diversión principal(argumentos: Array<String>) {
Salida cuando prueba = "No"
prueba val: Cadena = "No"
intentar {
println("Iniciar prueba")
código de riesgo (prueba)
println("Terminar intento")
} captura (e: BadException) {
println("Excepción incorrecta")
} finalmente {
println("Finalmente")
}
println("Fin de principal")
Salida cuando prueba = "Sí"
}
clase BadException: excepción ()
código de riesgo divertido (prueba: cadena) {
println("Iniciar código arriesgado")
si (prueba == "Sí") {
lanzar BadException()
}
println("Finalizar código riesgoso")
}
Respuestas en la página 248.
246 Capítulo 8
Machine Translated by Google
nulos y excepciones
Imanes de código
Algo de código Kotlin está revuelto en el refrigerador. Vea si
puede reconstruir el código para que si a myFunction se le pasa
una cadena de "Sí", imprima el texto "descongela", y si a myFunction
se le pasa una cadena de "No", imprima el texto "throws".
Los imanes deben ir en este espacio.
código de riesgo divertido (prueba: cadena) {
imprimir("h") } finalmente {
clase BadException: excepción ()
fun myFunction(prueba: Cadena) {
si (prueba == "Sí") {
lanzar BadException()
imprimir("w") código de riesgo (prueba)
imprimir("t")
intentar {
}
imprimir("a")
huellas dactilares")
imprimir("o")
imprimir("r")
}
} captura (e: BadException) {
}
Respuestas en la página 249.
estas aqui 4 247
Machine Translated by Google
afilar solución
Mira el código de la izquierda. ¿Cuál crees que será la salida cuando se
ejecute? ¿Qué crees que sería si el código de la línea 2 se cambiara por el
siguiente?:
prueba val: Cadena = "Sí"
Escribe tus respuestas en los recuadros de la derecha.
diversión principal(argumentos: Array<String>) {
Salida cuando prueba = "No"
prueba val: Cadena = "No"
Empezar a probar
intentar {
println("Iniciar prueba") Iniciar código arriesgado
código de riesgo (prueba)
Terminar con el código riesgoso
println("Terminar intento")
} captura (e: BadException) { Finalizar intento
println("Excepción incorrecta")
Finalmente
} finalmente {
println("Finalmente")
fin de principal
println("Fin de principal")
Salida cuando prueba = "Sí"
}
Empezar a probar
clase BadException: excepción ()
Iniciar código arriesgado
código de riesgo divertido (prueba: cadena) {
mala excepción
println("Iniciar código arriesgado")
Finalmente
si (prueba == "Sí") {
fin de principal
lanzar BadException()
}
println("Finalizar código riesgoso")
}
248 Capítulo 8
Machine Translated by Google
nulos y excepciones
Solución de imanes de código
Algo de código Kotlin está revuelto en el refrigerador. Vea si
puede reconstruir el código para que si a myFunction se le pasa
una cadena de "Sí", imprima el texto "descongela", y si a myFunction
se le pasa una cadena de "No", imprima el texto "throws".
Cree una subclase de Excepción.
clase BadException: excepción ()
fun myFunction(prueba: Cadena) { Crear miFunción.
intentar {
imprimir("t")
Intenta ejecutar este código.
código de riesgo (prueba)
imprimir("o")
} captura (e: BadException) {
imprimir("a")
Ejecute este código si se lanza una BadException.
} finalmente {
imprimir("w") Este código se ejecuta pase lo que pase.
huellas dactilares")
Crear código de riesgo
código de riesgo divertido (prueba: cadena) {
imprimir("h")
si (prueba == "Sí") {
lanzar BadException() Lanzar una BadException si la prueba == "Sí"
imprimir("r")
estas aqui 4 249
Machine Translated by Google
caja de herramientas
Tu caja de herramientas de Kotlin
Puede descargar
Tiene el Capítulo 8 en su haber y ahora ha agregado
el código completo
valores nulos y excepciones a su caja de
del capítulo desde
herramientas.
CAPÍTULO
8 https://tinyurl.com/
HFKotlin.
null es un valor que significa que una variable no El operador Elvis (?:) es una alternativa
contiene una referencia a un objeto. segura a una expresión if.
La variable existe, pero no hace referencia a nada.
El operador de aserción no nulo (!!) lanza una
NullPointerException si el asunto de su aserción es nulo.
Un tipo que acepta valores NULL puede contener
valores NULL además de su tipo base. Usted define un
Una excepción es una advertencia que se produce
tipo como anulable agregando un ? hasta el final de la misma.
en situaciones excepcionales. Es un objeto de tipo
Para acceder a las propiedades y funciones de una Excepción.
variable anulable, primero debe verificar que no sea
Usa throw para lanzar una excepción.
nula.
Captura una excepción usando try/catch/finally.
Si el compilador no puede garantizar que una
variable no sea nula entre una comprobación nula
y su uso, debe acceder a las propiedades y try and throw son expresiones.
funciones mediante el operador de llamada segura (?.).
Use un lanzamiento seguro (¿como?) para evitar
obtener una ClassCastException.
Puede encadenar llamadas seguras juntas.
Para ejecutar código si (y solo si) un valor no es
nulo, use ?.let.
250 Capítulo 8
Machine Translated by Google
9 colecciones
Conseguir
Organizado
Oh, si tan solo hubiera
una manera de agregar
nuevos novios a mi colección...
¿Alguna vez quiso algo más flexible que una matriz?
Kotlin viene con un montón de colecciones útiles que le brindan más flexibilidad y un mayor
control sobre cómo almacena y administra grupos de objetos. ¿ Quiere mantener una lista
redimensionable a la que pueda seguir agregando? ¿Quiere ordenar, barajar o invertir su
contenido? ¿ Quieres encontrar algo por nombre? ¿O quieres algo que elimine automáticamente
los duplicados sin que muevas un dedo? Si quieres alguna de estas cosas, o más, sigue
leyendo. esta todo aqui...
este es un nuevo capitulo 251
Machine Translated by Google
más sobre arreglos
Las matrices pueden ser útiles...
Hasta ahora, cada vez que queríamos tener referencias a
un grupo de objetos en un solo lugar, usamos una matriz. Las
matrices se crean rápidamente y tienen muchas funciones útiles. 1 3 2
Estas son algunas de las cosas que puede hacer con una matriz
(según el tipo de sus elementos):
ÁRBITRO ÁRBITRO ÁRBITRO
¥ Haz una matriz:
var matriz = matrizDe(1, 3, 2)
¥ Haz una matriz inicializada con valores nulos: Crea una matriz de tamaño 2 inicializada
con valores nulos. Es como decir:
var nullArray: Array<String?> = arrayOfNulls(2) arrayOf(null, null)
¥ Averigüe el tamaño de la matriz:
2 3 1
matriz tiene espacio para tres
val tamaño = matriz.tamaño elementos, por lo que su tamaño es 3.
Cambia el orden de los elementos de la matriz.
array.reverse()
¥ Averigua si contiene algo:
val isIn = array.contains(1) matriz contiene 1, por lo que devuelve verdadero.
¥ Calcula la suma de sus elementos (si son numéricos):
Esto devuelve 6 como 2 + 3 + 1 = 6.
val suma = array.sum()
¥ Calcule el promedio de sus artículos (si son numéricos):
val promedio = matriz.promedio() Esto devuelve un Doble, en este caso, (2 + 3 + 1)/3 = 2,0.
¥ Averigüe el artículo mínimo o máximo (funciona para números,
cadenas, caracteres y booleanos):
min() devuelve 1, ya que este es el valor más bajo de la
matriz.min()
matriz. max() devuelve 3 ya que este es el más alto.
matriz.max()
1 2 3
¥ Ordene la matriz en un orden natural (funciona para números,
cadenas, caracteres y booleanos):
ÁRBITRO ÁRBITRO ÁRBITRO
Cambia el orden de los elementos de la matriz
array.ordenar()
para que vayan del valor más bajo al más alto, de
o falso a verdadero.
Pero las matrices no son perfectas.
252 Capítulo 9
Machine Translated by Google
colecciones
...pero hay cosas que una matriz no puede manejar
Aunque una matriz le permite realizar muchas acciones útiles, hay dos
áreas importantes en las que las matrices se quedan cortas.
No puedes cambiar el tamaño de una matriz
Cuando crea una matriz, el compilador deduce su tamaño a partir de la
cantidad de elementos con los que se inicializa. Su tamaño se fija
entonces para siempre. La matriz no crecerá si desea agregarle un nuevo
elemento, y no se reducirá si desea eliminar un elemento.
Las matrices son mutables, por lo que se pueden actualizar
Otra limitación es que una vez que crea una matriz, no puede evitar que
se modifique. Si crea una matriz usando un código como este:
val miArray = arrayOf(1, 2, 3)
no hay nada que impida que la matriz se actualice de esta manera:
miArray[0] = 6
Si su código se basa en que la matriz no cambia, esto puede ser una
fuente de errores en su aplicación.
Entonces, ¿cuál es la alternativa?
P: ¿No puedo eliminar un elemento de una matriz configurándolo en P: ¿No podría crear una copia de la matriz que tenga un diferente
¿tamaño?
¿nulo?
R: Si crea una matriz que contiene tipos anulables, puede establecer R: Podría, y las matrices incluso tienen una función llamada más
uno o más de sus elementos a nulo usando un código como este: eso lo hace más fácil; plus copia la matriz y agrega un nuevo elemento
al final de la misma. Pero esto no cambia el tamaño de la matriz original.
val a: Array<Int?> = arrayOf(1, 2, 3) a[2] = null
P: ¿ Es eso un problema?
Sin embargo, esto no cambia el tamaño de la matriz. En el ejemplo
R: Sí. Necesitarás escribir código extra, y si otras variables
anterior, el tamaño de la matriz sigue siendo 3 aunque uno de sus elementos
mantenga referencias a la versión anterior de la matriz, esto podría conducir a un
se haya establecido en nulo.
código con errores.
Sin embargo, existen buenas alternativas al uso de una matriz, que veremos
a continuación.
estas aqui 4 253
Machine Translated by Google
biblioteca estándar de kotlin
En caso de duda, acude a la Biblioteca
Biblioteca estándar
Kotlin se envía con cientos de clases y funciones prediseñadas que puede
usar en su código. Ya conoces algunos de estos, como String y Any. Y la Puedes ver lo que hay en el Kotlin
gran noticia para nosotros es que la biblioteca estándar de Kotlin incluye navegando
Biblioteca estándar
clases que brindan excelentes alternativas a las matrices. a:
En la biblioteca estándar de Kotlin, las clases y funciones se agrupan en
paquetes. Cada clase pertenece a un paquete y cada paquete tiene un https://kotlinlang.org/api/latest/jvm/stdlib/index.html
nombre. El paquete kotlin , por ejemplo, contiene funciones y tipos
básicos, y el paquete kotlin.math contiene funciones y constantes matemáticas.
El paquete que nos interesa aquí es el paquete kotlin.collections . Este
paquete incluye varias clases que le permiten agrupar objetos en una
colección. Veamos los principales tipos de colección.
Puede usar
estos filtros
para mostrar solo
aquellas colecciones
que son relevantes
para una plataforma
en particular o una
versión de Kotlin.
Aquí está el kotlin.
paquete de colecciones
en la biblioteca
estándar de Kotlin.
254 Capítulo 9
Machine Translated by Google
colecciones
Lista, conjunto y mapa
Kotlin tiene tres tipos principales de colección: Lista, Conjunto y Mapa,
ÁRBITRO "Té"
y cada uno tiene su propio propósito:
0
Cadena
Lista cuando la secuencia importa ÁRBITRO
1
Una lista conoce y se preocupa por la posición del índice. Sabe dónde "Café"
está algo en la Lista y puede tener más de un elemento que haga
ÁRBITRO
Una lista permite
referencia al mismo objeto. 2 Cadena
valores duplicados.
Lista
"Jim"
Conjunto: cuando la singularidad importa
Un conjunto no permite
Un conjunto no permite duplicados y no se preocupa por el orden en Cadena
ÁRBITRO
valores duplicados.
que se mantienen los valores. Nunca puede tener más de un elemento
ÁRBITRO
que haga referencia al mismo objeto, o más de un elemento que haga "Demandar"
referencia a dos objetos que se consideran iguales.
Cadena
Colocar
por asuntos clave
Un mapa utiliza pares clave/valor. Conoce el valor
asociado a una clave dada. Puede tener dos claves Un mapa permite
ÁRBITRO ÁRBITRO ÁRBITRO
que hagan referencia al mismo objeto, pero no puede valores
tener claves duplicadas. Aunque las claves suelen ser duplicados, pero
nombres de cadena (por lo que puede crear listas de no claves duplicadas.
propiedades de nombre/valor, por ejemplo), una clave
“Clave A” “Tecla B” “Clave C”
puede ser cualquier objeto.
Mapa
Las listas simples, los conjuntos y los mapas son inmutables, lo que significa que no
puede agregar ni eliminar elementos después de que se haya inicializado la colección.
Si desea poder agregar o eliminar elementos, Kotlin tiene subtipos mutables que
puede usar en su lugar: MutableList, MutableSet y MutableMap. Entonces, si desea
todos los beneficios de usar una Lista y desea poder actualizar su contenido, use una
MutableList.
Ahora que ha visto los tres tipos principales de colección que Kotlin tiene para ofrecer,
veamos cómo usa cada uno, comenzando con una Lista.
255
estas aqui 4
Machine Translated by Google
Liza
Fantásticas listas...
El código crea una lista
Usted crea una Lista de una manera similar a cómo crea una matriz: llamando a una que contiene valores de
función llamada listOf, pasando los valores con los que desea que se inicialice la cadena de "Té", "Huevos" y "Leche".
Lista. El siguiente código, por ejemplo, crea una lista, la inicializa con tres cadenas
y la asigna a una nueva variable denominada compras:
ÁRBITRO "Té"
val compras = listOf("Té", "Huevos", "Leche") 0
El compilador infiere el tipo de objeto que debe contener cada Lista al Cadena
ÁRBITRO
observar el tipo de cada valor que se le pasa cuando se crea. La lista ÁRBITRO
"Huevos"
anterior, por ejemplo, se inicializa con tres cadenas, por lo que el compilador 1
crea una lista de tipo List<String>. También puede definir explícitamente el compras
tipo de Lista usando un código como este: Cadena
val Lista<Cadena>
"Leche"
ÁRBITRO
2
val compras: Lista<String>
La variable
compras = listOf("Té", "Huevos", "Leche") Cadena
tiene un tipo
de List<String>,
...y cómo usarlos por lo que List
contiene referencias
Una vez que haya creado una lista, puede acceder a los elementos que
a objetos String.
contiene utilizando la función de obtención . El siguiente código, por
ejemplo, verifica que el tamaño de la Lista sea mayor que 0, luego imprime
el elemento en el índice 0:
Es una buena idea verificar primero
if (compras.tamaño > 0) { el tamaño de la Lista porque get()
println(compras.obtener(0))
generará una
ArrayIndexOutOfBoundsException si pasa un índice no válido.
// Imprime "Té"
Puede recorrer todos los elementos en una Lista de esta manera:
para (artículo en compras) println (artículo)
Las listas y otras
Y también puede verificar si la Lista contiene una referencia a un objeto en particular y
colecciones
recuperar su índice:
pueden contener
if (compras.contiene("Leche")) {
println(compras.indexOf("Leche")) referencias a cualquier
//Imprime 2
}
tipo de objeto: cadenas,
enteros, patos, pizzas, etc.
Como puede ver, usar una lista es muy parecido a usar una matriz. Sin embargo,
la gran diferencia es que una Lista es inmutable: no puede actualizar ninguna de las
referencias que almacena.
256 Capítulo 9
Machine Translated by Google
colecciones
Crear una Lista Mutable...
Si desea una Lista cuyos valores pueda actualizar, debe usar una
MutableList. Usted define una MutableList de manera similar a como
define una Lista, excepto que esta vez, usa la función mutableListOf en
su lugar:
ÁRBITRO "Té"
val mShopping = mutableListOf("Té", "Huevos")
0
ÁRBITRO
MutableList es un subtipo de Lista, por lo que puede llamar Cadena
a las mismas funciones en una MutableList que en una Lista.
Sin embargo, la gran diferencia es que MutableLists tiene mCompras ÁRBITRO
"Huevos"
1
funciones adicionales que puede usar para agregar o
eliminar valores, o actualizar o reorganizar los existentes. valor
Cadena
ListaMutable<String>
..y agregarle valores Si pasa valores de cadena a la
función mutableListOf(), el compilador
Agrega nuevos valores a una MutableList usando la función de agregar . infiere que desea un objeto de tipo
Si desea agregar un nuevo valor al final de MutableList, pase el valor a la MutableList<String> (una MutableList que
función de agregar como un solo parámetro. contiene cadenas).
El siguiente código, por ejemplo, agrega "Milk" al final de mShopping:
mShopping.add("Leche")
Esto aumenta el tamaño de MutableList para que ahora tenga tres valores en
lugar de dos.
Si desea insertar un valor en un índice específico, puede hacerlo
pasando el valor del índice a la función de suma además del valor. Si
ÁRBITRO "Té"
quisiera insertar un valor de "Milk" en el índice 1 en lugar de agregarlo
0
al final de MutableList, podría hacerlo usando el siguiente código:
Cadena
ÁRBITRO
mShopping.add(1, "Leche") ÁRBITRO
"Leche"
1
Insertar un valor en un índice específico de esta mCompras
manera obliga a otros valores a moverse para hacerle Cadena
espacio. En este ejemplo, el valor "Huevos" se mueve valor ÁRBITRO
del índice 1 al índice 2 para que "Leche" se pueda ListaMutable<String>
"Huevos"
insertar en el índice 1.
2
Si agrega "Leche" al Cadena
Además de agregar valores a MutableList, también
puede eliminarlos o reemplazarlos. Veamos cómo. índice 1, "Huevos" se
mueve al índice 2 para
dar paso al nuevo valor.
estas aqui 4
257
Machine Translated by Google
modificando una MutableList
Puede eliminar un valor...
Hay dos formas de eliminar un valor de MutableList.
} 0
ÁRBITRO
Cadena
Cualquiera que sea el enfoque que utilice, eliminar un valor
de MutableList hace que se reduzca.
mCompras ÁRBITRO
"Huevos"
1
...y reemplazar un valor por otro valor
Cadena
ListaMutable<String>
Si desea actualizar MutableList para que el valor en un
índice particular se reemplace con otro, puede hacerlo Como se eliminó
usando la función set . El siguiente código, por ejemplo, "Leche", "Huevos"
reemplaza el valor "Té" en el índice 0 con "Café":
pasa del índice 2 al índice 1.
if (mCompras.tamaño > 0) { "Té"
mCompras.set(0, "Café")
}
Cadena
La función set() establece
la referencia contenida
ÁRBITRO "Café"
en un índice particular a
0
ÁRBITRO la de un objeto diferente.
Cadena
mCompras ÁRBITRO
"Huevos"
1
valor
Cadena
ListaMutable<String>
258 Capítulo 9
Machine Translated by Google
colecciones
Puedes cambiar el orden y hacer cambios masivos...
MutableList también incluye funciones para cambiar el orden en que se guardan los
elementos. Puede, digamos, ordenar MutableList en un orden natural usando la
función de ordenación , o revertirlo usando reversa:
mShopping.sort() Juntas, estas líneas ordenan
mCompras.reverse() MutableList en orden inverso.
P: ¿ Qué es un paquete?
O puede usar la función de reproducción aleatoria para aleatorizarlo:
mCompras.shuffle() R: Un paquete es una agrupación de clases.
y funciones Son útiles por un par de
Y también hay funciones útiles para realizar cambios masivos en MutableList. razones.
Puede, por ejemplo, usar la función addAll para agregar todos los elementos que se
encuentran en otra colección. El siguiente código, por ejemplo, agrega "Cookies" y Primero, ayudan a organizar un proyecto o
"Sugar" a mShopping: una biblioteca. En lugar de tener solo una
gran cantidad de clases, todas están agrupadas
val toAdd = listOf("Galletas", "Azúcar") en paquetes para tipos específicos de
mShopping.addAll(toAdd) funcionalidad.
La función removeAll elimina elementos que se encuentran en otra colección:
En segundo lugar, le brindan un alcance
de nombre, lo que significa que varias
personas pueden escribir clases con el mismo
val toRemove = listOf("Leche", "Azúcar")
nombre, siempre que estén en diferentes paquetes.
mShopping.removeAll(toRemove)
Encontrará más información sobre cómo estructurar
Y la función preserveAll conserva todos los elementos que se encuentran en otra
su código en paquetes en el Apéndice III.
colección y elimina todo lo demás:
val toRetain = listOf("Leche", "Azúcar") P: En Java tengo que importar cualquier
paquetes que quiero usar, incluidas
mShopping.retainAll(toRetain)
las colecciones. ¿Yo en Kotlin?
También puede usar la función borrar para eliminar todos los elementos como este:
R: Kotlin importa automáticamente muchos
mCompras.clear() Esto vacía mShopping por lo que su tamaño es 0.
paquetes de la biblioteca estándar de
Kotlin, incluido kotlin.collections. Sin embargo,
...o tomar una copia de MutableList completa todavía hay situaciones en las que necesita
importar paquetes explícitamente y puede
A veces, puede ser útil copiar una Lista, o MutableList, para que pueda guardar una obtener más información en el Apéndice III.
instantánea de su estado. Puede hacer esto usando la función toList . El siguiente
código, por ejemplo, copia mShopping y asigna la copia a una nueva variable
denominada shoppingSnapshot:
val shoppingCopy = mShopping.toList()
La función toList devuelve una Lista, no una MutableList, por lo que shoppingCopy MutableList también tiene una función
no se puede actualizar. Otras funciones útiles que puede usar para copiar MutableList toMutableList() que devuelve una copia que es
incluyen sorted (que devuelve una Lista ordenada), invertida (que devuelve una Lista una MutableList nueva.
con los valores en orden inverso) y barajada (que devuelve una Lista y mezcla sus
valores).
estas aqui 4 259
Machine Translated by Google
crear proyecto
Crear el proyecto Colecciones
Ahora que aprendió sobre Listas y MutableLists, creemos un proyecto que las use.
Cree un nuevo proyecto de Kotlin que se dirija a la JVM y asígnele el nombre
"Colecciones". Luego cree un nuevo archivo de Kotlin llamado Collections.kt resaltando la
carpeta src , haciendo clic en el menú Archivo y eligiendo Nuevo → Archivo/clase de Kotlin.
Cuando se le solicite, asigne al archivo el nombre "Colecciones" y elija Archivo en la opción Tipo.
A continuación, agregue el siguiente código a Collections.kt:
diversión principal(argumentos: Array<String>) {
val mListaDeCompras = mutableListOf("Té", "Huevos", "Leche")
println("mShoppingList original: $mShoppingList")
val extraShopping = listOf("Galletas", "Azúcar", "Huevos")
mShoppingList.addAll(compras adicionales)
println("artículos mShoppingList agregados: $mShoppingList")
if (mListaDeCompras.contains("Té")) {
mShoppingList.set(mShoppingList.indexOf("Té"), "Café")
}
mListaDeCompras.sort()
println("mListaCompras ordenada: $mListaCompras") Colecciones
mListaDeCompras.reverse()
println("mListaDeCompras invertida: $mListaDeCompras") origen
Colecciones.kt
Prueba de conducción
Cuando ejecutamos el código, el siguiente texto se imprime en la ventana de salida del
IDE:
La impresión de una
mShoppingList original: [Té, Huevos, Leche] Lista o MutableList imprime
mShoppingList elementos agregados: [Té, Huevos, Leche, Galletas, Azúcar, cada elemento en orden de
Huevos] mShoppingList ordenada: [Café, Galletas, Huevos, Huevos, Leche, Azúcar] índice dentro de corchetes.
mShoppingList invertida: [Azúcar, Leche , Huevos, Huevos, Galletas, Café]
A continuación, inténtalo con el siguiente ejercicio.
260 Capítulo 9
Machine Translated by Google
colecciones
La función necesita producir esta salida.
Imanes de código
Alguien usó imanes de nevera para crear una [Cero, Dos, Cuatro, Seis]
función principal funcional que produzca el [Dos, Cuatro, Seis, Ocho]
resultado que se muestra a la derecha.
[Dos, Cuatro, Seis, Ocho, Diez]
Desafortunadamente, un extraño sharknado ha desalojado los imanes.
Vea si puede reconstruir la función. [Dos, Cuatro, Seis, Ocho, Diez]
Su código debe ir aquí.
a.add(2, "Cuatro") a.add(0, "Cero")
var a: MutableList<String> = mutableListOf()
a.add(1, "Dos")
imprimir(a)
diversión principal(argumentos: Array<String>) {
imprimir(a) if (a.indexOf("Cuatro") != 4) a.add("Diez")
a.add(3, "Seis")
imprimir(a) if (a.contains("Cero")) a.add("Ocho")
imprimir(a)
} a.removeAt(0) if (a.contains("Zero")) a.add("Twelve")
estas aqui 4 261
Machine Translated by Google
solución de imanes
Solución de imanes de código
Alguien usó imanes de nevera para crear una [Cero, Dos, Cuatro, Seis]
función principal funcional que produzca el [Dos, Cuatro, Seis, Ocho]
resultado que se muestra a la derecha.
[Dos, Cuatro, Seis, Ocho, Diez]
Desafortunadamente, un extraño sharknado ha desalojado los imanes.
Vea si puede reconstruir la función. [Dos, Cuatro, Seis, Ocho, Diez]
diversión principal(argumentos: Array<String>) {
var a: MutableList<String> = mutableListOf()
a.add(0, "Cero")
a.add(1, "Dos")
a.add(2, "Cuatro")
a.add(3, "Seis")
imprimir(a)
if (a.contains("Cero")) a.add("Ocho")
a.removeAt(0)
imprimir(a)
if (a.indexOf("Cuatro") != 4) a.add("Diez")
imprimir(a)
if (a.contains("Zero")) a.add("Twelve")
imprimir(a)
262 Capítulo 9
Machine Translated by Google
colecciones
Las listas permiten valores duplicados
Como ya aprendió, el uso de una Lista o MutableList le brinda más
flexibilidad que el uso de una matriz. A diferencia de una matriz, puede
elegir explícitamente si la colección debe ser inmutable o si su código
puede agregar, eliminar y actualizar sus valores.
Sin embargo, hay algunas situaciones en las que el uso de una Lista
(o MutableList) no funciona del todo.
Imagina que estás organizando una comida con un grupo de amigos y
necesitas saber cuántas personas van a ir para poder reservar una
mesa. Podría usar una Lista para esto, pero hay un problema: una Lista
puede contener valores duplicados. Es posible, por ejemplo, crear una
Lista de amigos donde algunos de los amigos se enumeran dos veces:
ÁRBITRO
"Jim"
0
val listaAmigos = listaDe("Jim", Cadena
Aquí hay tres amigos "Demandar",
ÁRBITRO
ÁRBITRO
llamados Jim, Sue y Nick, "Demandar",
1
pero Sue y Nick aparecen "Mella", amigo "Demandar"
dos veces en la lista. Lista
"Mella")
ÁRBITRO
val Lista<Cadena> Cadena
Pero si quiere saber cuántos amigos distintos hay en la Lista, 2
no puede simplemente usar el código:
listaamigos.tamaño ÁRBITRO
3 "Mella"
para saber para cuántas personas debes reservar una mesa. La
propiedad de tamaño solo ve que hay cinco elementos en la Lista y no
le importa que dos de estos elementos estén duplicados. ÁRBITRO Cadena
En este tipo de situación, necesitamos usar una colección que no 4
permita que se mantengan valores duplicados. Entonces, ¿qué tipo La Lista tiene un
de colección debemos usar? tamaño de 5, pero
solo 3 valores distintos.
Anteriormente en el capítulo, discutimos los diferentes tipos de colección
que están disponibles en Kotlin. ¿Qué tipo de colección crees que sería la más
apropiada para esta situación?
estas aqui 4 263
Machine Translated by Google
Conjuntos
Cómo crear un conjunto
Si necesita una colección que no permita duplicados, puede usar un Conjunto: una
colección desordenada sin valores duplicados.
Usted crea un Conjunto llamando a una función llamada setOf, pasando los
valores con los que desea que se inicialice el Conjunto. El siguiente código, por El código crea un
ejemplo, crea un conjunto, lo inicializa con tres cadenas y lo asigna a una nueva
Conjunto que contiene
variable llamada friendSet:
los tres valores de Cadena.
val friendSet = setOf("Jim", "Sue", "Nick")
"Jim"
Un conjunto no puede contener valores duplicados, por lo que si intenta definir uno
usando un código como este:
ÁRBITRO
Cadena
ÁRBITRO ÁRBITRO
val friendSet = setOf("Jim", "Demandar"
"Demandar", amigo
Colocar
"Demandar",
ÁRBITRO
Cadena
"Mella", val Set<Cadena>
"Mella") "Mella"
el conjunto ignora los valores duplicados de "Sue" y "Nick". El código crea un
Los valores de un conjunto Cadena
Conjunto que contiene tres Cadenas distintas como antes.
no tienen orden y no se
El compilador infiere el tipo del Conjunto al observar los valores que se le pasan permiten valores duplicados.
cuando se crea. El código anterior, por ejemplo, inicializa un conjunto con valores
de cadena, por lo que el compilador crea un conjunto de tipo Set<String>.
Cómo usar los valores de un conjunto
Los valores de un conjunto no están ordenados, por lo que, a diferencia de una
lista, no hay una función de obtención que pueda usar para obtener el valor en un
índice específico. Sin embargo, aún puede usar la función contiene para verificar si
un conjunto contiene un valor particular usando un código como este: Esto devuelve verdadero si friendSet tiene
un valor "Fred", y falso si no lo tiene.
val isFredGoing = friendSet.contains("Fred")
Y también puede recorrer un conjunto como este:
for (elemento en friendSet) println(elemento)
Un conjunto es inmutable, por lo que no puede agregarle valores ni eliminar A diferencia de una lista, un
los existentes. Para hacer este tipo de cosas, necesitaría usar un MutableSet
en su lugar. Pero antes de que le mostremos cómo crear y usar uno de estos, conjunto no está ordenado y
hay una pregunta importante que debemos analizar: ¿ cómo decide un conjunto
si un valor es un
no puede contener valores duplicados.
¿duplicar?
264 Capítulo 9
Machine Translated by Google
colecciones
Cómo un conjunto comprueba si hay duplicados
Para responder a esta pregunta, repasemos los pasos que sigue
un Conjunto cuando decide si un valor es un duplicado o no.
1
El Conjunto obtiene el código hash del objeto y lo compara
con los códigos hash de los objetos que ya están en el Necesito saber si
Conjunto. los valores de su
Un conjunto utiliza códigos hash para almacenar sus elementos de una código hash son los mismos.
manera que hace que sea mucho más rápido acceder a ellos. Utiliza el
código hash como una especie de etiqueta en un "cubo" donde almacena
elementos, por lo que todos los objetos con un código hash de, por
código hash: 742
ejemplo, 742, se almacenan en el cubo etiquetado como 742.
ÁRBITRO
Si el Conjunto no tiene códigos hash coincidentes para el
nuevo valor, el Conjunto asume que no es un duplicado y
agrega el nuevo valor. Sin embargo, si el conjunto tiene
código hash: 742
códigos hash coincidentes, debe realizar pruebas adicionales
y pasar al paso 2. Colocar
2 El Conjunto utiliza el operador === para comparar
Tus códigos hash
el nuevo valor con cualquier objeto que contenga son lo mismo. ¿Eres el
con el mismo código hash. mismo objeto...?
Como aprendió en el Capítulo 7, el operador ===
código hash: 742
se utiliza para comprobar si dos referencias se refieren a
el mismo objeto Entonces, si el operador === devuelve ÁRBITRO
verdadero para cualquier objeto con el mismo código
hash, el Conjunto sabe que el nuevo valor es un
duplicado, por lo que lo rechaza. Sin embargo, si el código hash: 742
operador === devuelve falso, el Conjunto pasa al paso 3. Colocar
3 El Conjunto utiliza el operador == para comparar el nuevo
... ¿O sois iguales?
valor con cualquier objeto que contenga con códigos hash
coincidentes.
El operador == llama a la función de igualdad del valor.
código hash: 742
Si esto devuelve verdadero, el Conjunto trata el nuevo valor
como un duplicado y lo rechaza. Sin embargo, si el operador ÁRBITRO
== devuelve falso, el Conjunto asume que el nuevo valor no
es un duplicado y lo agrega.
código hash: 742
Entonces, hay dos situaciones en las que un Conjunto ve un
Colocar
valor como un duplicado: si es el mismo objeto o si es igual a un
valor que ya contiene. Veamos esto con más detalle.
estas aqui 4 265
Machine Translated by Google
la igualdad importa
Códigos hash e igualdad
Como aprendió en el Capítulo 7, el operador === verifica si dos referencias apuntan
al mismo objeto, y el operador == verifica si las referencias apuntan a objetos que deben
considerarse iguales. Sin embargo, un conjunto solo usa estos operadores una vez que se
establece que los dos objetos tienen valores de código hash coincidentes. Esto significa
que para que un conjunto funcione correctamente, los objetos iguales deben tener códigos
hash coincidentes.
Veamos cómo se aplica esto a los operadores === y ==.
Igualdad usando el operador ===
ÁRBITRO
Si tiene dos referencias que hacen referencia al mismo objeto, obtendrá el Código
hash "Sue": 83491
mismo resultado cuando llame a la función hashCode en cada referencia. Si a
no anula la función hashCode, el comportamiento predeterminado (que hereda
de la superclase Any) es que cada objeto obtendrá un código hash único.
valor cadena Cadena
ÁRBITRO
Cuando se ejecuta el siguiente código, el Conjunto detecta que a y b
Aquí, a y b se refieren
tienen el mismo código hash y se refieren al mismo objeto, por lo que se al mismo objeto, por lo
b
agrega un valor al Conjunto:
que el Conjunto sabe que
b es un duplicado de a.
val a = "Sue" valor cadena
valor b = un
val set = setOf(a, b)
Igualdad usando el operador ==
Código hash
Si desea que un Conjunto trate dos objetos Receta diferentes como iguales “Curry tailandés”: 64
o equivalentes, tiene dos opciones: convertir Receta en una clase de datos
o anular las funciones hashCode y equals que hereda de Cualquiera. Convertir ÁRBITRO
a Recipe en una clase de datos es más fácil, ya que anula automáticamente Receta
las dos funciones. a
Como dijimos anteriormente, el comportamiento predeterminado (de
Cualquiera) es dar a cada objeto un valor de código hash único. Por lo val Receta Código hash
tanto, debe anular hashCode para asegurarse de que dos objetos “Curry tailandés”: 64
equivalentes devuelvan el mismo código hash. Pero también debe anular
los valores iguales para que el operador == devuelva verdadero cuando se ÁRBITRO
usa para comparar objetos con valores de propiedad coincidentes.
Receta
b
En el siguiente ejemplo, se agregará un valor al conjunto si la receta es una Aquí, a y b se refieren a
clase de datos: objetos separados. El
val Receta
Conjunto ve a b como un
val a = Receta ("Curry tailandés") duplicado solo si a y b tienen
val b = Receta ("Curry tailandés") el mismo valor de código hash,
val set = setOf(a, b) y a == b. Este será el caso si
Receta es una clase de datos.
266 Capítulo 9
Machine Translated by Google
colecciones
Reglas para anular
hashCode y equals
Si decide anular manualmente las funciones hashCode y equals
en su clase en lugar de usar una clase de datos, hay una serie de leyes P: ¿ Cómo pueden los códigos hash ser iguales incluso si
que debe cumplir. Si no lo hace, el universo de Kotlin colapsará porque los objetos no son iguales?
cosas como los Conjuntos no funcionarán correctamente, así que
asegúrese de seguirlos.
R: Como dijimos antes, un Conjunto usa códigos hash
Estas son las reglas: para almacenar sus elementos de una manera que lo
haga mucho más rápido de acceder. Si desea encontrar
¥ Si dos objetos son iguales, deben tener códigos un objeto en un conjunto, no tiene que comenzar a
hash coincidentes. buscar desde el principio, mirando cada elemento para
ver si coincide. En cambio, usa el código hash como una
¥ Si dos objetos son iguales, llamar a equals en etiqueta en un "cubo" donde almacenó el elemento.
cualquiera de los objetos debe devolver verdadero. En Entonces, si dices "Quiero encontrar un objeto en el
otras palabras, si (a. es igual a (b)) entonces (b. es igual a (a)). Conjunto que se parezca a este...", el Conjunto obtiene el
valor del código hash del objeto que le das, luego va
¥ Si dos objetos tienen el mismo valor de código hash,
directamente al cubo para ese código hash.
no es necesario que sean iguales. Pero si son iguales,
deben tener el mismo valor de código hash.
Esta no es toda la historia, pero es más que suficiente para
usar un conjunto de manera efectiva y comprender lo que
¥ Entonces, si anula equals, debe anular hashCode. está sucediendo.
El punto es que los códigos hash pueden ser
¥ El comportamiento predeterminado de la función
iguales sin garantizar necesariamente que los objetos
hashCode es generar un número entero único
sean iguales, porque el "algoritmo hash" utilizado en la
para cada objeto. Entonces, si no anula
función hashCode podría devolver el mismo valor para
hashCode en una clase que no es de datos, no hay varios objetos. Y sí, eso significa que todos los objetos
dos objetos de ese tipo que puedan considerarse iguales. múltiples aterrizarían en el mismo cubo en el Conjunto
¥ El comportamiento predeterminado de la función equals (porque cada cubo representa un valor de código hash
es hacer una comparación ===, que prueba si las dos separado), pero ese no es el fin del mundo. Puede
significar que el Conjunto es un poco menos eficiente,
referencias se refieren a un solo objeto. Por lo tanto, si
o que está lleno de una cantidad extremadamente
no anula los valores iguales en una clase que no es de
grande de elementos, pero si el Conjunto encuentra
datos, dos objetos nunca se pueden considerar iguales,
más de un objeto en el mismo cubo de código hash, el
ya que las referencias a dos objetos diferentes siempre
Conjunto simplemente usará el === y == operadores
contendrán un patrón de bits diferente.
para buscar una combinación perfecta. En otras
palabras, los valores del código hash a veces se usan
a.equals(b) también debe significar para restringir la búsqueda, pero para encontrar la
coincidencia exacta, el Conjunto todavía tiene que
que a.hashCode() == b.hashCode() tomar todos los objetos en ese cubo (el cubo para todos
los objetos con el mismo código hash) y vea si hay un
objeto coincidente en ese depósito.
Pero a.hashCode() == b.hashCode() no
tiene por qué significar que a.equals(b)
estas aqui 4 267
Machine Translated by Google
MutableSet
Cómo usar un MutableSet
Ahora que sabe acerca de los Conjuntos, echemos un vistazo a
MutableSets. Un MutableSet es un subtipo de Set, pero con funciones
adicionales que puede usar para agregar y eliminar valores.
Defina un MutableSet usando la función mutableSetOf de esta manera: "Jim"
val mFriendSet = mutableSetOf("Jim", "Sue") ÁRBITRO
Cadena
ÁRBITRO
ÁRBITRO
Esto inicializa un MutableSet con dos cadenas, por lo que el "Demandar"
amigo m
compilador infiere que desea un MutableSet de tipo
Colocar
MutableSet<String>.
Cadena
valor
Agrega nuevos valores a un MutableSet usando la función de
agregar . El siguiente código, por ejemplo, agrega "Nick" a MutableSet<String>
mFriendSet:
Si pasa valores de cadena a la
mFriendSet.add("Nick") función mutableSetOf(), el compilador
infiere que desea un objeto de tipo
La función de agregar verifica si el objeto que se pasa ya existe en MutableSet<String> (un MutableSet
MutableSet. Si encuentra un valor duplicado, devuelve falso. Sin embargo, que contiene cadenas).
si no es un duplicado, el valor se agrega al MutableSet (aumentando su
tamaño en uno) y la función devuelve verdadero para indicar que la operación
fue exitosa.
Elimina valores de un MutableSet usando la función de eliminación. El
siguiente código, por ejemplo, elimina "Nick" de mFriendSet:
mFriendSet.remove("Nick")
Si "Nick" existe en MutableSet, la función lo elimina y devuelve
"Jim"
verdadero. Sin embargo, si no hay ningún objeto coincidente, la función
simplemente devuelve falso.
ÁRBITRO
Cadena
También puede usar las funciones addAll, removeAll y retainAll para
ÁRBITRO
realizar cambios masivos en MutableSet, tal como puede hacerlo con "Demandar"
MutableList. La función addAll, por ejemplo, agrega todos los elementos al
MutableSet que se encuentran en otra colección, por lo que puede usar el
siguiente código para agregar "Joe" y "Mia" a mFriendSet: ÁRBITRO
Cadena
"José"
addAll() agrega
val toAdd = setOf("Joe", "Mia")
los valores ÁRBITRO
Y tal como puede hacerlo con MutableList, puede usar la función "Desaparecido en combate"
borrar para eliminar todos los elementos del MutableSet:
mFriendSet.clear()
Cadena
268 Capítulo 9
Machine Translated by Google
colecciones
Puedes copiar un MutableSet
Si desea tomar una instantánea de un MutableSet, puede
hacerlo, al igual que con una MutableList. Puede usar la función
toSet , por ejemplo, para tomar una copia inmutable de
mFriendSet y asignar la copia a una nueva variable llamada
friendSetCopy:
val friendSetCopy = mFriendSet.toSet()
También puede copiar un Set o MutableSet en un nuevo objeto
List usando toList:
val lista de amigos = mFriendSet.toList()
MutableSet también tiene una función
Y si tiene una MutableList o List, puede copiarla en un Set usando toMutableSet() (que lo copia en un nuevo
MutableSet) y toMutableList() (que lo copia en
su función toSet :
un nuevo MutableList).
val shoppingSet = mShopping.toSet()
Copiar una colección en otro tipo puede ser particularmente útil
cuando desea realizar alguna acción que de otro modo sería
ineficaz. Puede, por ejemplo, comprobar si una Lista contiene
valores duplicados copiando la Lista en un Conjunto y comprobando
el tamaño de cada colección. El siguiente código usa esta técnica
para verificar si mShopping (una MutableList) contiene duplicados: Esto crea una versión Set de
mShopping y obtiene su tamaño.
if (mCompras.tamaño > mCompras.toSet().tamaño) {
//mShopping tiene valores duplicados
}
Si mShopping contiene duplicados, su tamaño será mayor
que cuando se copia en un Conjunto, porque al convertir
MutableList en un Conjunto se eliminarán los valores duplicados.
"Té"
ÁRBITRO
"Té" Cuando la
Esta es una Lista. 0 lista se copia en
Tiene tres ÁRBITRO Cadena
Cadena un conjunto, se
elementos, por
ÁRBITRO
elimina el valor ÁRBITRO
lo que su tamaño es 3. "Café"
1 duplicado de
"Café"
"Café".
ÁRBITRO El tamaño del Cadena
2 Cadena conjunto es de 2.
estas aqui 4 269
Machine Translated by Google
proyecto de actualización
Actualizar el proyecto Colecciones
Ahora que conoce Sets y MutableSets, actualicemos el proyecto Collections para
que los use.
Actualice su versión de Collections.kt para que coincida con la nuestra a continuación
(nuestros cambios están en negrita):
diversión principal(argumentos: Array<String>) {
val var mShoppingList = mutableListOf("Té", "Huevos", "Leche")
Actualice mShoppingList a println("mShoppingList original: $mShoppingList")
una var para que podamos val extraShopping = listOf("Galletas", "Azúcar", "Huevos")
reemplazarla con otra mShoppingList.addAll(compras adicionales)
MutableList<String> más
println("artículos mShoppingList agregados: $mShoppingList")
adelante en el código.
if (mListaDeCompras.contains("Té")) {
mShoppingList.set(mShoppingList.indexOf("Té"), "Café")
}
mListaDeCompras.sort()
Colecciones
println("mListaCompras ordenada: $mListaCompras")
mListaDeCompras.reverse()
origen
println("mListaDeCompras invertida: $mListaDeCompras")
Colecciones.kt
val mShoppingSet = mShoppingList.toMutableSet()
println("mConjuntoCompras: $mConjuntoCompras")
Agrega este código. val másCompras = setOf("Cebollino", "Espinacas", "Leche")
mShoppingSet.addAll(moreShopping)
println("mShoppingSet artículos agregados: $mShoppingSet")
mShoppingList = mShoppingSet.toMutableList()
println("mShoppingList nueva versión: $mShoppingList")
}
Tomemos el código para una prueba de manejo.
270 Capítulo 9
Machine Translated by Google
colecciones
Prueba de conducción
Cuando ejecutamos el código, el siguiente texto se imprime en el
Ventana de salida del IDE:
mShoppingList original: [Té, Huevos, Leche]
mShoppingList elementos agregados: [Té, Huevos, Leche, Galletas, Azúcar,
Huevos] mShoppingList ordenado: [Café, Galletas, Huevos, Huevos, Leche, Azúcar] Al imprimir un conjunto
mShoppingList invertida: [Azúcar, Leche, Huevos, Huevos, Galletas, Café] o un conjunto mutable, se
imprime cada elemento entre corchetes.
mShoppingSet: [Azúcar, Leche, Huevos, Galletas, Café]
Se agregaron elementos de mShoppingSet: [Azúcar, Leche, Huevos, Galletas, Café, Cebollino,
Espinacas] Nueva versión de mShoppingList: [Azúcar, Leche, Huevos, Galletas, Café, Cebollino, Espinacas]
De manera similar, List y
Set (y por lo tanto MutableList y val a = setOf(1, 2, 3) val b = R: Sí, puedes usar == para comparar
setOf(3, 2, 1) //a == b es el contenido de dos Listas. regresará
MutableSet) tienen una función llamada
verdadero verdadero si las listas tienen los mismos valores
toTypedArray() que copia la colección a
contra los mismos índices, y falso si las Listas
una nueva matriz del tipo apropiado. Sin embargo, si los dos conjuntos comparan contienen valores diferentes, o contienen los
Entonces el código:
valores diferentes, el resultado será falso. mismos valores en un orden diferente. así que en
el siguiente ejemplo, a == b devuelve
val s = setOf(1, 2, 3) val a =
verdadero:
s.toTypedArray()
crea una matriz de tipo Array<Int>. val a = lista de (1, 2, 3) val b =
lista de (1, 2, 3)
estas aqui 4 271
Machine Translated by Google
ser el conjunto
SER el conjunto Aquí
hay cuatro clases de patos. Tu trabajo es Esta es la función principal.
jugar como si fueras el Conjunto y decir qué
clases producirán un Conjunto que contenga diversión principal(argumentos: Array<String>) {
exactamente un elemento cuando val set = setOf(Pato(), Pato(17))
se use con la función principal a
imprimir (establecer)
la derecha. ¿Algún pato rompe }
las reglas hashCode() y equals()?
¿Si es así, cómo?
A pato de clase (tamaño de valor: Int = 17) {
anular la diversión es igual a (otro: ¿alguno?): booleano {
if (this === otro) devuelve verdadero if (otro es Pato
&& tamaño == otro.tamaño) devuelve verdadero devuelve falso
anular fun hashCode (): Int { tamaño de retorno
}
}
B pato de clase (tamaño de valor: Int = 17) {
anular la diversión es igual a (otro: ¿alguno?): booleano {
falso retorno
}
anular fun hashCode(): Int {
volver 7
}
}
C pato de clase de datos (tamaño de val: Int = 18)
D pato de clase (tamaño de valor: Int = 17) {
anular la diversión es igual a (otro: ¿alguno?): booleano {
volver verdadero
}
anular fun hashCode(): Int {
volver (Math.random() * 100).toInt()
}
}
Respuestas en la página 274.
272 Capítulo 9
Machine Translated by Google
colecciones
Cuatro amigos han hecho cada uno una Lista de sus mascotas. Un artículo
en la Lista representa una mascota. Aquí están las cuatro listas:
val mascotasLiam = listOf("Gato", "Perro", "Pez", "Pez")
val mascotasSophia = listOf("Gato", "Búho")
val mascotasNoé = listaDe("Perro", "Paloma", "Perro", "Paloma")
val mascotasEmily = listOf("Erizo")
Escriba el código a continuación para crear una nueva colección llamada mascotas que contenga cada mascota.
¿ Cómo usarías la colección de mascotas para obtener el número total de mascotas?
Escriba el código para imprimir cuántos tipos de mascotas hay.
¿Cómo enumerarías los tipos de mascotas en orden alfabético?
Respuestas en la página 275.
estas aqui 4 273
Machine Translated by Google
ser la solución establecida
SER la solución establecida
Aquí hay cuatro clases de patos. Tu trabajo Esta es la función principal.
es jugar como si fueras el Conjunto y decir qué
clases producirán un Conjunto que contenga diversión principal(argumentos: Array<String>) {
exactamente un elemento cuando val set = setOf(Pato(), Pato(17))
se use con la función principal a imprimir (establecer)
la derecha. ¿Algún pato rompe las }
reglas hashCode() y equals()? ¿Si
es así, cómo?
A pato de clase (tamaño de valor: Int = 17) {
anular la diversión es igual a (otro: ¿alguno?): booleano {
if (this === otro) devuelve verdadero if (otro es Pato
&& tamaño == otro.tamaño) devuelve verdadero devuelve falso
}
Esto sigue las reglas hashCode() y equals(). El
Conjunto reconoce que el segundo Pato es un
anular fun hashCode (): Int { tamaño de retorno
duplicado, por lo que la función principal crea un
Conjunto que contiene un elemento.
}
}
B pato de clase (tamaño de valor: Int = 17) {
anular la diversión es igual a (otro: ¿alguno?): booleano {
falso retorno
Esto produce un Conjunto con dos elementos.
}
La clase rompe las reglas hashCode() y equals()
anular fun hashCode(): Int { ya que equals() siempre devuelve falso, incluso si se
volver 7
usa para comparar un objeto consigo mismo.
}
}
C pato de clase de datos (tamaño de val: Int = 18) Esto sigue las reglas, pero produce un Conjunto con dos elementos.
D pato de clase (tamaño de valor: Int = 17) {
Esto produce un Conjunto con dos elementos.
anular la diversión es igual a (otro: ¿alguno?): booleano {
La clase rompe las reglas ya que hashCode()
volver verdadero
} devuelve un número aleatorio. Las reglas dicen que
los objetos iguales deben tener el mismo código hash.
anular fun hashCode(): Int {
volver (Math.random() * 100).toInt()
}
}
274 Capítulo 9
Machine Translated by Google
colecciones
Cuatro amigos han hecho cada uno una Lista de sus mascotas. Un artículo
en la Lista representa una mascota. Aquí están las cuatro listas:
val mascotasLiam = listOf("Gato", "Perro", "Pez", "Pez")
val mascotasSophia = listOf("Gato", "Búho")
val mascotasNoé = listaDe("Perro", "Paloma", "Perro", "Paloma")
val mascotasEmily = listOf("Erizo")
Escriba el código a continuación para crear una nueva colección llamada mascotas que contenga cada mascota.
var mascotas: MutableList<String> = mutableListOf() mascotas.addAll(mascotasLiam)
No se preocupe
si sus respuestas
se ven diferentes
a las nuestras. mascotas.addAll(mascotasSophia)
Hay diferentes
mascotas.addAll(mascotasNoah)
formas de obtener
el mismo resultado.
mascotas.addAll(mascotasEmily)
¿ Cómo usarías la colección de mascotas para obtener el número total de mascotas?
mascotas.tamaño
Escribe el siguiente código para imprimir cuántos tipos de mascotas hay.
val petSet = mascotas.toMutableSet() println(petSet.size)
¿Cómo enumerarías los tipos de mascotas en orden alfabético?
val petList = petSet.toMutableList()
petList.sort()
println(lista de mascotas)
estas aqui 4 275
Machine Translated by Google
mapas
Hora de un mapa Estos son los valores del Mapa.
Cada entrada en un mapa es en realidad dos objetos: una clave y un valor.
Cada clave tiene un único valor asociado a ella. Puede tener valores
duplicados, pero no puede tener claves duplicadas.
“Clave A” “Tecla B” “Clave C”
Cómo crear un mapa
Estas son las claves del Mapa.
Creas un mapa llamando a una función llamada mapOf, pasando los
pares clave/valor con los que deseas que se inicialice el mapa.
El siguiente código, por ejemplo, crea un Mapa con tres entradas.
Las claves son las cadenas ("Recipe1", "Recipe2" y "Recipe3"), y los
valores son los objetos Recipe:
val r1 = Receta("Sopa de Pollo")
Cada entrada toma la forma Clave de
val r2 = Receta("Ensalada de Quinoa")
valor. Las claves son normalmente
val r3 = Receta ("Curry tailandés") cadenas, como en este ejemplo.
val recetaMap = mapOf("Receta1" a r1, "Receta2" a r2, "Receta3" a r3)
val recetaMap: Map<String, Receta>
En general, el tipo de Mapa toma la forma: “Receta1” “Receta2” “Receta3”
ÁRBITRO
Mapa<tipo_clave, tipo_valor>
receta
Ahora que sabe cómo crear un mapa, veamos cómo
Mapa
usarlo.
valor
Mapa<Cadena, Receta>
El tipo de clave es primero... ...seguido del tipo Valor.
276 Capítulo 9
Machine Translated by Google
colecciones
Cómo usar un mapa
Hay tres cosas principales que podría querer hacer con un mapa:
verificar si contiene una clave o valor específico, recuperar un valor para
una clave específica o recorrer las entradas del mapa.
Comprueba si un mapa contiene una clave o valor en particular
usando sus funciones containsKey y containsValue . El siguiente código,
por ejemplo, verifica si el Mapa llamado recetaMap contiene la clave
"Receta1":
recetaMapa.containsKey("Receta1")
Y puede averiguar si el mapa de recetas contiene una Receta para
Sopa de pollo usando la función containsValue de esta manera: Aquí, asumimos que Receta es una clase
de datos, por lo que el Mapa puede
val recetaParaVerificar = Receta("Sopa de Pollo")
detectar cuándo dos objetos Receta son iguales.
if (recetaMapa.containsValue(recetaParaVerificar)) {
//Código que se ejecuta si el Mapa contiene el valor
}
Puede obtener el valor de una clave específica utilizando las
funciones get y getValue . get devuelve un valor nulo si la clave especificada
no existe, mientras que getValue genera una excepción.
Así es como, por ejemplo, usaría la función getValue para obtener el objeto
Receta asociado con la clave "Receta1":
if (recetaMapa.containsKey("Receta1")) { Si el mapa de recetas no contiene
val receta = recetaMap.getValue("Receta1") una clave "Receta1", esta línea
//Código para usar el objeto Receta generará una excepción.
También puede recorrer las entradas de un mapa. Así es como,
por ejemplo, usaría un bucle for para imprimir cada par clave/valor en el
mapa de recetas:
for ((clave, valor) en recetaMap) {
println("La clave es $clave, el valor es $valor")
}
Un mapa es inmutable, por lo que no puede agregar ni quitar pares clave/
valor, ni actualizar el valor retenido contra una clave específica. Para
realizar este tipo de acción, debe usar un MutableMap en su lugar. Veamos
cómo funcionan estos.
estas aqui 4 277
Machine Translated by Google
MutableMaps
Crear un mapa mutable
Usted define un MutableMap de manera similar a cómo define un
Mapa, excepto que usa la función mutableMapOf en lugar de mapOf.
El siguiente código, por ejemplo, crea un MutableMap con tres
entradas, como antes:
val r1 = Receta("Sopa de Pollo")
val r2 = Receta("Ensalada de Quinoa")
val mRecipeMap = mutableMapOf("Receta1" a r1, "Receta2" a r2)
MutableMap se inicializa con claves de cadena y valores de "Quinua
"Pollo
receta, por lo que el compilador infiere que debe ser un Ensalada"
Sopa"
MutableMap de tipo MutableMap<String, Recipe>.
Receta
MutableMap es un subtipo de Mapa, por lo que puede llamar a las Receta
mismas funciones en un MutableMap que en un Mapa. Sin embargo,
un MutableMap tiene funciones adicionales que puede usar para ÁRBITRO ÁRBITRO
ÁRBITRO
agregar, eliminar y actualizar pares clave/valor.
mReceta
Poner entradas en un MutableMap Mapa “Receta1” “Receta2”
Pones entradas en un MutableMap usando la función put . val MutableMap
El siguiente código, por ejemplo, coloca una clave llamada <Cadena, Receta>
"Recipe3" en mRecipeMap y la asocia con un objeto Receta
para Thai Curry: Si pasa claves de cadena y valores de
receta a la función mutableMapOf(), el
val r3 = Receta ("Curry tailandés") Especifique primero compilador infiere que desea un objeto
la clave y luego el valor. de tipo MutableMap<String, Recipe>.
mRecipeMap.put("Receta3", r3)
Si MutableMap ya contiene la clave especificada, la función put
reemplaza el valor de esa clave y devuelve el valor original.
Puede poner muchos pares clave/valor en MutableMap a la vez usando la
función putAll . Esto toma un argumento, un Mapa que contiene las
entradas que desea agregar. El siguiente código, por ejemplo, agrega los
objetos Jambalaya y Sausage Rolls Recipe a un mapa llamado
RecipesToAdd y luego coloca estas entradas en mRecipeMap:
val r4 = Receta("Jambalaya")
val r5 = Receta("Rollos De Salchicha")
val recetasParaAgregar = mapOf("Receta4" a r4, "Receta5" a r5)
mRecipeMap.putAll(recetasParaAgregar)
A continuación, veamos cómo elimina valores.
278 Capítulo 9
Machine Translated by Google
colecciones
Puede eliminar entradas de un MutableMap
Elimina una entrada de un MutableMap usando la función de eliminación .
Esta función está sobrecargada, por lo que hay dos formas de llamarla.
La primera forma es pasar a la función eliminar la clave cuya
entrada desea eliminar. El siguiente código, por ejemplo, elimina
la entrada de mRecipeMap que tiene una clave de "Recipe2":
mRecetaMap.remove("Receta2") Eliminar la entrada con una clave de "Receta2"
La segunda forma es pasar a la función de eliminación el nombre de la
clave y un valor. En este caso, la función solo eliminará la entrada si
encuentra una coincidencia tanto para la clave como para el valor. Por lo
tanto, el siguiente código solo elimina la entrada de "Receta2" si está
asociada con un objeto Receta de ensalada de quinoa:
val recetaParaEliminar = Receta("Ensalada de Quinoa") Elimine la entrada con una clave de
mRecipeMap.remove("Receta2", recetaParaEliminar) "Receta2", pero solo si su valor es un
objeto Receta de ensalada de quinoa.
Cualquiera que sea el enfoque que utilice, eliminar una entrada de
MutableMap reduce su tamaño.
Finalmente, puede usar la función borrar para eliminar todas las entradas
de MutableMap, tal como puede hacerlo con MutableLists y MutableSets:
mRecetaMap.clear()
Oye, ¿adónde
fueron todos?
ÁRBITRO
mReceta
Mapa La función clear() elimina todas
las entradas, pero el objeto MutableMap
val MutableMap
en sí todavía existe.
<Cadena, Receta>
Ahora que ha visto cómo actualizar un MutableMap, veamos
cómo puede hacer copias de uno.
estas aqui 4 279
Machine Translated by Google
copiando mapas
Puedes copiar Maps y MutableMaps
Al igual que los otros tipos de colección que ha visto, puede
tomar una instantánea de un MutableMap. Puede usar la función
toMap , por ejemplo, para tomar una copia de solo lectura de
mRecipeMap y asignar la copia a una nueva variable:
val recetaMapCopy = mRecipeMap.toMap()
Puede copiar un mapa o mapa mutable en un nuevo objeto de lista que
Un MutableMap también tiene las funciones
contenga todos los pares clave/valor usando toList de esta manera: toMutableMap() y toMutableList().
val Lista de Recetas = mRecipeMap.toList()
Y también puede obtener acceso directo a los pares clave/valor utilizando la
propiedad de entradas del mapa . La propiedad de entradas devuelve un
conjunto si se usa con un mapa y devuelve un conjunto mutable si se usa con
un mapa mutable. El siguiente código, por ejemplo, devuelve un MutableSet
de pares clave/valor de mRecipeMap:
val recetasEntries = mRecipeMap.entries
Tenga en cuenta que las propiedades de
Otras propiedades útiles son las claves (que devuelve un Conjunto, entradas, claves y valores son los
o MutableSet, de las claves del Mapa) y los valores (que devuelve una contenidos reales del Mapa o MutableMap.
colección genérica de los valores del Mapa). Puede usar estas propiedades No son copias. Y si está utilizando un
para, por ejemplo, verificar si un mapa contiene valores duplicados usando MutableMap, estas propiedades son actualizables.
un código como este:
if (mRecipeMap.tamaño > mRecipeMap.valores.toSet().tamaño) {
println("mRecipeMap contiene valores duplicados")
}
Esto se debe a que el código:
mRecipeMap.valores.toSet()
copia los valores del mapa en un conjunto, que elimina los valores duplicados.
Ahora que ha aprendido a usar Maps y MutableMaps, agreguemos
algunos a nuestro proyecto Colecciones.
280 Capítulo 9
Machine Translated by Google
colecciones
El código completo para el proyecto Colecciones
Actualice su versión de Collections.kt para que coincida con la nuestra a
continuación (nuestros cambios están en negrita):
clase de datos Receta (var nombre: Cadena) Agregue la clase de datos Receta.
diversión principal(argumentos: Array<String>) {
var mShoppingList = mutableListOf("Té", "Huevos", "Leche") println("mShoppingList
original: $mShoppingList") val extraShopping = listOf("Galletas", "Azúcar", "Huevos")
mShoppingList.addAll(extraShopping) println("artículos de mShoppingList agregados:
$mShoppingList") if (mShoppingList.contains("Tea"))
{ mShoppingList.set(mShoppingList.indexOf("Tea"), "Coffee")
}
mShoppingList.sort()
println("mShoppingList ordenada: $mShoppingList") mShoppingList.reverse()
println("mShoppingList invertida: $mShoppingList")
Colecciones
val mShoppingSet = mShoppingList.toMutableSet() println("mShoppingSet:
origen
$mShoppingSet") val másCompras = setOf("Cebollino", "Espinacas",
"Leche") mShoppingSet.addAll(másCompras) println("mShoppingSet elementos
agregados: $mShoppingSet" ) mShoppingList = mShoppingSet.toMutableList() Colecciones.kt
println("mShoppingList nueva versión: $mShoppingList")
val r1 = Receta("Sopa de Pollo") val r2 =
Receta("Ensalada de Quinoa") val r3 =
Receta("Curry Tailandés") val r4 =
Receta("Jambalaya") val r5 = Receta("Rollitos de
Salchicha") val mRecipeMap =
Agrega este
mutableMapOf("Receta1" a r1, "Receta2" a r2, "Receta3" a r3) println("mRecipeMap original: $mRecipeMap") val
código.
recetasParaAgregar = mapOf("Receta4" a r4, "Receta5" a r5) mRecipeMap .putAll(recetasParaAgregar) println("mRecipeMap
actualizado: $mRecipeMap") if (mRecipeMap.containsKey("Recipe1")) {
println("Receta1 es: ${mRecetaMap.getValue("Receta1")}")
}
}
Tomemos el código para una prueba de manejo.
estas aqui 4 281
Machine Translated by Google
prueba de manejo
Prueba de conducción
Cuando ejecutamos el código, el siguiente texto se imprime en el
Ventana de salida del IDE:
mShoppingList original: [Té, Huevos, Leche]
mShoppingList elementos agregados: [Té, Huevos, Leche, Galletas, Azúcar,
Huevos] mShoppingList ordenada: [Café, Galletas, Huevos, Huevos, Leche,
Azúcar] mShoppingList invertida: [Azúcar, Leche , Huevos, Huevos, Galletas,
Café] mShoppingSet: [Azúcar, Leche, Huevos, Galletas, Café] Elementos de
mShoppingSet agregados: [Azúcar, Leche, Huevos, Galletas, Café, Cebollino, Espinacas]
mShoppingList nueva versión: [Azúcar, Leche, Huevos, Galletas, Café, Cebollino, Espinacas]
mRecipeMap original: {Recipe1=Recipe(name=Pollo Sopa), Recipe2=Receta(name=Ensalada de Quinoa),
Receta3=Receta(nombre=Curry tailandés)}
mRecipeMap actualizado: {Receta1=Receta(nombre=Sopa de pollo), Receta2=Receta(nombre=Ensalada de quinua),
Receta3=Receta(nombre=Curry tailandés), Receta4=Receta(nombre=Jambalaya),
Receta5=Receta(nombre=Rollos De Salchicha)}
La impresión de un Mapa o MutableMap
Receta1 es: Receta(nombre=Sopa de Pollo) imprime cada par clave/valor dentro de llaves.
puede actualizar. val solo significa que la este capítulo, a menos que haya una buena
variable solo puede referirse a esa colección. razón por la que no debería hacerlo.
282 Capítulo 9
Machine Translated by Google
colecciones
Rompecabezas de piscina
diversión principal(argumentos: Array<String>) { Su trabajo es tomar fragmentos de código del
val term1 = "Array" grupo y colocarlos en las líneas en blanco
val term2 = "Lista" del código. No puede usar el mismo
fragmento de código más de una vez y no
val term3 = "Mapa"
necesitará usar todos los fragmentos de
val term4 =
código. Su objetivo es imprimir las
val term5 = "MapaMutable" entradas de un mapa llamado glosario
val term6 = "ConjuntoMutable" que proporciona definiciones de todos los
val term7 = "Establecer" tipos de colecciones que ha aprendido.
val def1 = "Mantiene los valores sin ningún orden en particular". val def2 =
"Contiene pares clave/valor".
val def3 = "Retiene valores en una secuencia".
val def4 = "Se puede actualizar".
val def5 = "No se puede actualizar".
val def6 = "Se puede cambiar el tamaño".
val def7 = "No se puede cambiar el tamaño".
glosario val = a "$def1 ( a "$def3 $def4 $def6",
$def5 $def7", a "$def3 $def4 $def7",
a "$def2 $def4 $def6", a "$def3 $def5
$def7", a "$def1 $def4 $ def6", a
"$def2 $def5 $def7")
for ((clave, valor) en el glosario) println("$clave: $valor")
}
Nota: ¡cada cosa del grupo
solo se puede usar una vez!
"Matriz Mutable"
"ListaMutable" termino 1
Mapa term4
term2
mapa de term5
term3 term6
term7
estas aqui 4 283
Machine Translated by Google
solución de rompecabezas de piscina
Solución de rompecabezas de piscina
diversión principal(argumentos: Array<String>) { Su trabajo es tomar fragmentos de código del
val term1 = "Array" grupo y colocarlos en las líneas en blanco
val term2 = "Lista" del código. No puede usar el mismo
fragmento de código más de una vez y
val term3 = "Mapa"
no necesitará usar todos los fragmentos
val term4 = "ListaMutable"
de código. Su objetivo es imprimir las
val term5 = "MapaMutable" entradas de un mapa llamado glosario
val term6 = "ConjuntoMutable" que proporciona definiciones de todos los
val term7 = "Establecer" tipos de colecciones que ha aprendido.
val def1 = "Mantiene los valores sin ningún orden en particular". val def2 =
"Contiene pares clave/valor".
val def3 = "Retiene valores en una secuencia".
val def4 = "Se puede actualizar".
val def5 = "No se puede actualizar".
val def6 = "Se puede cambiar el tamaño".
val def7 = "No se puede cambiar el tamaño".
No necesitabas
usar estos fragmentos.
"Matriz Mutable"
Mapa
284 Capítulo 9
Machine Translated by Google
colecciones
A continuación se muestra un breve programa de Kotlin. Falta un
bloque del programa. Su desafío es hacer coincidir el bloque de código
candidato (a la izquierda), con el resultado que vería si se insertara el
bloque. No se usarán todas las líneas de salida y algunas líneas de
salida se pueden usar más de una vez. Dibuje líneas que conecten los
Mezclado bloques de código candidatos con su salida correspondiente.
Mensajes
diversión principal(argumentos: Array<String>) {
val mList = mutableListOf("Fútbol", "Béisbol", "Baloncesto")
El código de
candidato va aquí.
Relaciona
cada candidato
con uno de los
}
posibles resultados.
Candidatos: Salida posible:
mList.sort()
[Baloncesto]
println(mLista)
[Béisbol, Baloncesto, Fútbol]
val mMap = mutableMapOf("0" a "Netball")
var x = 0 [Baloncesto]
para (elemento en mlist) {
[Fútbol, Baloncesto, Béisbol]
mMap.put(x.toString(), elemento)
}
{Baloncesto}
println(mMap.valores)
[Baloncesto, Béisbol, Fútbol]
mList.addAll(mList)
mList.reverse() {Baloncesto}
val conjunto = mlist.toSet()
[Fútbol americano]
imprimir (establecer)
{Baloncesto, Béisbol, Fútbol}
mList.sort()
mList.reverse() [Fútbol, Béisbol, Baloncesto]
println(mLista)
estas aqui 4 285
Machine Translated by Google
solución de mensajes mixtos
A continuación se muestra un breve programa de Kotlin. Falta un
bloque del programa. Su desafío es hacer coincidir el bloque de código
candidato (a la izquierda), con el resultado que vería si se insertara el
bloque. No se usarán todas las líneas de salida y algunas líneas de
salida se pueden usar más de una vez. Dibuje líneas que conecten los
Mezclado bloques de código candidatos con su salida correspondiente.
Mensajes
Solución
diversión principal(argumentos: Array<String>) {
val mList = mutableListOf("Fútbol", "Béisbol", "Baloncesto")
El código de
candidato va aquí.
Candidatos: Salida posible:
mList.sort()
[Baloncesto]
println(mLista)
[Béisbol, Baloncesto, Fútbol]
val mMap = mutableMapOf("0" a "Netball")
var x = 0 [Baloncesto]
para (elemento en mlist) {
mMap.put(x.toString(), elemento) [Fútbol, Baloncesto, Béisbol]
}
{Baloncesto}
println(mMap.valores)
[Baloncesto, Béisbol, Fútbol]
mList.addAll(mList)
mList.reverse() {Baloncesto}
val conjunto = mlist.toSet()
[Fútbol americano]
imprimir (establecer)
{Baloncesto, Béisbol, Fútbol}
mList.sort()
mList.reverse() [Fútbol, Béisbol, Baloncesto]
println(mLista)
286 Capítulo 9
Machine Translated by Google
colecciones
Tu caja de herramientas de Kotlin
CAPÍTULO
9 Tiene el Capítulo 9 en su haber y ahora ha
agregado colecciones a su caja de herramientas.
Puede descargar
el código completo
del capítulo desde
https://tinyurl.com/
HFKotlin.
Cree una matriz inicializada con valores Crea una lista usando la función listOf.
nulos mediante la función arrayOfNulls.
Cree una MutableList usando
Las funciones de matriz útiles incluyen: mutableListOf.
ordenar, invertir, contiene, min, max,
Cree un conjunto usando la función setOf.
suma, promedio.
Cree un MutableSet usando
La biblioteca estándar de Kotlin contiene clases
mutableSetOf.
y funciones prediseñadas agrupadas en
paquetes. Un conjunto busca duplicados buscando
primero valores de código hash coincidentes.
Una lista es una colección que conoce y se
Luego usa los operadores === y == para
preocupa por la posición del índice. Puede
verificar la igualdad referencial o de objeto.
contener valores duplicados.
Cree un mapa usando la función mapOf,
Un Conjunto es una colección desordenada
pasando pares clave/valor.
que no permite valores duplicados.
Cree un MutableMap usando
Un mapa es una colección que utiliza pares
mutableMapOf.
clave/valor. Puede contener valores duplicados,
pero no claves duplicadas.
List, Set y Map son inmutables.
MutableList, MutableSet y MutableMap son
subtipos mutables de estas colecciones.
estas aqui 4 287
Machine Translated by Google
Machine Translated by Google
10 genéricos
Saber Su
Entradas desde Su salidas
Cariño, me temo que
la T en Meat<T> puede
implementar la interfaz
Tabby.
A todo el mundo le gusta el código que es consistente.
Y una forma de escribir código consistente que sea menos propenso a problemas es usar genéricos.
En este capítulo, veremos cómo las clases de colección de Kotlin usan genéricos para evitar que
coloques un repollo en una lista <Seagull>. Descubrirá cuándo y cómo escribir sus propias clases,
interfaces y funciones genéricas, y cómo restringir un tipo genérico a un supertipo específico.
Finalmente, descubrirá cómo usar la covarianza y la contravarianza, lo que le permite a USTED
controlar el comportamiento de su tipo genérico.
este es un nuevo capitulo 289
Machine Translated by Google
los genéricos permiten la coherencia de tipos
Las colecciones usan genéricos
Como aprendiste en el capítulo anterior, cada vez que declaras
explícitamente el tipo de una colección, debes especificar tanto el
tipo de colección que deseas usar como el tipo de elemento que contiene.
El siguiente código, por ejemplo, define una variable que puede
contener una referencia a una MutableList of Strings:
val x: MutableList<Cadena>
El tipo de elemento se define entre paréntesis angulares <>, lo
que significa que utiliza genéricos. Generics le permite escribir código
con seguridad de tipos. Es lo que hace que el compilador le impida
poner un Volkswagen en una lista de patos. El compilador sabe que
solo puede colocar objetos Duck en una MutableList<Duck>, lo que
significa que se detectan más problemas en el momento de la compilación.
SIN genéricos, los objetos
entrarían como referencia a los
objetos Pato, Pez, Guitarra y Coche...
Pato Pez Guitarra Auto
Sin genéricos, no
MutableList habría manera de
declarar qué tipo
de objetos debería
contener MutableList.
...y sale como una referencia de tipo Cualquiera.
CON genéricos, los
objetos entran como una
Con los genéricos, puede
referencia solo a los asegurarse de que su
objetos Duck... Pato Pato Pato Pato
colección solo contenga
objetos del tipo correcto. No
tienes que preocuparte de
ListaMutable<Pato> que alguien meta una
Calabaza en un
MutableList<Duck>, o que
lo que obtengas no sea un Pato.
...y sale como una referencia de tipo Pato.
290 Capítulo 10
Machine Translated by Google
genéricos
Cómo se define una MutableList
Miremos la documentación en línea para ver cómo se
define MutableList y cómo usa genéricos. Hay dos áreas clave que
consideraremos: la declaración de la interfaz y cómo se define la
función de agregar.
Comprender la documentación de la
colección (o, ¿cuál es el significado de "E"?)
Aquí hay una versión simplificada de la definición MutableList:
MutableList hereda de las interfaces List y
MutableCollection. Cualquier tipo (el valor de
La "E" es un marcador de "E") que especifique para MutableList se usa
posición para el tipo REAL que automáticamente para el tipo de List y
usa cuando declara una MutableList. MutableCollection.
interfaz MutableList<E> : List<E>, MutableCollection<E> {
divertido agregar (índice: Int, elemento: E): Unidad
Cualquiera que sea la "E"
//Más código
determina qué tipo de cosas
puede agregar a MutableList.
}
MutableList usa "E" como sustituto del tipo de elemento que
desea que la colección contenga y devuelva.
Cuando vea una "E" en la documentación, puede buscar/
reemplazar mentalmente para cambiarla por el tipo que desee P: ¿ Entonces MutableList no es una clase?
que contenga.
R: No, es una interfaz. Cuando creas un
MutableList<String>, por ejemplo, significa que "E" se
MutableList usando la función mutableListOf , el
convierte en "String" en cualquier función o declaración de
sistema crea una implementación de esta
variable que use "E". Y MutableList<Duck> significa que todas
interfaz. Sin embargo, todo lo que le importa
las instancias de "E" se convierten en "Duck" en su lugar.
cuando lo usa es que tiene todas las propiedades
Veamos esto con más detalle. y funciones definidas en la interfaz MutableList .
estas aqui 4 291
Machine Translated by Google
cómo funcionan los parámetros de tipo
Uso de parámetros de tipo con MutableList
Cuando escribes este código:
val x: MutableList<Cadena>
Significa que MutableList:
interfaz MutableList<E> : List<E>, MutableCollection<E> {
divertido agregar (índice: Int, elemento: E): Unidad
//Más código
es tratado por el compilador como:
interfaz MutableList<String> : List<String>, MutableCollection<String> {
divertido agregar (índice: Int, elemento: Cadena): Unidad
//Más código
En otras palabras, la "E" se reemplaza por el tipo real (también
llamado parámetro de tipo) que usa cuando define MutableList.
Y es por eso que la función de agregar no le permitirá agregar
nada excepto objetos con un tipo que sea compatible con el tipo de
"E". Entonces, si crea una MutableList<String>, la función de agregar
de repente le permite agregar cadenas. Y si crea MutableList de tipo
Duck, de repente la función de agregar le permite agregar Ducks.
292 Capítulo 10
Machine Translated by Google
genéricos
Cosas que puede hacer con
una clase o interfaz genérica
Aquí hay un resumen de algunas de las cosas clave que
puede hacer cuando usa una clase o interfaz que tiene tipos
genéricos:
¥ Cree una instancia de una clase generada.
Cuando crea una colección como MutableList, debe indicarle el tipo
de objetos que permitirá que contenga, o dejar que el compilador lo
deduzca:
val lista de patos: lista mutable<pato>
patoLista = mutableListOf(Pato("Donald"), Pato("Daisy"), Pato("Lucas"))
val list = mutableListOf("Tarifa", "Fi", "Fum")
¥ Cree una función que tome un tipo genérico.
Puede crear una función con un parámetro genérico especificando su
tipo, tal como lo haría con cualquier otro tipo de parámetro:
graznido divertido (patos: MutableList<Duck>) {
//Código para hacer graznar a los Patos
¥ Cree una función que devuelva un tipo genérico.
Una función también puede devolver un tipo genérico. El siguiente
código, por ejemplo, devuelve una MutableList of Ducks:
diversión getDucks(raza: String): MutableList<Duck> {
//Código para obtener patos para la raza especificada
Pero todavía hay preguntas importantes que deben responderse
sobre los genéricos, como ¿cómo define sus propias clases e
interfaces genéricas? ¿Y cómo funciona el polimorfismo con los
tipos genéricos? Si tiene un MutableList<Animal>, ¿qué sucede si
intenta asignarle un MutableList<Dog>?
Para responder estas preguntas y más, vamos a crear una aplicación
que use tipos genéricos.
estas aqui 4 293
Machine Translated by Google
pasos
Esto es lo que vamos a hacer
Vamos a crear una aplicación que se ocupe de las mascotas. Crearemos algunas
mascotas, organizaremos concursos de mascotas para ellas y crearemos minoristas
de mascotas que puedan vender tipos específicos de mascotas. Y como estamos
usando genéricos, nos aseguraremos de que cada concurso y minorista que creemos
solo pueda tratar con un tipo específico de mascota.
Estos son los pasos que seguiremos:
1 Cree la jerarquía de mascotas.
Crearemos una jerarquía de mascotas que nos permitirá crear tres tipos de
mascotas: gatos, perros y peces.
2 Cree la clase Concurso.
La clase Contest nos permitirá crear concursos para diferentes tipos de mascotas.
Lo usaremos para administrar las puntuaciones de los concursantes para que
podamos determinar el ganador. Y como queremos que cada concurso se limite a un
tipo específico de mascota, definiremos la clase Concurso usando genéricos.
3 Cree la jerarquía de minoristas.
Crearemos una interfaz para minoristas e implementaciones concretas de
esta interfaz denominadas CatRetailer, DogRetailer y FishRetailer. Usaremos
genéricos para garantizar que cada tipo de minorista solo pueda vender un
tipo específico de mascota, de modo que no pueda comprar un gato de un
FishRetailer.
4 Crear una clase de veterinario.
Finalmente, crearemos una clase Vet, para que podamos asignar un veterinario a cada
concurso. Definiremos la clase de veterinario utilizando genéricos para reflejar el tipo
de mascota en el que se especializa cada veterinario.
Comenzaremos creando la jerarquía de clases de mascotas.
294 Capítulo 10
Machine Translated by Google
genéricos
Mascotas
Concurso
Crear la jerarquía de clases de mascotas minoristas
Veterinario
Nuestra jerarquía de clases de mascotas constará de cuatro clases: una
clase de mascotas que marcaremos como subclases abstractas y concretas
denominadas gato, perro y pez. Agregaremos una propiedad de nombre a la
clase Pet, que heredarán sus subclases concretas.
Estamos marcando Mascota como abstracta porque solo queremos poder
crear objetos que sean un subtipo de Mascota, como Gato o Perro, y como
aprendiste en el Capítulo 6, marcar una clase como abstracta evita que esa
clase sea instanciada.
Aquí está la jerarquía de clases:
Mascota
Aquí, Pet es abstracto, mientras
nombre
que Cat, Dog y Fish son
implementaciones concretas.
El código para la jerarquía de clases se ve así:
clase abstracta Mascota (var nombre: Cadena)
clase Gato(nombre: Cadena) : Mascota(nombre)
Cada subtipo de Pet tiene un
nombre (que hereda de Pet), que se
clase Perro(nombre: Cadena) : Mascota(nombre)
establece en el constructor de la clase.
clase Pez (nombre: Cadena) : Mascota (nombre)
A continuación, creemos la clase Concurso para que podamos realizar
concursos para diferentes tipos de mascotas.
295
estas aqui 4
Machine Translated by Google
clase de concurso
Mascotas
Concurso
Definir la clase Contest minoristas
Veterinario
Usaremos la clase Concurso para ayudarnos a administrar los puntajes de un
concurso de mascotas y determinar el ganador. La clase tendrá una propiedad
llamada puntuaciones y dos funciones llamadas addScore y getWinners.
Queremos que cada concurso se limite a un tipo particular de mascota. Un concurso
de gatos, por ejemplo, solo tiene concursantes de gatos, y solo los peces pueden
participar en un concurso de peces. Haremos cumplir esta regla usando genéricos.
Declarar que Contest usa un tipo genérico
Usted especifica que una clase usa un tipo genérico poniendo el nombre del tipo entre
paréntesis angulares inmediatamente después del nombre de la clase. Aquí, usaremos
"T" para indicar el tipo genérico. Puede pensar en "T" como un sustituto del tipo real
con el que tratará cada objeto del Concurso.
Aquí está el código:
La <T> después del nombre
de la clase le dice al Concurso<T>
Concurso de clase<T> {
compilador que T es un tipo genérico.
//Más código aquí
El nombre de tipo genérico puede ser cualquier identificador legal, pero la
convención (que debe seguir) es usar "T". La excepción es si está escribiendo
una clase o interfaz de colección, en cuyo caso la convención es usar "E" en su lugar
(para "Elemento"), o "K" y "V" (para "Clave" y "Valor" ) si es un mapa.
Puede restringir T a un supertipo específico
En el ejemplo anterior, T se puede reemplazar por cualquier tipo real cuando se
instancia la clase. Sin embargo, puede imponer restricciones a T especificando que
tiene un tipo. El siguiente código, por ejemplo, le dice al compilador que T debe ser
un tipo de Mascota:
//Más código aquí que debe ser Pet, o de
uno
} sus subtipos.
Entonces, el código anterior significa que puede crear objetos de Concurso que se
ocupen de Gatos, Peces o Mascotas, pero no de Bicicletas o Begonias.
A continuación, agreguemos la propiedad de puntajes a la clase Contest.
296 Capítulo 10
Machine Translated by Google
genéricos
Mascotas
Concurso
Agregar la propiedad de puntuaciones minoristas
Veterinario
La propiedad de puntajes necesita realizar un seguimiento de qué
concursante recibe qué puntaje. Por lo tanto, usaremos un MutableMap,
con los concursantes como claves y sus puntajes como valores. Como
cada concursante es un objeto de tipo T y cada puntuación es un Int, la
propiedad de puntuaciones tendrá un tipo de MutableMap<T, Int>. Si creamos
un Contest<Cat> que se ocupa de los concursantes Cat, el tipo de propiedad
de puntuaciones se convertirá en MutableMap<Cat, Int>, pero si creamos un
objeto Contest<Pet>, el tipo de puntuaciones se convertirá automáticamente
en MutableMap<Pet, Int> en su lugar .
Aquí está el código actualizado para la clase Contest:
clase Concurso<T: Mascota> { Concurso<T: Mascota>
puntuaciones val: MutableMap<T, Int> = mutableMapOf() puntuaciones
//Más código aquí
Esto define un MutableMap con claves T
}
y valores Int, donde T es el tipo genérico de
Mascota con el que se trata el Concurso.
Ahora que hemos agregado la propiedad de puntuaciones,
agreguemos las funciones addScore y getWinners.
Crear la función addScore
Queremos que la función addScore agregue la puntuación de un concursante
a las puntuaciones MutableMap. Pasaremos el concursante y la puntuación
a la función como valores de parámetros; siempre que la puntuación sea 0 o
superior, la función los agregará a MutableMap como un par clave/valor.
Aquí está el código para la función:
clase Concurso<T: Mascota> {
puntuaciones val: MutableMap<T, Int> = mutableMapOf()
Concurso<T: Mascota>
diversión addScore(t: T, puntuación: Int = 0) {
puntuaciones
if (puntuación >= 0) puntuaciones.put(t, puntuación)
añadirPuntuación
}
Poner al concursante y su
puntuación en el MutableMap,
//Más código va aquí
siempre que la puntuación
}
sea mayor o igual a 0.
Finalmente, agreguemos la función getWinners.
estas aqui 4 297
Machine Translated by Google
función obtenerGanadores
Mascotas
Concurso
Crear la función obtenerGanadores minoristas
Veterinario
La función getWinners debe devolver los concursantes con la puntuación
más alta. Obtendremos el valor de la puntuación más alta de la propiedad
de puntuaciones y devolveremos a todos los concursantes con esta
puntuación en un MutableSet. Como cada concursante tiene un tipo
genérico de T, la función debe tener un tipo de retorno de MutableSet<T>.
Aquí está el código para la función getWinners:
Obtenga el valor más alto de las puntuaciones.
diversión getWinners(): MutableSet<T> {
val puntuación más alta = puntuaciones.valores.max() Concurso<T: Mascota>
val ganadores: MutableSet<T> = mutableSetOf() puntuaciones
para ((t, puntaje) en puntajes) {
añadirPuntuación
if (puntuación == puntuación más alta) ganadores.add(t)
obtenerGanadores
} Agregue cualquier concursante con la
ganadores de regreso puntuación más alta a un MutableSet.
} Devuelve el MutableSet de ganadores.
Y aquí está el código para la clase Contest completa:
Agregaremos esta clase a una nueva
clase Concurso<T: Mascota> {
aplicación unas páginas más adelante.
puntuaciones val: MutableMap<T, Int> = mutableMapOf()
diversión addScore(t: T, puntuación: Int = 0) {
if (puntuación >= 0) puntuaciones.put(t, puntuación)
diversión getWinners(): MutableSet<T> {
val puntuación más alta = puntuaciones.valores.max()
val ganadores: MutableSet<T> = mutableSetOf()
para ((t, puntaje) en puntajes) {
if (puntuación == puntuación más alta) ganadores.add(t)
ganadores de regreso
Ahora que hemos escrito la clase Contest, usémosla para crear algunos
objetos.
298 Capítulo 10
Machine Translated by Google
genéricos
Mascotas
Concurso
Crear algunos objetos de concurso minoristas
Veterinario
Los objetos Contest se crean especificando con qué tipo de objetos debe tratar y
llamando a su constructor. El siguiente código, por ejemplo, crea un objeto Contest<Cat>
llamado catContest que se ocupa de los objetos Cat:
val catContest = Concurso<Cat>() Esto crea un concurso que aceptará gatos.
Esto significa que puede agregar objetos Cat a su propiedad de puntajes y usar su
función getWinners para devolver un MutableSet of Cats:
catContest.addScore(Gato("Fuzz Lightyear"), 50)
getWinners() devuelve un MutableSet<Cat> porque
concursogato.addScore(Gato("Katsu"), 45)
hemos especificado que catContest debe tratar
val topCat = catContest.getWinners().first() con Cats.
Y como Contest usa genéricos, el compilador evita que le pases referencias que no
sean de Cat. El siguiente código, por ejemplo, no se compilará:
El compilador le impide agregar elementos que no sean
catContest.addScore(Perro("Fido"), 23)
gatos a Contest<Cat>, por lo que esta línea no se compilará.
Un Contest<Pet>, sin embargo, aceptará cualquier tipo de Mascota, como esta:
val petContest = Concurso<Mascota>()
petContest.addScore(Gato("Fuzz Lightyear"), 50) Dado que Contest<Pet> trata con Mascotas, los
petContest.addScore(Pez("Finny McGraw"), 56) concursantes pueden ser cualquier subtipo de Mascota.
El compilador puede inferir el tipo genérico.
En algunas circunstancias, el compilador puede inferir el tipo genérico de la
información disponible. Si, por ejemplo, crea una variable de tipo Contest<Dog>,
el compilador inferirá automáticamente que cualquier objeto Contest que le pase
es un Contest<Dog> (a menos que le indique lo contrario). El siguiente código, por
ejemplo, crea un objeto Contest<Dog> y lo asigna a dogContest:
val dogContest: Concurso<Perro>
Aquí, puede usar Contest() en lugar de Contest<Dog>() ya que el compilador
dogContest = Concurso()
puede inferir el tipo de objeto del tipo de variable.
Cuando corresponda, el compilador también puede inferir el tipo genérico a partir
de los parámetros de su constructor. Si, por ejemplo, hubiéramos usado un
parámetro de tipo genérico en el constructor principal de la clase Contest como este:
clase Concurso<T: Mascota>(t: T) {...}
El compilador podría inferir que el siguiente código crea un Contest<Fish>: Esto es lo mismo que crear un concurso
usando Contest<Fish>(Fish(“Finny McGraw”)).
val concurso = Concurso(Pez("Finny McGraw")) Puede omitir <Fish> ya que el compilador
lo deduce del argumento del constructor.
estas aqui 4 299
Machine Translated by Google
de cerca
Funciones genéricas de cerca
Hasta ahora, ha visto cómo definir una función que usa un tipo genérico
dentro de una definición de clase. Pero, ¿qué sucede si desea definir una
función con un tipo genérico fuera de una clase? ¿O qué sucede si desea
que una función dentro de una clase use un tipo genérico que no está incluido
en la definición de la clase?
Si desea definir una función con su propio tipo genérico, puede hacerlo
declarando el tipo genérico como parte de la definición de la función. El
siguiente código, por ejemplo, define una función denominada listPet con
un tipo genérico, T, que se limita a los tipos de Pet. La función acepta un
parámetro T y devuelve una referencia a un objeto MutableList<T>:
Para las funciones diversión <T: Mascota> listPet(t: T): MutableList<T> {
que declaran su propio tipo println("Crear y devolver MutableList")
genérico, la <T: Mascota> volver mutableListOf(t)
va antes del nombre de la función.
}
Tenga en cuenta que cuando declara una función genérica de esta manera,
el tipo debe declararse entre corchetes antes del nombre de la función, así:
divertido <T: Mascota> listPet...
Para llamar a la función, debe especificar el tipo de objeto con el que
debe tratar la función. El siguiente código, por ejemplo, llama a la
función listPet, usando paréntesis angulares para especificar que la estamos
usando con objetos Cat:
val catList = listPet<Gato>(Gato("Zazzles"))
Sin embargo, el tipo genérico puede omitirse si el compilador puede
inferirlo de los argumentos de la función. El siguiente código, por ejemplo, Estas dos llamadas a funciones
es legal porque el compilador puede inferir que la función listPet se usa hacen lo mismo, ya que el
con Cats: compilador puede inferir que desea
que la función se ocupe de Cats.
val catList = listPet(Gato("Zazzles"))
300 Capítulo 10
Machine Translated by Google
genéricos
Mascotas
Concurso
Crear el proyecto Genéricos minoristas
Veterinario
Ahora que ha visto cómo crear una clase que usa genéricos, agréguela a
una nueva aplicación.
Cree un nuevo proyecto de Kotlin que se dirija a la JVM y asígnele el
nombre "Genéricos". Luego cree un nuevo archivo de Kotlin llamado Pets.kt
resaltando la carpeta src , haciendo clic en el menú Archivo y eligiendo Nuevo
→ Archivo/Clase de Kotlin. Cuando se le solicite, asigne al archivo el nombre
de "Mascotas" y elija Archivo en la opción Tipo.
A continuación, actualice su versión de Pets.kt para que coincida con la nuestra a continuación:
clase abstracta Mascota (var nombre: Cadena)
clase Gato(nombre: Cadena) : Mascota(nombre)
Agregue la jerarquía de mascotas.
clase Perro(nombre: Cadena) : Mascota(nombre)
clase Pez (nombre: Cadena) : Mascota (nombre)
Agregue la clase Concurso.
clase Concurso<T: Mascota> {
puntuaciones val: MutableMap<T, Int> = mutableMapOf()
Genéricos
diversión addScore(t: T, puntuación: Int = 0) {
origen
if (puntuación >= 0) puntuaciones.put(t, puntuación)
}
Mascotas.kt
diversión getWinners(): MutableSet<T> {
val ganadores: MutableSet<T> = mutableSetOf()
val puntuación más alta = puntuaciones.valores.max()
para ((t, puntaje) en puntajes) {
if (puntuación == puntuación más alta) ganadores.add(t)
ganadores de regreso
El código continúa en la
página siguiente.
estas aqui 4 301
Machine Translated by Google
prueba de manejo
Mascotas
Concurso
El código continuó... minoristas
Veterinario
diversión principal(argumentos: Array<String>) { Crea dos gatos y un pez.
val catFuzz = Gato("Fuzz Lightyear")
val catKatsu = Gato("Katsu")
val fishFinny = Fish("Finny McGraw")
Genéricos
val catContest = Concurso<Cat>() Organice un concurso de gatos (solo para gatos).
concursocat.addScore(catFuzz, 50)
origen
concursocat.addScore(catKatsu, 45)
val topCat = catContest.getWinners().first() println("El ganador del
concurso de gatos es ${topCat.name}") Mascotas.kt
Realice un concurso de mascotas, que
val petContest = Contest<Pet>()
aceptará todo tipo de mascotas.
petContest.addScore(catFuzz, 50)
petContest.addScore(fishFinny, 56) val topPet =
petContest.getWinners().first() println("El ganador del concurso de
mascotas es ${topPet.name} ")
}
Prueba de conducción
Cuando ejecutamos el código, el siguiente texto se imprime en la ventana de
salida del IDE:
El ganador del concurso de gatos es Fuzz Lightyear
El ganador del concurso de mascotas es Finny McGraw
Una vez que haya probado el siguiente ejercicio, veremos la jerarquía de
minoristas.
P: ¿Puede un tipo genérico ser anulable? P: ¿Puede una clase tener más de un tipo genérico?
R: Sí. Si tiene una función que devuelve un tipo genérico y R: Sí. Usted define múltiples tipos genéricos especificándolos
desea que este tipo sea anulable, simplemente agregue un ? después del tipo de dentro de paréntesis angulares, separados por una coma. Si
retorno genérico como este: quisiera definir una clase llamada MyMap con tipos genéricos K y
V , la definiría usando un código como este:
clase MiClase<T> { fun
miDiversión(): T? class MiMapa<K, V> {
} //El codigo va aqui
}
302 Capítulo 10
Machine Translated by Google
genéricos
Rompecabezas de piscina
Su trabajo es tomar fragmentos de código del
grupo y colocarlos en las líneas en blanco
del código. No puede usar el mismo
fragmento de código más de una vez y no
propietario de mascota de clase {
necesitará usar todos los fragmentos de val mascotas = mutableListOf( )
código. Su objetivo es crear una clase
llamada PetOwner
agregar diversión ( ) {
que acepta tipos genéricos de mascotas , mascotas.add( )
que luego debe usar para crear un nuevo
}
PetOwner<Cat> que contenga referencias a dos
objetos Cat .
divertido eliminar ( ) {
mascotas.remove( )
mascotas contiene una
}
referencia a cada mascota Dueño de mascota<T: Mascota>
que se posee. Se inicializa }
mascotas: MutableList<T>
con un valor que se pasa
al constructor PetOwner. agregar (t: diversión principal(argumentos: Array<String>) {
T) eliminar (t: T)
val catFuzz = Gato("Fuzz Lightyear")
val catKatsu = Gato("Katsu")
utilizan para actualizar la propiedad de mascotas. val catOwner = PetOwner
La función add agrega una referencia, propietariogato.add(gatoKatsu)
y el quitar la función elimina una.
}
Nota: cada cosa de Nota: cada cosa de la piscina
solo puede ser ¡ la piscina solo se puede usar una
vez! ¡usado una vez!
estas aqui 4 303
Machine Translated by Google
solución de rompecabezas de piscina
Solución de rompecabezas de piscina
Su trabajo es tomar fragmentos de código del
Especifique el tipo genérico. el constructor
grupo y colocarlos en
las líneas en blanco en el código. Tú
no puede usar el mismo fragmento T) clase PetOwner <T: Mascota>(t: {
de código más de una vez, y no val mascotas = mutableListOf( ) t
necesitará usar todos los fragmentos
de código. Su objetivo es crear una Esto crea una
clase llamada PetOwner agregar diversión ( t: T ) {
MutableList<T>.
que acepta tipos genéricos de mascotas , mascotas.add( ) t
que luego debe usar para crear un nuevo
} Añadir/Quitar valores T.
PetOwner<Cat> que contiene
referencias a dos objetos Cat .
divertido eliminar ( t: t ) {
mascotas.remove( ) t
}
Dueño de mascota<T: Mascota>
}
mascotas: MutableList<T>
agregar (t: diversión principal(argumentos: Array<String>) {
T) eliminar (t: T)
val catFuzz = Gato("Fuzz Lightyear")
val catKatsu = Gato("Katsu")
val fishFinny = Fish("Finny McGraw")
val catOwner = PetOwner (gato Fuzz)
propietariogato.add(gatoKatsu)
}
Crea un PetOwner<Cat> e
inicializa las mascotas con
una referencia a catFuzz.
No necesitabas
usar estos fragmentos. T
< T
> T: Mascota
T
T: Mascota
pescadoFinny
304 Capítulo 10
Machine Translated by Google
genéricos
Mascotas
Concurso
La jerarquía del minorista minoristas
Veterinario
Vamos a usar las clases de mascotas que creamos
anteriormente para definir una jerarquía de minoristas que
pueden vender diferentes tipos de mascotas. Para hacer esto,
definiremos una interfaz Retailer con una función de venta y tres
clases concretas llamadas CatRetailer, DogRetailer y FishRetailer
que implementan la interfaz.
Cada tipo de minorista debe poder vender un tipo particular
P: ¿ Por qué no utiliza un PetRetailer ?
de objeto. Un Minorista de Gatos, por ejemplo, solo puede
clase concreta?
vender Gatos, y un Minorista de Perros solo puede vender
Perros. Para hacer cumplir esto, usaremos genéricos para
especificar el tipo de objeto con el que trata cada clase. R: En el mundo real, es bastante probable que
desea incluir un PetRetailer que vende todos
Agregaremos un tipo genérico T a la interfaz del minorista y
tipos de mascota. Aquí, estamos diferenciando entre
especificaremos que la función de venta debe devolver objetos de
los diferentes tipos de minoristas para que podamos
este tipo. Como las clases CatRetailer, DogRetailer y FishRetailer
enseñarle detalles más importantes sobre los genéricos.
implementan esta interfaz, cada una tendrá que sustituir el tipo de
objeto “real” con el que tratan por el tipo genérico T.
Esta es la jerarquía de clases que usaremos:
(interfaz)
Minorista<T>
Retailer es una interfaz, mientras
vender(): T que CatRetailer, DogRetailer y
FishRetailer son implementaciones
concretas.
Ahora que ha visto la jerarquía de clases, escribamos el código,
comenzando con la interfaz del minorista.
estas aqui 4 305
Machine Translated by Google
Interfaz de minorista
Mascotas
Concurso
Definir la interfaz del minorista minoristas
Veterinario
La interfaz del minorista debe especificar que utiliza un tipo T genérico, que se utiliza
como tipo de devolución para la función de venta.
Aquí está el código para la interfaz:
interfaz Minorista<T> { (interfaz)
venta divertida(): T Minorista<T>
}
Las clases CatRetailer, DogRetailer y FishRetailer necesitan implementar la interfaz
vender(): T
Retailer, especificando el tipo de objeto que trata cada una. La clase CatRetailer, por
ejemplo, solo trata con Cats, por lo que la definiremos usando un código como este:
clase CatRetailer : Minorista<Cat> { CatRetailer
anular venta divertida(): Gato {
println("Vender gato")
La clase CatRetailer implementa la
interfaz Retailer para que trate con Cats. vender (): gato
devolver gato("")
Esto significa que la función vender()
}
debe devolver un Cat.
}
De manera similar, la clase DogRetailer se ocupa de los perros, por lo que
podemos definirla así:
(interfaz)
clase DogRetailer : Minorista<Perro> { Minorista<T>
anular venta divertida(): Perro {
DogRetailer reemplaza
println("Vender perro")
el tipo genérico de Retailer con vender(): T
volver Perro("")
Dog, por lo que su función sell()
}
debe devolver un Dog.
}
Cada implementación de la interfaz Retailer debe especificar el tipo de objeto que
trata reemplazando la “T” definida en la interfaz por el tipo real. La implementación PerroMinorista
de CatRetailer, por ejemplo, reemplaza "T" con "Cat", por lo que su función de venta
debe devolver un Cat. Si intenta usar algo que no sea Cat (o un subtipo de Cat) para
el tipo de retorno de venta, el código no se compilará: vender (): perro
clase CatRetailer : Minorista<Cat> { Este código no se compilará porque
anular la venta divertida (): Perro = Perro ("") la función sell() de CatRetailer debe devolver
} un Gato, y un Perro no es un tipo de Gato.
Por lo tanto, usar genéricos significa que puede establecer límites sobre cómo una clase
usa sus tipos, lo que hace que su código sea mucho más consistente y robusto.
Ahora que tenemos el código para nuestros minoristas, creemos algunos objetos.
306 Capítulo 10
Machine Translated by Google
genéricos
Mascotas
Concurso
Podemos crear objetos minoristas
CatRetailer, DogRetailer y FishRetailer... Veterinario
Como era de esperar, puede crear un objeto CatRetailer,
DogRetailer o FishRetailer y asignarlo a una variable declarando
explícitamente el tipo de variable o dejando que el compilador lo
deduzca del valor que se le asigna. El siguiente código utiliza estas
técnicas para crear dos variables CatRetailer y asignar un objeto
CatRetailer a cada una:
val catRetailer1 = CatRetailer()
val minoristaCat2: MinoristaCat = MinoristaCat()
... pero ¿qué pasa con el polimorfismo?
Como CatRetailer, DogRetailer y FishRetailer implementan la
interfaz Retailer, deberíamos poder crear una variable de tipo
Retailer (con un parámetro de tipo compatible) y asignarle uno de
sus subtipos. Y esto funciona si asignamos un objeto CatRetailer a
una variable Retailer<Cat>, o asignamos un DogRetailer a un
Retailer<Dog>:
Estas líneas son legales porque
val perroMinorista: Minorista<Perro> = PerroMinorista()
DogRetailer implementa Retailer<Dog>
val catRetailer: Minorista<Cat> = CatRetailer() y CatRetailer implementa Retailer<Cat>.
Pero si intentamos asignar uno de estos objetos a Retailer<Pet>, el
código no se compilará:
Esto no se compilará, aunque
val petRetailer: Minorista<Mascota> = CatRetailer() CatRetailer es un Retailer<Cat> y
Cat es un subtipo de Pet.
Aunque CatRetailer es un tipo de Retailer, y Cat es un tipo
de Pet, nuestro código actual no nos permitirá asignar un
objeto Retailer<Cat> a una variable Retailer<Pet>. Una variable
Retailer<Pet> solo aceptará un objeto Retailer<Pet>. No un
Minorista<Gato>, ni un Minorista<Perro>, sino solo un
Minorista<Mascota>.
Este comportamiento parece violar todo el punto del
polimorfismo. La buena noticia, sin embargo, es que podemos
ajustar el tipo genérico en la interfaz de Retailer para controlar qué
tipos de objetos puede aceptar una variable Retailer<Pet>.
estas aqui 4 307
Machine Translated by Google
covarianza
Mascotas
Concurso
Use out para hacer una covariante de tipo genérico minoristas
Veterinario
Si desea poder utilizar un objeto de subtipo genérico en lugar de un
supertipo genérico, puede hacerlo prefijando el tipo genérico sin . En
nuestro ejemplo, queremos poder asignar un Minorista<Gato> (un subtipo)
a un Minorista<Mascota> (un supertipo), por lo que agregaremos el prefijo
del tipo genérico T en la interfaz del Minorista sin así:
Si un tipo genérico
es covariante, significa
interfaz Minorista<out T> {
venta divertida(): T
Aquí está el prefijo de salida. que puede usar un subtipo
}
en lugar de un supertipo.
Cuando anteponemos un tipo genérico con out, decimos que el tipo genérico
es covariante. En otras palabras, significa que se puede usar un subtipo en
lugar de un supertipo.
Hacer el cambio anterior significa que a una variable Retailer<Pet>
ahora se le pueden asignar objetos Retailer que se ocupan de los subtipos
de Mascotas. El siguiente código, por ejemplo, ahora compila: El prefijo out en la interfaz de Retailer
significa que ahora podemos asignar un
val petRetailer: Minorista<Mascota> = CatRetailer()
Retailer<Cat> a una variable
Retailer<Pet>.
En general, un tipo genérico de clase o interfaz puede tener el prefijo out si
la clase tiene funciones que lo usan como un tipo de devolución, o si la clase
tiene propiedades val de ese tipo. Sin embargo, no puede usar si la clase
tiene parámetros de función o propiedades var de ese tipo genérico.
Otra forma de pensar en esto es que un tipo genérico
que tiene el prefijo out solo se puede usar en una posición
"out", como un tipo de retorno de función. Sin embargo,
Las colecciones se definen utilizando tipos covariantes. no se puede usar en una posición "adentro", por lo que
una función no puede recibir un tipo covariante como valor
El prefijo out no solo lo usan las clases e interfaces genéricas que usted
de parámetro.
mismo define. También son muy utilizados por el código integrado de Kotlin,
como las colecciones.
La colección List, por ejemplo, se define usando un código como este:
interfaz pública List<out E> ... { ... }
Esto significa que puede, por ejemplo, asignar una Lista de gatos a una
Lista de mascotas, y el código se compilará:
val catList: Lista<Gato> = listaDe(Gato(""), Gato(""))
val petList: List<Pet> = catList
Ahora que ha visto cómo hacer que los tipos genéricos sean covariantes,
agreguemos el código que hemos escrito a nuestro proyecto.
308 Capítulo 10
Machine Translated by Google
genéricos
Mascotas
Concurso
Actualizar el proyecto Generics minoristas
Veterinario
Actualice su versión de Pets.kt en el proyecto Generics para que coincida con la
nuestra a continuación (nuestros cambios están en negrita):
clase abstracta Mascota (var nombre: Cadena)
clase Gato(nombre: Cadena) : Mascota(nombre)
Genéricos
clase Perro(nombre: Cadena) : Mascota(nombre)
clase Pez (nombre: Cadena) : Mascota (nombre)
origen
clase Concurso<T: Mascota> {
Mascotas.kt
puntuaciones val: MutableMap<T, Int> = mutableMapOf()
diversión addScore(t: T, puntuación: Int = 0) {
if (puntuación >= 0) puntuaciones.put(t, puntuación)
}
diversión getWinners(): MutableSet<T> {
val ganadores: MutableSet<T> = mutableSetOf()
val puntuación más alta = puntuaciones.valores.max()
para ((t, puntaje) en puntajes) {
if (puntuación == puntuación más alta) ganadores.add(t)
}
ganadores de regreso
}
} Agregue la interfaz de minorista.
interfaz Minorista<out T> {
venta divertida(): T
clase CatRetailer : Minorista<Cat> {
anular venta divertida(): Gato {
println("Vender gato") return Gato("")
Agregue las clases
}
CatRetailer y DogRetailer.
}
class DogRetailer : Retailer<Dog> { override fun sell(): Dog
{ println("Vender perro") return Dog("")
El código continúa en
} la página siguiente.
}
estas aqui 4 309
Machine Translated by Google
prueba de manejo
Mascotas
Concurso
El código continuó... minoristas
Veterinario
clase MinoristaPescado : Minorista<Pescado> {
Agregue la clase FishRetailer.
anular la venta divertida (): Pescado {
println("Vender pescado") return
Pescado("")
}
}
diversión principal(argumentos: Array<String>) { Genéricos
val catFuzz = Gato("Fuzz Lightyear") val catKatsu =
Gato("Katsu")
origen
val fishFinny = Fish("Finny McGraw")
Mascotas.kt
val catContest = Concurso<Cat>()
concursocat.addScore(catFuzz, 50)
concursocat.addScore(catKatsu, 45)
val topCat = catContest.getWinners().first() println("El ganador del concurso
de gatos es ${topCat.name}")
val petContest = Concurso<Mascota>()
petContest.addScore(catFuzz, 50)
petContest.addScore(fishFinny, 56)
val topPet = petContest.getWinners().first() println("El ganador del concurso
de mascotas es ${topPet.name}")
Cree algunos objetos Minorista.
val perroMinorista: Minorista<Perro> = PerroMinorista() val gatoMinorista:
Minorista<Gato> = GatoMinorista()
val petRetailer: Retailer<Pet> = CatRetailer() petRetailer.sell()
Prueba de conducción
Cuando ejecutamos el código, el siguiente texto se imprime en la ventana de
salida del IDE:
El ganador del concurso de gatos es Fuzz Lightyear
El ganador del concurso de mascotas es Finny McGraw
vender gato
Ahora que ha visto cómo hacer que los tipos genéricos sean covariantes
usando el prefijo out, pruebe el siguiente ejercicio.
310 Capítulo 10
Machine Translated by Google
genéricos
SER el compilador
Aquí hay cinco clases e
interfaces que usan genéricos.
Tu trabajo es jugar como si
fueras el
Compilador y
determinar si cada
uno compilará. Si
no se compila, ¿por qué no?
interfaz A<salida T> {
A
diversión myFunction(t: T)
B interfaz B<salida T> {
valor x: T
diversión myFunction(): T
C interfaz C<salida T> {
variar: T
diversión myFunction(): T
D interfaz D<salida T> {
fun myFunction(str: String): T
clase abstracta E<out T>(t: T) {
mi
valor x = t
estas aqui 4 311
Machine Translated by Google
ser la solución del compilador
SER la solución del compilador
Aquí hay cinco clases e
interfaces que usan genéricos.
Tu trabajo es jugar como si
fueras el
Compilador y
determinar si cada
uno compilará. Si
no se compila, ¿por qué no?
A interfaz A<salida T> { Este código no se compilará porque el tipo covariante T no se
diversión myFunction(t: T) puede usar como un parámetro de función.
}
B interfaz B<salida T> { Este código se compila correctamente.
valor x: T
diversión myFunction(): T
C interfaz C<salida T> { Este código no se compilará porque el tipo covariante T no se
variar: T puede usar como el tipo de una propiedad var.
diversión myFunction(): T
D interfaz D<salida T> { Este código se compila correctamente.
fun myFunction(str: String): T
mi clase abstracta E<out T>(t: T) { Este código se compila correctamente.
valor x = t
312 Capítulo 10
Machine Translated by Google
genéricos
Mascotas
Concurso
Necesitamos una clase de veterinario
minoristas
Veterinario
Como dijimos anteriormente en el capítulo, queremos poder asignar un
veterinario a cada concurso en caso de que haya una emergencia médica con
alguno de los concursantes. Como los veterinarios pueden especializarse en el
tratamiento de diferentes tipos de mascotas, crearemos una clase Vet con un tipo
T genérico y especificaremos que tiene una función de tratamiento que acepta un
argumento de este tipo. También diremos que T debe ser un tipo de Mascota para
que no puedas crear un Veterinario que trate, digamos, objetos Planeta o Brócoli.
Aquí está la clase de veterinario
clase Veterinario<T: Mascota> {
regalo divertido (t: T) { Veterinario<T: Mascota>
println("Tratar mascota ${t.nombre}")
}
tratar (t: T)
}
A continuación, cambiemos la clase de concurso para que acepte un veterinario.
Asignar un veterinario a un concurso
Queremos asegurarnos de que cada Concurso tenga un Vet, por lo que
Estamos agregando un Vet<T> al
agregaremos una propiedad Vet al constructor del Concurso. Aquí está el código
constructor del Concurso para que no
del Concurso actualizado:
pueda crear un Concurso sin asignarle un Vet.
clase Concurso<T: Mascota>(var vet: Vet<T>) {
puntuaciones val: MutableMap<T, Int> = mutableMapOf()
diversión addScore(t: T, puntuación: Int = 0) {
if (puntuación >= 0) puntuaciones.put(t, puntuación)
diversión getWinners(): MutableSet<T> {
val ganadores: MutableSet<T> = mutableSetOf()
val puntuación más alta = puntuaciones.valores.max()
para ((t, puntaje) en puntajes) {
if (puntuación == puntuación más alta) ganadores.add(t)
}
ganadores de regreso
Vamos a crear algunos objetos Vet y asignarlos a Concursos.
estas aqui 4 313
Machine Translated by Google
crear algunos veterinarios
Mascotas
Concurso
Crear objetos veterinarios minoristas
Veterinario
Podemos crear objetos Vet de la misma manera que creamos
objetos de Concurso: especificando el tipo de objeto con el que debe
tratar cada objeto Vet. El siguiente código, por ejemplo, crea tres objetos,
uno de tipo Vet<Cat>, Vet<Fish> y Vet<Pet>:
val catVet = Veterinario<Gato>()
val fishVet = Vet<Pez>()
val petVet = Veterinario<Mascota>()
Cada veterinario puede tratar con un tipo específico de mascota.
Vet<Cat>, por ejemplo, puede tratar gatos, mientras que Vet<Pet> puede
tratar a cualquier mascota, incluidos gatos y peces. Un Vet<Cat>, sin
embargo, no puede tratar nada que no sea un Gato, como un Pez:
Tanto un Vet<Cat> como un Vet<Pet> pueden tratar Gatos.
catVet.treat(Gato("Fuzz Lightyear"))
petVet.treat(Gato("Katsu"))
Un Vet<Pet> puede tratar a un Pez.
petVet.treat(Pez("Finny McGraw"))
catVet.treat(Pescado("Finny McGraw"))
Esta línea no se compilará, ya que un Vet<Cat> no puede tratar a un Pez.
Veamos qué sucede cuando intentamos pasar objetos Vet a
Concursos.
Pase un veterinario al constructor del concurso
La clase Concurso tiene un parámetro, un Veterinario, que debe
ser capaz de tratar el tipo de Mascota para el que es el Concurso.
Esto significa que podemos pasar un Vet<Cat> a un Contest<Cat>, y un
Vet<Pet> a un Contest<Pet> así:
val catContest = Concurso<Cat>(catVet)
val petContest = Concurso<Mascota>(petVet)
Pero hay un problema. Un Vet<Pet> puede tratar todos los tipos de
mascotas, incluidos los gatos, pero no podemos asignar un Vet<Pet> a
un Contest<Cat> porque el código no se compilará:
Aunque un Vet<Pet> puede tratar gatos, un
val catContest = Concurso<Gato>(petVet)
Contest<Cat> no aceptará un Vet<Pet>, por lo
que esta línea no se compilará.
Entonces, ¿qué debemos hacer en esta situación?
314 Capítulo 10
Machine Translated by Google
genéricos
Mascotas
Concurso
Usar in para hacer un tipo genérico contravariante minoristas
Veterinario
En nuestro ejemplo, queremos poder pasar Pet<Vet> a Contest<Cat>
en lugar de Pet<Cat>. En otras palabras, queremos poder usar un supertipo
genérico en lugar de un subtipo genérico.
En esta situación, podemos resolver el problema prefijando el tipo genérico Si un tipo genérico
usado por la clase Vet con in. in es el polo opuesto de out.
Mientras que out le permite usar un subtipo genérico en lugar de un supertipo
es contravariante,
(como asignar un Retailer<Cat> a un Retailer<Pet>), in le permite usar un
supertipo genérico en lugar de un subtipo. Así que prefijando el tipo genérico significa que puede
de la clase Vet con esto:
Aquí está el prefijo in.
usar un supertipo en
class Veterinario<en T: Mascota> {
lugar de un subtipo.
regalo divertido (t: T) {
println("Tratar mascota ${t.nombre}") Esto es lo opuesto a
} la covarianza.
}
significa que podemos usar un Vet<Pet> en lugar de un Vet<Cat>. Ahora se
compila el siguiente código: El prefijo in en la clase Vet significa
que ahora podemos usar Vet<Pet>
val catContest = Concurso<Gato>(Veterinario<Mascota>())
en lugar de Vet<Cat>, por lo que este
código ahora se compila.
Cuando anteponemos un tipo genérico con in, decimos que el tipo genérico
es contravariante. En otras palabras, significa que se puede usar un supertipo
en lugar de un subtipo.
En general, un tipo genérico de clase o interfaz puede tener el prefijo in si la
En otras palabras, un tipo genérico
clase tiene funciones que lo usan como un tipo de parámetro. Sin embargo,
que tiene el prefijo "in" solo se puede
no puede usar in si alguna de las funciones de clase lo usa como un tipo de usar en una posición "in", como un
devolución, o si ese tipo lo usa alguna propiedad, independientemente de si valor de parámetro de función. No se
están definidas usando val o var. puede utilizar en posiciones de “fuera”.
¿Debe un Vet<Cat> SIEMPRE aceptar un Vet<Pet>?
Antes de anteponer in a un tipo genérico de clase o interfaz, debe
considerar si desea que el subtipo genérico acepte un supertipo genérico
en cada situación. in le permite, por ejemplo, asignar un objeto Vet<Pet> a
la variable Vet<Cat>, lo que puede no ser algo que siempre quiera que
suceda:
val catVet: Veterinario<Gato> = Veterinario<Mascota>()
Esta línea se compila a medida que la clase Vet usa un prefijo in para T.
La buena noticia es que, en situaciones como esta, puede adaptar las
circunstancias en las que un tipo genérico es contravariante. Veamos cómo.
estas aqui 4 315
Machine Translated by Google
contravarianza local
Mascotas
Concurso
Un tipo genérico puede ser localmente contravariante minoristas
Veterinario
Como ha visto, anteponer un tipo genérico con in como parte de la
declaración de clase o interfaz hace que el tipo genérico sea
contravariante globalmente. Sin embargo, puede restringir este
comportamiento a propiedades o funciones específicas.
Cuando un tipo genérico
Supongamos, por ejemplo, que queremos poder usar una no tiene prefijo de entrada o
referencia Vet<Pet> en lugar de Vet<Cat>, pero solo donde se
pasa a Contest<Cat> en su constructor. Podemos lograr esto salida, decimos que el tipo
eliminando el prefijo in del tipo genérico en la clase Vet y agregándolo es invariante. Un
a la propiedad vet en el constructor Contest.
tipo invariable solo
Aquí está el código para hacer esto:
Eliminar el prefijo in
de la clase Vet...
puede aceptar referencias
class Veterinario<en T: Mascota> {
de ese tipo específico.
regalo divertido (t: T) {
println("Tratar mascota ${t.nombre}")
clase Concurso<T: Mascota>(var vet: Veterinario<in T>) {
...
... y añádelo al constructor Contest en
} su lugar. Esto significa que T es
contravariante, pero solo en el constructor Contest.
Estos cambios significan que aún puede pasar un Vet<Pet> a un
Concurso<Cat> así:
Esta línea se compila, ya que puede
val catContest = Concurso<Gato>(Veterinario<Mascota>()) usar Vet<Pet> en lugar de Vet<Cat> en
el constructor Contest<Cat>.
Sin embargo, el compilador no le permitirá asignar un objeto
Vet<Pet> a una variable Vet<Cat> ya que el tipo genérico de Vet no
es globalmente contravariable:
Esta línea, sin embargo, no se compilará ya que no
val catVet: Veterinario<Gato> = Veterinario<Mascota>() puede usar globalmente Vet<Pet> en lugar de Vet<Cat>.
Ahora que ha aprendido a usar la contravarianza, agreguemos el código
Vet a nuestro proyecto Genéricos.
316 Capítulo 10
Machine Translated by Google
genéricos
Mascotas
Concurso
Actualizar el proyecto Generics minoristas
Veterinario
Actualice su versión de Pets.kt en el proyecto Generics para que coincida con la
nuestra a continuación (nuestros cambios están en negrita):
clase abstracta Mascota (var nombre: Cadena)
clase Gato(nombre: Cadena) : Mascota(nombre)
Genéricos
clase Perro(nombre: Cadena) : Mascota(nombre)
clase Pez (nombre: Cadena) : Mascota (nombre)
origen
Agregue la clase Vet.
clase Veterinario<T: Mascota> {
regalo divertido (t: T) { Mascotas.kt
println("Tratar mascota ${t.nombre}")
}
Agrega un constructor a la clase Contest.
clase Concurso<T: Mascota>(var vet: Veterinario<in T>) {
puntuaciones val: MutableMap<T, Int> = mutableMapOf()
diversión addScore(t: T, puntuación: Int = 0) {
if (puntuación >= 0) puntuaciones.put(t, puntuación)
}
diversión getWinners(): MutableSet<T> {
val ganadores: MutableSet<T> = mutableSetOf()
val puntuación más alta = puntuaciones.valores.max()
para ((t, puntaje) en puntajes) {
if (puntuación == puntuación más alta) ganadores.add(t)
}
ganadores de regreso
}
}
interfaz Minorista<out T> {
venta divertida(): T
clase CatRetailer : Minorista<Cat> {
anular venta divertida(): Gato {
println("Vender gato")
devolver gato("")
} El código continúa en
} la página siguiente.
estas aqui 4 317
Machine Translated by Google
más código
Mascotas
Concurso
El código continuó... minoristas
Veterinario
clase DogRetailer : Minorista<Perro> {
anular venta divertida(): Perro {
println("Vender perro")
volver Perro("")
clase MinoristaPescado : Minorista<Pescado> {
anular la venta divertida (): Pescado {
println("Vender pescado")
Genéricos
devolver pescado("")
}
origen
}
Mascotas.kt
diversión principal(argumentos: Array<String>) {
val catFuzz = Gato("Fuzz Lightyear")
val catKatsu = Gato("Katsu")
val fishFinny = Fish("Finny McGraw")
Crea algunos objetos veterinarios.
val catVet = Veterinario<Gato>()
val fishVet = Vet<Pez>()
val petVet = Veterinario<Mascota>()
Haz que los veterinarios traten a algunas mascotas.
gatoVet.treat(gatoFuzz)
petVet.treat(gatoKatsu)
petVet.treat(fishFinny)
Asigne un Veterinario<Gato> al Concurso<Gato>.
val catContest = Concurso<Cat>(catVet)
concursocat.addScore(catFuzz, 50)
concursocat.addScore(catKatsu, 45)
val topCat = catContest.getWinners().first()
println("El ganador del concurso de gatos es ${topCat.name}")
El código continúa en la
página siguiente.
318 Capítulo 10
Machine Translated by Google
genéricos
Mascotas
Concurso
El código continuó... Asigne un Vet<Pet> al Contest<Pet>.
minoristas
Veterinario
val petContest = Concurso<Mascota>(petVet)
petContest.addScore(catFuzz, 50)
petContest.addScore(fishFinny, 56)
val topPet = petContest.getWinners().first()
Genéricos
println("El ganador del concurso de mascotas es ${topPet.name}")
Asigne un Vet<Pet> origen
val fishContest = Concurso<Pescado>(petVet)
a un Contest<Fish>.
Mascotas.kt
val perroMinorista: Minorista<Perro> = PerroMinorista()
val catRetailer: Minorista<Cat> = CatRetailer()
val petRetailer: Minorista<Mascota> = CatRetailer()
petRetailer.vender()
}
Prueba de conducción
Cuando ejecutamos el código, el siguiente texto se imprime en la ventana de
salida del IDE:
Tratar Pet Fuzz Lightyear
Tratar a la mascota Katsu
Tratar a la mascota Finny McGraw
El ganador del concurso de gatos es Fuzz Lightyear
El ganador del concurso de mascotas es Finny McGraw
vender gato
P: ¿No podría haber convertido la propiedad veterinaria de Contest en una P: El enfoque de Kotlin para los genéricos parece diferente al de Java.
¿Veterinario <Mascota>? ¿Está bien?
R: No. Esto significaría que la propiedad veterinaria solo podría R: Sí, lo es. Con Java, los tipos genéricos siempre son invariables, pero
aceptar un Vet<Pet>. Y aunque podría hacer que la propiedad vet puede usar comodines para sortear algunos de los problemas que esto
sea localmente covariante usando: crea. Kotlin, sin embargo, le brinda un control mucho mayor, ya que puede
hacer que los tipos genéricos sean covariantes, contravariantes o dejarlos
como invariantes.
var vet: Veterinario<fuera Mascota>
significaría que podrías asignar un Veterinario<Pez> a un
Concurso<Gato>, lo cual es poco probable que termine bien.
estas aqui 4 319
Machine Translated by Google
ser el compilador
SER el compilador
Aquí hay cuatro clases e interfaces
que usan genéricos.
Tu trabajo es jugar como si fueras el
Compilador y determinar
si cada uno compilará. Si
no se compila, ¿por qué
no?
A clase A<en T>(t: T) {
fun myFunction(t: T) { }
B clase B<en T>(t: T) {
valor x = t
fun myFunction(t: T) { }
C clase abstracta C<en T> {
fun myFunction(): T { }
D clase E<en T>(t: T) {
var y = t
fun myFunction(t: T) { }
Respuestas en la página 322.
320 Capítulo 10
Machine Translated by Google
genéricos
A continuación se muestra una lista completa de archivos de Kotlin. El código,
sin embargo, no se compilará. ¿Qué líneas no compilarán? ¿Qué cambios
necesita hacer en las definiciones de clase e interfaz para hacerlos?
¿compilar?
Nota: No puede modificar la función principal .
//Tipos de comida
comida de clase abierta
clase VeganFood: Comida()
//Vendedores
Vendedor de interfaz<T>
clase VendedorAlimentos: Vendedor<Alimentos>
class VendedorComidaVegana: Vendedor<ComidaVegana>
//Consumidores
interfaz Consumidor<T>
clase Persona: Consumidor<Comida>
clase Vegano: Consumidor<ComidaVegana>
diversión principal(argumentos: Array<String>) {
var foodSeller: Vendedor<Comida>
VendedorComida = VendedorComida()
vendedor de comida = vendedor de comida vegana ()
var veganFoodConsumer: Consumidor<VeganFood>
ConsumidorComidaVegan = Vegano()
ConsumidorComidaVegan = Persona()
Respuestas en la página 323.
estas aqui 4 321
Machine Translated by Google
ser la solución del compilador
SER la solución del compilador
Aquí hay cuatro clases e
interfaces que usan genéricos.
Tu trabajo es jugar como si
fueras el
Compilador y
determinar si cada
uno compilará. Si
no se compila, ¿por qué no?
A clase A<en T>(t: T) { Este código se compila correctamente porque el tipo T contravariante se
fun myFunction(t: T) { }
puede usar como constructor o como tipo de parámetro de función.
}
Este código no se compilará porque T no se
B clase B<en T>(t: T) {
valor x = t puede usar como el tipo de una propiedad val.
fun myFunction(t: T) { }
C clase abstracta C<en T> { Este código no se compilará porque T no se
fun myFunction(): T { } puede usar como tipo de retorno de una función.
}
Este código no se compilará porque T no se
D clase E<en T>(t: T) {
puede usar como el tipo de una propiedad var.
var y = t
fun myFunction(t: T) { }
322 Capítulo 10
Machine Translated by Google
genéricos
A continuación se muestra una lista completa de archivos de Kotlin. El código,
sin embargo, no se compilará. ¿Qué líneas no compilarán? ¿Qué cambios
necesita hacer en las definiciones de clase e interfaz para hacerlos?
¿compilar?
Nota: No puede modificar la función principal .
//Tipos de comida
comida de clase abierta
clase VeganFood: Comida()
//Vendedores
interfaz Vendedor<out T>
clase VendedorAlimentos: Vendedor<Alimentos>
class VendedorComidaVegana: Vendedor<ComidaVegana>
//Consumidores
interfaz Consumidor<en T>
clase Persona: Consumidor<Comida>
clase Vegano: Consumidor<ComidaVegana>
diversión principal(argumentos: Array<String>) {
var foodSeller: Vendedor<Comida> Esta línea no se compilará, ya que está asignando un
VendedorComida = VendedorComida() Vendedor<Comida vegana> a un Vendedor<Comida>.
Para hacerlo compilar, debemos prefijar T sin en la
vendedor de comida = vendedor de comida vegana ()
interfaz del vendedor.
var veganFoodConsumer: Consumidor<VeganFood>
ConsumidorComidaVegan = Vegano()
ConsumidorComidaVegan = Persona()
}
Esta línea no se compilará, ya que está asignando un
Consumidor<Comida> a un Consumidor<ComidaVegana>. Para
hacerlo compilar, debemos prefijar T con in en la interfaz del Consumidor.
estas aqui 4 323
Machine Translated by Google
caja de herramientas
Tu caja de herramientas de Kotlin
Puede descargar
Tiene el Capítulo 10 en su haber y ahora ha el código completo
agregado genéricos a su caja de herramientas. del capítulo desde
CAPÍTULO
10
https://tinyurl.com/
HFKotlin.
Los genéricos le permiten escribir código coherente Puede definir una función que use un tipo genérico
que es seguro para tipos. Colecciones como MutableList fuera de una declaración de clase o una que use
usan genéricos. un tipo genérico diferente, por ejemplo:
El tipo genérico se define entre paréntesis
diversión <T> listPet(): List<T>{
angulares <>, por ejemplo:
...
}
Concurso de clase<T>
Un tipo genérico es invariable si solo puede aceptar
Puede restringir el tipo genérico a un supertipo
referencias de ese tipo específico. Los tipos genéricos
específico, por ejemplo:
son invariantes por defecto.
Concurso de clase<T: Mascota> Un tipo genérico es covariante si puede usar un
subtipo en lugar de un supertipo. Usted especifica que
Crea una instancia de una clase con un tipo genérico
un tipo es covariante prefijándolo con out.
especificando el tipo "real" entre paréntesis angulares,
por ejemplo: Un tipo genérico es contravariante si puede usar un
supertipo en lugar de un subtipo. Usted especifica que
Concurso<Gato> un tipo es contravariante prefijándolo con in.
Siempre que sea posible, el compilador inferirá
el tipo genérico.
324 Capítulo 10
Machine Translated by Google
11 lambdas y funciones de orden superior
val pastel = cocinar
{ it.pastry()
it.filling() it.bake() }
¿Quiere escribir código que sea aún más potente y flexible?
Si es así, entonces necesita lambdas. Una expresión lambda, o lambda, es un bloque de código
que puede pasar como un objeto. Aquí, descubrirá cómo definir una lambda, asignarla a una
variable y luego ejecutar su código. Aprenderá sobre los tipos de funciones y cómo estos pueden
ayudarlo a escribir funciones de orden superior que usan lambdas para sus parámetros o valores
devueltos. Y en el camino, descubrirá cómo un poco de azúcar sintáctico puede hacer que su vida
de codificación sea más dulce.
este es un nuevo capitulo 325
Machine Translated by Google
introduciendo lambdas
Introduciendo lambdas
A lo largo de este libro, ha visto cómo usar las funciones integradas
de Kotlin y crear las suyas propias. Pero a pesar de que hemos cubierto
mucho terreno, todavía estamos rascando la superficie. Kotlin tiene un
montón de funciones que son incluso más poderosas que las que ya has
encontrado, pero para usarlas, hay una cosa más que debes aprender:
cómo crear y usar expresiones lambda.
Una expresión lambda, o lambda, es un tipo de objeto que contiene un bloque
de código. Puede asignar una lambda a una variable, al igual que cualquier
otro tipo de objeto, o pasar una lambda a una función que luego puede
ejecutar el código que contiene. Esto significa que puede usar lambdas para
pasar un comportamiento específico a una función más generalizada.
Usar lambdas de esta manera es particularmente útil cuando se trata
de colecciones. El paquete de colecciones tiene una función sortBy
incorporada, por ejemplo, que proporciona una implementación genérica
para ordenar MutableList; usted especifica cómo la función debe ordenar
la colección pasándole una lambda que describe los criterios:
Aquí hay una lista mutable de
artículos de comestibles que ÁRBITRO
¿Cómo debo Ordenarlos por
necesitan clasificación. 0 ordenar estos precio unitario.
ÁRBITRO
artículos de comestibles?
ÁRBITRO
1
comestibles
ÁRBITRO
.Ordenar por() λ
valor
2
MutableList<Comestibles> La función sortBy() sabe lambda
cómo ordenar en general...
... y la lambda le dice
qué ordenar
que vamos a hacer
específicamente en esta situación.
Antes de presentarle las funciones integradas que usan lambdas, queremos
que se familiarice con el funcionamiento de las lambdas, por lo que en este
capítulo aprenderá a hacer lo siguiente:
1 Defina una lambda.
Descubrirá cómo se ve una lambda, cómo asignarla a una variable, cuál es
su tipo y cómo invocar el código que contiene.
2 Crea una función de orden superior.
Descubrirá cómo crear una función que tenga un parámetro lambda y cómo
usar una lambda como valor de retorno de una función.
Comencemos examinando cómo se ve una lambda.
326 Capítulo 11
Machine Translated by Google
lambdas y funciones de orden superior
Cómo se ve el código lambda
Vamos a escribir una lambda simple que suma 5 a un valor de
parámetro Int. Así es como se ve la lambda para esto:
Tirante de
apertura de la lambda. Tirante de cierre de la lambda.
{ x: Int > x + 5 }
Los parámetros de la lambda. El cuerpo de la lambda.
Separa los
Aquí, a la lambda se le Aquí, el cuerpo toma x, suma
parámetros 5 y lo devuelve.
debe dar un Int, y el Int se del cuerpo.
llama x.
La lambda comienza y termina con llaves {}. Todas las lambdas se definen
Tomo un parámetro
entre llaves, por lo que no se pueden omitir.
Int llamado x. Sumo 5
Dentro de las llaves, la lambda define un solo parámetro Int llamado x usando a x y devuelvo el
x: Int. Lambdas puede tener parámetros únicos (como es el caso aquí), múltiples resultado.
parámetros o ninguno.
La definición del parámetro va seguida de >. > se usa para separar cualquier
parámetro del cuerpo. Es como decir "¡Oye, parámetros, haz esto!" { x: Int > x + 5 }
Finalmente, el > es seguido por el cuerpo lambda, en este caso, x + 5.
λ
Este es el código que desea que se ejecute cuando se ejecute la lambda. lambda
El cuerpo puede tener varias líneas y la última expresión evaluada en el
cuerpo se usa como valor de retorno de la lambda.
En el ejemplo anterior, la lambda toma el valor de x y devuelve x + 5. Es como
escribir la función: Tomo dos
parámetros Int llamados x e y.
divertido sumarCinco(x: Int) = x + 5
Los sumo y devuelvo el resultado.
excepto que las lambdas no tienen nombre, por lo que son anónimas.
Como mencionamos anteriormente, las lambdas pueden tener múltiples parámetros.
La siguiente lambda, por ejemplo, toma dos parámetros Int, x e y, y devuelve
el resultado de x + y: { x: Int, y: Int > x + y }
{ x: Int, y: Int > x + y } λ
Si la lambda no tiene parámetros, puede omitir >. La siguiente lambda, lambda
por ejemplo, no tiene parámetros y simplemente devuelve la cadena "Pow!":
{ "¡Pum!" } Esta lambda no tiene parámetros, por lo que podemos omitir >.
Ahora que sabe cómo se ve una lambda, veamos cómo asigna una a una
variable.
estas aqui 4 327
Machine Translated by Google
asignando lambdas
Puedes asignar una lambda a una variable
Asignas una lambda a una variable de la misma manera que asignas
cualquier otro tipo de objeto a una variable: definiendo la variable
usando val o var, y luego asignándole la lambda.
{ x: Int > x + 5 }
El siguiente código, por ejemplo, asigna una lambda a una nueva
λ
ÁRBITRO
variable llamada addFive:
val sumarCinco = { x: Int > x + 5 } agregar
lambda
Cinco
Hemos definido la variable addFive usando val, por lo que no se
puede actualizar para contener una lambda diferente. Para actualizar Val Lambda
la variable, debe definirse usando var así:
var sumarCinco = { x: Int > x + 5 } Aquí, podemos asignar una nueva lambda a
sumarCinco = { y: Int > 5 + y } addFive porque hemos definido la variable usando var.
Cuando asigna una lambda a una variable, le está asignando un bloque
de código, no el resultado de la ejecución del código. Para ejecutar el
código en una lambda, debe invocarlo explícitamente.
No te preocupes si
Ejecute el código de un lambda invocándolo
lambda
Invoca una lambda llamando a su función de invocación, las expresiones
pasando los valores para cualquier parámetro. El siguiente código, parecen un poco
por ejemplo, define una variable denominada addInts y le asigna extraño al principio.
una lambda que suma dos parámetros Int. Luego, el código invoca
Tómese su tiempo y trabaje en este
la lambda, le pasa valores de parámetro de 6 y 7, y asigna el resultado
capítulo a un ritmo suave, y estará bien.
a una nueva variable llamada resultado:
val addInts = { x: Int, y: Int > x + y }
val resultado = addInts.invoke(6, 7)
También puede invocar la lambda usando el siguiente atajo:
13
ÁRBITRO
val resultado = addInts(6, 7)
pero con un poco menos de código. Es como decir "ejecutar la val Int
expresión lambda contenida en variables addInts usando valores de
parámetro de 6 y 7".
Vayamos detrás de escena y veamos qué sucede cuando invocas una
lambda.
328 Capítulo 11
Machine Translated by Google
lambdas y funciones de orden superior
¿Qué sucede cuando invocas una lambda?
Cuando ejecutas el código:
val addInts = { x: Int, y: Int > x + y }
val resultado = addInts(6, 7)
Suceden las siguientes cosas:
1 val addInts = { x: Int, y: Int > x + y }
Esto crea una lambda con un valor de { x: Int, y: Int > x + y }.
Se asigna una referencia a la lambda a una nueva variable llamada addInts.
{ x: Int, y: Int > x + y }
λ
ÁRBITRO
agregar
lambda
enteros
Val Lambda
2 val resultado = addInts(6, 7)
Esto invoca la lambda a la que hace referencia addInts, pasándole valores de
6 y 7. El 6 aterriza en el parámetro x de lambda y el 7 aterriza en el parámetro y
de lambda.
{ x: Int, y: Int > x + y }
λ
ÁRBITRO
agregar ÁRBITRO
6
lambda
enteros
X En t
Val Lambda
val Int
7
ÁRBITRO
y En t
val Int
estas aqui 4 329
Machine Translated by Google
que pasa
La historia continúa...
13
val addInts = { x: Int, y: Int > x + y }
El cuerpo lambda se ejecuta y calcula x + y. La lambda crea un objeto Int con
un valor de 13 y devuelve una referencia a él.
Necesito devolver x + y. 6
ÁRBITRO
x es 6 y y es 7, por lo que
devolveré un Int de 13.
X En t
val Int
{ x: Int, y: Int > x + y }
λ ÁRBITRO ÁRBITRO
7
lambda
y En t
13
val Int
En t
4 val resultado = addInts(6, 7)
El valor devuelto por la lambda se asigna a una nueva variable Int llamada
resultado.
{ x: Int, y: Int > x + y }
λ
ÁRBITRO
agregar
lambda
enteros
Val Lambda 13
ÁRBITRO
resultado En t
val Int
Ahora que sabe lo que sucede cuando invoca una
lambda, veamos los tipos de lambda.
330 Capítulo 11
Machine Translated by Google
lambdas y funciones de orden superior
Las expresiones lambda tienen un tipo
Al igual que cualquier otro tipo de objeto, una lambda tiene un tipo.
Sin embargo, la diferencia con el tipo de lambda es que no especifica
El tipo de lambda
un nombre de clase que implementa la lambda. En su lugar, también se conoce
especifica el tipo de parámetros de lambda y el valor de retorno.
como tipo de función.
El tipo de una lambda toma la forma:
(parámetros) > tipo_retorno
Entonces, si tiene una lambda con un solo parámetro Int que
devuelve una cadena como esta:
val msg = { x: Int > "El valor es $x" }
{x: Int >
λ
ÁRBITRO
“El valor es $x” }
su tipo es:
(Int) > Cadena mensaje
(Int) > Cadena
valor agregar: (Int, Int) > Int
agregar
sumar = { x: Int, y: Int > x + y } (Int, Int) > Int
Este tipo tiene dos parámetros Int y un
De manera similar, el siguiente código define una variable valor
valor de retorno Int.
denominada saludo que puede contener una referencia a una (Int, Int) > Int
lambda sin parámetros y un valor de retorno de cadena:
saludo val: () > Cadena
saludo = { "¡Hola!" } { "¡Hola!" }
λ
ÁRBITRO
Declarar la variable. Asignarle un valor.
Especifique su tipo.
estas aqui 4 331
Machine Translated by Google
tipo de inferencia
El compilador puede inferir
tipos de parámetros lambda
Cuando declara explícitamente el tipo de una variable, puede omitir
cualquier declaración de tipo de la lambda que el compilador pueda inferir.
Suponga que tiene el siguiente código, que asigna una lambda a una
Esta lambda suma 5 a un Int llamado x.
variable llamada addFive:
val addFive: (Int) > Int = { x: Int > x + 5 }
El compilador ya sabe por la definición de tipo de addFive que cualquier
lambda asignada a la variable debe tener un parámetro Int. Esto significa que
puede omitir la declaración de tipo Int de la definición del parámetro lambda
porque el compilador puede inferir su tipo:
{ x > x + 5 }
λ
ÁRBITRO
val addFive: (Int) > Int = { x > x + 5 } agregar
Cinco (Int) > Int
El compilador sabe que x debe ser un
Int, por lo que podemos omitir su tipo. valor
(Int) > Int
Puede reemplazar un solo parámetro con él
Si tiene una lambda que tiene un solo parámetro y el compilador puede
inferir su tipo, puede omitir el parámetro y hacer referencia a él en el cuerpo
de lambda usando la palabra clave it.
Para ver cómo funciona esto, suponga, como se indicó anteriormente,
que tiene una lambda asignada a una variable mediante el código:
val addFive: (Int) > Int = { x > x + 5 }
Como la lambda tiene un solo parámetro, x, y el compilador puede inferir que x
es un Int, podemos omitir el parámetro x de la lambda y reemplazarlo con él { eso + 5 }
λ
ÁRBITRO
en el cuerpo de lambda de esta manera:
agregar
val addFive: (Int) > Int = { it + 5 } (Int) > Int
Cinco
En el código anterior, { it + 5 } es equivalente a { x > x + 5 },
valor
pero es mucho más conciso.
(Int) > Int
Tenga en cuenta que solo puede usar la sintaxis it en situaciones
en las que el compilador puede inferir el tipo del parámetro. El siguiente
código, por ejemplo, no se compilará porque el compilador no puede decir
de qué tipo debería ser:
Esto no compilará porque el
val sumaCinco = { it + 5 }
compilador no puede inferir su tipo.
332 Capítulo 11
Machine Translated by Google
lambdas y funciones de orden superior
Use la lambda correcta para el tipo de variable
Como ya sabe, el compilador se preocupa profundamente por el tipo de
variable. Esto se aplica a los tipos lambda, así como a los tipos de objetos
simples, lo que significa que el compilador solo le permitirá asignar una
lambda a una variable que sea compatible con el tipo de esa variable.
Suponga que tiene un cálculo con nombre de variable que puede
ÁRBITRO
contener referencias a lambdas con dos parámetros Int y un valor de retorno
Int como este:
agregar
cálculo de valor: (Int, Int) > Int
valor
Si intenta asignar una lambda al cálculo cuyo tipo no coincide con el
(Int, Int) > Int
de la variable, el compilador se molestará.
El siguiente código, por ejemplo, no se compilará porque la lambda
usa Doubles explícitamente: Esto no se compilará, porque la variable
cálculo = { x: Doble, y: Doble > x + y } de cálculo solo aceptará una lambda con
dos parámetros Int y un tipo de retorno Int.
Use Unit para decir que una lambda no tiene valor de retorno
Si desea especificar que una lambda no tiene valor de retorno, puede
hacerlo declarando que su tipo de retorno es Unidad. La siguiente lambda,
{ println(“¡Hola!”) }
por ejemplo, no tiene valor de retorno e imprime el texto "¡Hola!" cuando se
λ
ÁRBITRO
invoca:
miLambda () > Unidad
val myLambda: () > Unidad = { println("¡Hola!") }
valor
También puede usar Unidad para especificar explícitamente que no
() > Unidad
desea acceder al resultado del cálculo de una lambda. El siguiente código,
por ejemplo, se compilará, pero no podrá acceder al resultado de x + y:
cálculo de valor: (Int, Int) > Unidad = { x, y > x + y }
"¡Pow!" a x?
R: Sí. Cualquier variable puede aceptar una R: ¡ Sí! En el Capítulo 8 lo usamos
A: No. Lo anterior asigna una lambda a x , referencia a cualquier tipo de objeto, incluidas con let. No te lo dijimos en ese momento
las lambdas. porque queríamos que te concentraras en los
y no una cadena. La lambda, sin embargo,
devuelve "¡Pow!" cuando se ejecuta. valores nulos, pero let es en realidad una función
que acepta una lambda como parámetro.
estas aqui 4 333
Machine Translated by Google
prueba de manejo
Crear el proyecto Lambdas
Ahora que ha visto cómo crear lambdas, agreguemos algunas a una nueva
aplicación.
Cree un nuevo proyecto de Kotlin que se dirija a la JVM y asígnele el nombre
"Lambdas". Luego cree un nuevo archivo de Kotlin llamado Lambdas.kt resaltando
la carpeta src , haciendo clic en el menú Archivo y eligiendo Nuevo → Archivo/
clase de Kotlin. Cuando se le solicite, nombre el archivo "Lambdas" y seleccione
Archivo en la opción Tipo.
A continuación, actualice su versión de Lambdas.kt para que coincida con la nuestra a continuación:
diversión principal(argumentos: Array<String>) {
var sumarCinco = { x: Int > x + 5 }
lambdas
println("Pase 6 para agregarCinco: ${agregarCinco(6)}")
origen
val addInts = { x: Int, y: Int > x + y }
val resultado = addInts.invoke(6, 7)
Lambdas.kt
println("Pase 6, 7 a addInts: $resultado")
*
valor intLambda: (Int, Int) > Int = { x, y > x y }
println("Pase 10, 11 a intLambda: ${intLambda(10, 11)}")
val addSeven: (Int) > Int = { it + 7 }
println("Pase 12 para sumarSiete: ${sumarSiete(12)}")
val myLambda: () > Unidad = { println("¡Hola!") }
miLambda()
}
Prueba de conducción
Cuando ejecutamos el código, el siguiente texto se imprime en la ventana de
salida del IDE:
Pase 6 para sumarCinco: 11
Pase 6, 7 a addInts: 13
Pase 10, 11 a intLambda: 110
Pase 12 para sumar Siete: 19
¡Hola!
334 Capítulo 11
Machine Translated by Google
lambdas y funciones de orden superior
A continuación se muestra un breve programa de Kotlin. Falta un
bloque del programa. Su desafío es hacer coincidir el bloque de código
candidato (a la izquierda), con el resultado que vería si se insertara el
bloque. No se usarán todas las líneas de salida y algunas líneas de
salida se pueden usar más de una vez. Dibuje líneas que conecten los
Mezclado bloques de código candidatos con su salida correspondiente.
Mensajes
diversión principal(argumentos: Array<String>) {
valor x = 20
El código de
valor y = 2.3
candidato va aquí.
Relaciona
cada candidato
con uno de los
}
posibles resultados.
Candidatos: Salida posible:
valor lam1 = { x: Int > x } println(lam1(x
+ 6))
22.3
26
val lam2: (Doble) > Doble
lam2 = {(it * 2) + 5} 9.6
println(lam2(y))
8.3
val lam3: (Doble, Doble) > Unidad 1.1513.3
lam3 = { x, y > imprimirln(x + y) } lam3.invocar(y,
y) 9.3
10.013.3
var lam4 = { y: Int > (y/2).toDouble() } imprimir(lam4(x))
4.6
lam4 = { it + 6.3 }
imprimir (lam4 (7))
estas aqui 4 335
Machine Translated by Google
solución de mensajes mixtos
A continuación se muestra un breve programa de Kotlin. Falta un
bloque del programa. Su desafío es hacer coincidir el bloque de código
candidato (a la izquierda), con el resultado que vería si se insertara el
bloque. No se usarán todas las líneas de salida y algunas líneas de
salida se pueden usar más de una vez. Dibuje líneas que conecten los
Mezclado bloques de código candidatos con su salida correspondiente.
Mensajes
Solución
diversión principal(argumentos: Array<String>) {
valor x = 20
valor y = 2.3
El código de
candidato va aquí.
Candidatos: Salida posible:
valor lam1 = { x: Int > x } println(lam1(x
+ 6))
22.3
26
val lam2: (Doble) > Doble
lam2 = {(it * 2) + 5} 9.6
println(lam2(y))
8.3
val lam3: (Doble, Doble) > Unidad 1.1513.3
lam3 = { x, y > imprimirln(x + y) } lam3.invocar(y,
y) 9.3
10.013.3
var lam4 = { y: Int > (y/2).toDouble() } imprimir(lam4(x))
4.6
lam4 = { it + 6.3 }
imprimir (lam4 (7))
336 Capítulo 11
Machine Translated by Google
lambdas y funciones de orden superior
¿Cuál es mi tipo?
?
Aquí hay una lista de definiciones de variables y una lista de lambdas.
¿Qué lambdas se pueden asignar a qué variables? Dibuja líneas que conecten
las lambdas con sus correspondientes variables.
Definiciones de variables: lambda:
var lambda1: (Doble) > Int { it + 7.1 }
var lambda2: (Int) > Doble { (es * 3) 4 }
var lambda3: (Int) > Int { x: Int > x + 56 }
var lambda4: (Doble) > Unidad { println("¡Hola!") }
var lambda5 { x: Doble > x + 75 }
estas aqui 4 337
Machine Translated by Google
¿Cuál es mi tipo de solución?
¿Cuál es mi tipo?
? Solución
Aquí hay una lista de definiciones de variables y una lista de lambdas.
¿Qué lambdas se pueden asignar a qué variables? Dibuja líneas que conecten
las lambdas con sus correspondientes variables.
Definiciones de variables: lambda:
var lambda1: (Doble) > Int { it + 7.1 }
var lambda2: (Int) > Doble { (es * 3) 4 }
var lambda3: (Int) > Int { x: Int > x + 56 }
var lambda4: (Doble) > Unidad { println("¡Hola!") }
var lambda5 { x: Doble > x + 75 }
338 Capítulo 11
Machine Translated by Google
lambdas y funciones de orden superior
Puedes pasar una lambda a una función.
Además de asignar una lambda a una variable, también puede usar uno o
más como parámetros de función. Hacerlo le permite pasar un Una función que
comportamiento específico a una función más generalizada.
utiliza una lambda
Para ver cómo funciona esto, vamos a escribir una función llamada
convert que convierte un Double usando alguna fórmula que se le pasa
a través de una lambda, imprime el resultado y lo devuelve. Esto nos
como parámetro o valor
permitirá, por ejemplo, convertir una temperatura de Centígrados a de retorno se conoce
Fahrenheit, o convertir un peso de kilogramos a libras, dependiendo de la
fórmula que le pasemos en el argumento lambda. como función de orden superior.
Comenzaremos definiendo los parámetros de la función.
Agregue un parámetro lambda a una función
especificando su nombre y tipo
Necesitamos decirle a la función de conversión dos cosas para que
convierta un Double en otro: el Double que queremos convertir y la lambda
que especifica cómo debe convertirse.
Por lo tanto, usaremos dos parámetros para la función de conversión: un
Double y un lambda.
Usted define un parámetro lambda de la misma manera que define
cualquier otro tipo de parámetro de función: especificando el tipo de
parámetro y dándole un nombre. Nombraremos nuestro convertidor lambda,
y como queremos que lambda convierta un Double en un Double, su tipo
debe ser (Double) > Double (un lambda que acepta un parámetro Double y
devuelve un Double).
La definición de la función (excluyendo el cuerpo de la función) se encuentra
a continuación. Como puede ver, especifica dos parámetros, un Double
llamado x y un convertidor con nombre lambda, y devuelve un Double:
Este es el parámetro x, un Doble.
conversión divertida (x: Doble,
convertidor: (Doble) > Doble) : Doble {
Este es un parámetro
lambda llamado convertidor.
La función devuelve un Doble.
Su tipo es (Doble) > Doble.
//Código para convertir el Int
A continuación, escribiremos el código para el cuerpo de la función.
estas aqui 4 339
Machine Translated by Google
función de conversión
Invocar la lambda en el cuerpo de la función.
Queremos que la función de conversión convierta el valor del
parámetro x usando la fórmula que se le pasa a través del
parámetro de conversión (una lambda). Por lo tanto,
invocaremos el convertidor lambda en el cuerpo de la función,
pasándole el valor de x, y luego imprimiremos y devolveremos
el resultado.
Aquí está el código completo para la función de conversión:
conversión divertida (x: Doble,
Invoca el convertidor convertidor: (Doble) > Doble) : Doble {
con nombre lambda y val resultado = convertidor(x)
Imprime el resultado.
asigna su valor devuelto println("$x se convierte en $resultado")
al resultado.
resultado devuelto
Devuelve el resultado.
}
Ahora que hemos escrito la función, intentemos llamarla.
Llame a la función pasándole valores de parámetro
Llamas a una función con un parámetro lambda de la misma
manera que llamas a cualquier otro tipo de función: pasándole
un valor para cada argumento, en este caso, un Double y un
lambda.
Usemos la función de conversión para convertir 20.0 grados
centígrados a Fahrenheit. Para hacer esto, pasaremos valores
de 20.0 y { c: Double > c * 1.8 + 32 } a la función:
convert(20.0, { c: Doble > c * 1.8 + 32 })
Este es el valor que
queremos convertir... ...y esta es la lambda que usaremos para convertirla. Tenga en cuenta
que podríamos usar "it" en lugar de c porque la lambda usa un solo
parámetro cuyo tipo puede inferir el compilador.
Cuando se ejecuta el código anterior, devuelve un valor de
68,0 (el valor de 20,0 grados centígrados cuando se convierte
a Fahrenheit).
Vayamos detrás de escena y analicemos lo que sucede cuando
se ejecuta el código.
340 Capítulo 11
Machine Translated by Google
lambdas y funciones de orden superior
¿Qué sucede cuando llamas a la función?
Lo siguiente sucede cuando llama a la función de conversión
usando el código:
val fahrenheit = convert(20.0, { c: Doble > c * 1.8 + 32 })
1 valor farenheit = convert(20.0, { c: Doble > c * 1.8 + 32 })
Esto crea un objeto Double con un valor de 20.0 y una lambda con un valor de { c: Double >
c * 1.8 + 32 }.
20.0 { c: Doble > c * 1.8 + 32 }
λ
Doble (Doble) > Doble
2 conversión divertida (x: Doble, convertidor: (Doble) > Doble) : Doble {
val resultado = convertidor(x)
println("$x se convierte en $resultado") devolver
resultado
}
El código pasa referencias a los objetos que se crean a la función de conversión.
El Double aterriza en el parámetro x de la función de conversión y el lambda aterriza
en su parámetro de conversión. Luego, el código invoca el convertidor lambda, usando
x como parámetro de lambda.
ÁRBITRO 20.0
Introduciré x en la
fórmula de mi cuerpo.
X
Doble
valor doble
{ c: Doble > c * 1.8 + 32 }
Estos son los parámetros de
ÁRBITRO
λ
las funciones de conversión.
convertidor (Doble) > Doble
val (Doble) > Doble
estas aqui 4 341
Machine Translated by Google
que pasa
La historia continúa...
3 fun convert(x: Doble, convertidor: (Doble) > Doble) : Doble { val resultado =
convertidor(x) println("$x se convierte en $resultado") devolver resultado
El cuerpo de la lambda se ejecuta y su resultado (un Double con un valor de 68,0)
se asigna a una nueva variable denominada resultado. La función imprime los
valores de x y las variables de resultado y devuelve una referencia al objeto de resultado.
X
Doble
valor doble { c: Doble > c * 1.8 + 32 }
ÁRBITRO
λ
convertidor (Doble) > Doble 68.0
ÁRBITRO
val (Doble) > Doble
resultado
Doble
valor doble La función imprime
este valor y devuelve
una referencia a él.
4 valor farenheit = convert(20.0, { c: Doble > c * 1.8 + 32 })
Se crea una nueva variable Fahrenheit. Se le asigna una referencia al objeto
devuelto por la función de conversión.
ÁRBITRO 68.0
farenheit
Doble
valor doble
Ahora que ha visto lo que sucede cuando llama a una
función con un parámetro lambda, veamos algunos
atajos que puede tomar cuando llama a este tipo de función.
342 Capítulo 11
Machine Translated by Google
lambdas y funciones de orden superior
Puedes mover la lambda FUERA de ()...
Hasta ahora, ha visto cómo llamar a una función con un
parámetro lambda pasando argumentos a la función dentro de
los paréntesis de la función. Llamamos a la función de conversión,
por ejemplo, usando el siguiente código:
convert(20.0, { c: Doble > c * 1.8 + 32 })
Si el parámetro final de una función que desea llamar es una lambda,
como es el caso de nuestra función de conversión, puede mover el
argumento lambda fuera de los paréntesis de la llamada a la función. El
siguiente código, por ejemplo, hace lo mismo que el código anterior, pero
hemos movido la lambda fuera de los paréntesis:
convert(20.0) { c: Doble > c * 1.8 + 32 }
La lambda ya no está encerrada por el
paréntesis de cierre de la función.
Aquí está el paréntesis de cierre de la función.
... o eliminar los () por completo
Si tiene una función que tiene solo un parámetro, y ese parámetro
es una lambda, puede omitir los paréntesis por completo cuando
llame a la función.
Para ver cómo funciona esto, suponga que tiene la siguiente
función llamada convertFive que convierte el Int 5 en Double
usando una fórmula de conversión que se le pasa a través de una
lambda. Aquí está el código para la función:
diversión convertFive(convertidor: (Int) > Doble) : Doble {
val resultado = convertidor(5)
println("5 se convierte en $resultado")
resultado devuelto
Como la función convertFive tiene un solo parámetro, una
lambda, puede llamar a la función de esta manera:
convertFive { it * 1.8 + 32 }
Observe que no hay paréntesis en esta
llamada de función. Esto es posible porque
Esto hace lo mismo que: la función tiene un solo parámetro, que es un
lambda.
convertFive() { it * 1.8 + 32 }
pero hemos quitado los paréntesis.
Ahora que ha aprendido a escribir una función que usa un parámetro
lambda, actualicemos el código de nuestro proyecto.
estas aqui 4 343
Machine Translated by Google
código de proyecto
Actualice el proyecto Lambdas
Agregaremos las funciones convert y convertFive a nuestro proyecto Lambdas. Actualice
su versión de Lambdas.kt en el proyecto para que coincida con la nuestra a continuación
(nuestros cambios están en negrita):
conversión divertida (x: Doble,
convertidor: (Doble) > Doble) : Doble {
val resultado = convertidor(x)
println("$x se convierte en $resultado")
resultado devuelto
} Suma estas dos funciones.
diversión convertFive(convertidor: (Int) > Doble) : Doble {
val resultado = convertidor(5)
println("5 se convierte en $resultado")
resultado devuelto
} lambdas
diversión principal(argumentos: Array<String>) { origen
var sumarCinco = { x: Int > x + 5 }
println("Pase 6 para agregarCinco: ${agregarCinco(6)}")
Lambdas.kt
val addInts = { x: Int, y: Int > x + y }
val resultado = addInts.invoke(6, 7)
println("Pase 6, 7 a addInts: $resultado")
Ya no
necesitamos
*
estas líneas, valor intLambda: (Int, Int) > Int = { x, y > x y }
por lo que println("Pase 10, 11 a intLambda: ${intLambda(10, 11)}")
puede eliminarlas.
val addSeven: (Int) > Int = { it + 7 }
println("Pase 12 para sumarSiete: ${sumarSiete(12)}")
val myLambda: () > Unidad = { println("¡Hola!") }
miLambda()
convert(20.0) { it * 1.8 + 32 } Añade estas líneas. Tenga en cuenta que
convertFive { it * 1.8 + 32 } podemos usar "it" porque cada lambda usa un solo
} parámetro cuyo tipo puede inferir el compilador.
Tomemos el código para una prueba de manejo.
344 Capítulo 11
Machine Translated by Google
lambdas y funciones de orden superior
Prueba de conducción
Cuando ejecutamos el código, el siguiente texto se imprime en la ventana
de salida del IDE:
20.0 se convierte a 68.0
5 se convierte a 41.0
Antes de ver qué más puede hacer con las lambdas, pruebe el siguiente
ejercicio.
Formato lambda de cerca
Como dijimos anteriormente en el
capítulo, un cuerpo lambda puede incluir varias
P: Parece que hay bastantes atajos
líneas de código. La siguiente lambda, por
puedes tomar cuando usas lambdas. ¿Realmente
ejemplo, imprime el valor de su parámetro y necesito saber acerca de todos ellos?
luego lo usa en un cálculo:
{ c: Doble > println(c) R: Es útil conocer estos atajos porque
una vez que se acostumbre a ellos, pueden hacer
C * 1.8 + 32 } que su código sea más conciso y legible. La sintaxis
alternativa que está diseñada para hacer que su código
Cuando tiene una lambda cuyo cuerpo sea más fácil de leer a veces se denomina azúcar
tiene varias líneas, la última expresión sintáctico, ya que puede hacer que el lenguaje sea más
evaluada se usa como valor de retorno de la "dulce" para los humanos. Pero incluso si no desea utilizar
los accesos directos que hemos discutido en su propio
lambda. Entonces, en el ejemplo anterior, el
código, vale la pena conocerlos porque puede encontrarlos en código de tercer
valor de retorno se define usando la línea:
C * 1.8 + 32 P: ¿ Por qué las lambdas se llaman lambdas?
Una lambda también se puede
R: Es porque vienen de un área de
formatear para que parezca un bloque de matemáticas e informática llamado Lambda
código, con las llaves que la rodean en Calculus, donde las funciones pequeñas y
líneas diferentes al contenido de la anónimas se representan con la letra griega λ (a lambda).
lambda. El siguiente código usa esta técnica
para pasar el lambda { it * 1.8 + 32 } a la P: ¿ Por qué las lambdas no se denominan funciones?
función convertFive:
R: Una lambda es un tipo de función, pero en la mayoría
convertircinco { lenguajes, las funciones siempre tienen nombres.
es * 1.8 + 32 Como ya has visto, una lambda no necesita tener un nombre.
estas aqui 4 345
Machine Translated by Google
rompecabezas de la piscina
Rompecabezas de piscina
Su trabajo es tomar fragmentos de código del grupo y colocarlos en las líneas en
blanco del código. No puede usar el mismo fragmento de código más de una
vez y no necesitará usar todos los fragmentos de código. Su objetivo es crear
una función nombrada a menos que sea llamada por la función principal a
continuación. La función a menos que tenga dos parámetros, una condición con
nombre booleano y un código con nombre lambda . La función debe
invoque el código lambda cuando la condición sea falsa.
divertido a menos que ( , código: ) {
si ( ) {
diversión principal(argumentos: Array<String>) {
val options = arrayOf("Rojo", "Ambar", "Verde")
var crossWalk = opciones[(Math.random() * opciones.tamaño).toInt()]
if (paso de peatones == "Verde") {
println("¡Camina!")
}
Imprimir "¡Alto!" a menos que CrossWalk == “Verde”.
a menos que (paso de peatones == "Verde") {
println("¡Alto!")
}
Nota: ¡cada cosa del grupo
solo se puede usar una vez!
Vacío
Unidad
: !condición
condición
código
Nulo
condición booleano
() ()
código()
>
Respuestas en la página 360.
346 Capítulo 11
Machine Translated by Google
lambdas y funciones de orden superior
Una función puede devolver una lambda
Además de usar una lambda como parámetro, una
función también puede devolver una especificando el tipo
de lambda como su tipo de retorno. El siguiente código, por
ejemplo, define una función denominada getConversionLambda
que devuelve una lambda de tipo (Doble) > Doble. La
lambda exacta que devuelve la función depende del valor
de la cadena que se le pasa.
Devuelve una lambda cuyo tipo
La función tiene un parámetro, una cadena. es (Doble) > Doble.
divertido getConversionLambda(str: String): (Doble) > Doble {
if (str == "Centígrados a Fahrenheit") {
devolver {es * 1.8 + 32}
λ
} else if (str == "KgsToPounds") { (Doble) > Doble
devolver {es * 2.204623}
} else if (str == "Libras a Toneladas") { La función devuelve una de
volver { it / 2000.0 }
estas lambdas, según el valor
de la cadena que se le pasa.
} demás {
devolverlo }
}
}
Puede invocar la lambda devuelta por una función o usarla
como argumento para otra función. El siguiente código, por
ejemplo, invoca el valor de retorno de getConversionLambda
para obtener el valor de 2,5 kilogramos en libras y lo asigna
a una variable llamada libras:
val libras = getConversionLambda("KgsToPounds"))(2.5)
Esto llama a la función getConversionLambda... ...y esto invoca la lambda
devuelta por la función.
Y el siguiente ejemplo usa getConversionLambda para
obtener una lambda que convierte una temperatura de Aquí, estamos pasando el valor de retorno de
Centígrados a Fahrenheit, y luego la pasa a la función de conversión: getConversionLambda a la función de conversión.
convert(20.0, getConversionLambda("CentigradeToFahrenheit"))
Incluso puede definir una función que reciba y devuelva
una lambda Veremos esto a continuación.
estas aqui 4 347
Machine Translated by Google
función de combinación
Escriba una función
que reciba Y devuelva lambdas
λ λ
Vamos a crear una función llamada combine que toma dos parámetros lambda,
los combina y devuelve el resultado (otro lambda). Si la función recibe lambdas Kgs a Libras Libras a Toneladas estadounidenses
para convertir un valor de kilogramos a libras y convertir un valor de libras a
toneladas, devolverá una lambda que convierte un valor de kilogramos a toneladas Crearemos
estadounidenses. una función combinar()
Entonces podremos usar esta lambda en otro lugar de nuestro código. que combine
dos lambdas
Comenzaremos definiendo los parámetros de la función y el tipo de devolución.
en una sola lambda.
Definir los parámetros y el tipo de retorno
λ
Kg a Toneladas estadounidenses
Todas las lambdas utilizadas por la función de combinación deben convertir un
valor Double en otro valor Double, por lo que cada una tiene un tipo de (Double)
> Double. Por lo tanto, nuestra definición de función debe verse así:
La función de combinación tiene dos
parámetros lambda de tipo (Doble) > Doble.
combinación divertida (lambda1: (Doble) > Doble,
lambda2: (Doble) > Doble): (Doble) > Doble {
//Código para combinar las dos lambdas
La función también devuelve
}
una lambda de este tipo.
A continuación, veamos el cuerpo de la función.
Definir el cuerpo de la función
El cuerpo de la función debe devolver una lambda, y esta lambda debe tener las
siguientes características:
¥ Debe tomar un parámetro, un Doble. Llamaremos a este parámetro x.
¥ El cuerpo de la lambda debe invocar lambda1, pasándole el valor de x.
El resultado de esta invocación debe pasarse a lambda2.
Esto lo podemos lograr usando el siguiente código:
combinación divertida (lambda1: (Doble) > Doble,
lambda2: (Doble) > Doble): (Doble) > Doble {
volver { x: Doble > lambda2(lambda1(x)) }
}
x se pasa a lambda1, que acepta y devuelve un Double.
La lambda devuelta por combine
Luego, el resultado se pasa a lambda2, que también
toma un parámetro Double llamado x.
acepta y devuelve un Double.
Escribamos un código que use la función.
348 Capítulo 11
Machine Translated by Google
lambdas y funciones de orden superior
Cómo usar la función combinar
La función de combinación que acabamos de crear toma dos lambdas
y las combina para formar una tercera. Esto significa que si pasamos la
función una lambda para convertir un valor de kilogramos a libras y otra
para convertir un valor de libras a toneladas estadounidenses, la función
devolverá una lambda que convierte un valor de kilogramos a toneladas
estadounidenses.
Aquí está el código para hacer esto:
//Definir dos lambdas de conversión Estas lambdas convierten un Double
val kgsToPounds = { x: Double > x * 2.204623 } val librasToUSTons = de kilogramos a libras y de libras a
toneladas estadounidenses.
{ x: Double > x / 2000.0 }
//Combina las dos lambdas para crear una nueva Pase las lambdas a la función de
combinación. Esto produce una lambda
val kgsToUSTons = combinar(kgsToLibras, librasToUSTons)
que convierte un Doble de kilogramos a
Toneladas estadounidenses.
//Invoque la lambda kgsToUSTons
val usTons = kgsToUSTons(1000.0) //1.1023115
Invoque la lambda resultante
pasándole un valor de 1000,0.
Esto devuelve 1.1023115.
Vayamos entre bastidores y veamos qué sucede cuando el código
carreras.
Qué sucede cuando se ejecuta el código
1 val kgsToLibras = { x: Doble > x * 2.204623 } val librasToUSTons =
{ x: Doble > x / 2000.0 } val kgsToUSTons = combine(kgsToLibras,
librasToUSTons)
Esto crea dos variables y asigna una lambda a cada una. A continuación, se pasa una
referencia a cada lambda a la función de combinación.
{ x: Doble > x * 2.204623 }
λ
ÁRBITRO
kgsA
Libras (Doble) > Doble { x: Doble > x / 2000.0 }
λ
ÁRBITRO
val (Doble) > Doble
libras a
UStoneladas
(Doble) > Doble
val (Doble) > Doble
estas aqui 4 349
Machine Translated by Google
que pasa
La historia continúa...
2 combinación divertida (lambda1: (Doble) > Doble,
lambda2: (Doble) > Doble): (Doble) > Doble {
volver { x: Doble > lambda2(lambda1(x)) }
}
La lambda kgsToPounds aterriza en el parámetro lambda1 de la función de
combinación, y la lambda librasToUSTons aterriza en su parámetro lambda2.
{ x: Doble > x * 2.204623 }
λ
ÁRBITRO
lambda1 (Doble) > Doble
{ x: Doble > x / 2000.0 }
val (Doble) > Doble
λ
ÁRBITRO
Estos son los parámetros
lambda2 (Doble) > Doble
de la función de combinación.
val (Doble) > Doble
3 combinación divertida (lambda1: (Doble) > Doble,
lambda2: (Doble) > Doble): (Doble) > Doble {
volver { x: Doble > lambda2(lambda1(x)) }
}
se ejecuta lambda1(x). Como el cuerpo de * 2.204623, donde x es un
lambda1 es x Double, esto crea un objeto Double con un valor d*
2.204623.
e x
{ x: Doble > x * 2.204623 }
λ
ÁRBITRO
lambda1 (Doble) > Doble
* 2.204623 x
val (Doble) > Doble
Doble
350 Capítulo 11
Machine Translated by Google
lambdas y funciones de orden superior
La historia continúa...
4 combinación divertida (lambda1: (Doble) > Doble,
lambda2: (Doble) > Doble): (Doble) > Doble {
volver { x: Doble > lambda2(lambda1(x)) }
}
El objeto Double con un valor de x * 2.204623 luego se pasa a lambda2.
Como el cuerpo de lambda2 es x / 2000.0, esto significa que x * 2.204623
se sustituye por x. Esto crea un Doble con un valor de (x * 2.204623) /
2000.0, o x * 0.0011023115.
{ x: Doble > x / 2000.0 }
λ
ÁRBITRO
val (Doble) > Doble
Doble
5 combinación divertida (lambda1: (Doble) > Doble,
lambda2: (Doble) > Doble): (Doble) > Doble { return { x:
Doble > lambda2(lambda1(x)) }
}
Esto crea la referencia lambda { x: Double > x * 0.0011023115 }, y un
a esta lambda devuelta por la función.
{ x: Doble > x / 0.0011023115 }
λ
(Doble) > Doble
estas aqui 4 351
Machine Translated by Google
tipo de alias
La historia continúa...
6 val kgsToUSTons = combinar(kgsToLibras, librastoUSTons) val usTons =
kgsToUSTons(1000.0)
La lambda devuelta por la función de combinación se asigna a una variable denominada
kgsToUSTons. Se invoca con un argumento de 1000,0, que devuelve un valor de 1,1023115.
Esto se asigna a una nueva variable llamada usTons.
{ x: Doble > x / 0.0011023115 }
λ
ÁRBITRO
kgsA
UStoneladas
(Doble) > Doble
val (Doble) > Doble
1.1023115
ÁRBITRO
nosotros Toneladas
Doble
Doble
Puede hacer que el código lambda sea más legible
Estamos casi al final del capítulo, pero antes de continuar, hay una cosa
más que queremos mostrarle: cómo hacer que su código lambda sea más
legible.
Cuando usa tipos de función (el tipo de tipo que se usa para definir una
lambda), puede hacer que su código sea engorroso y menos legible.
La función de combinación, por ejemplo, contiene múltiples referencias al La función de combinación tiene
tipo de función (Doble) > Doble: tres instancias del tipo de
función (Doble) > Doble.
combinación divertida (lambda1: (Doble) > Doble,
lambda2: (Doble) > Doble): (Doble) > Doble {
volver { x: Doble > lambda2(lambda1(x)) }
Sin embargo, puede hacer que su código sea más legible reemplazando el
tipo de función con un alias de tipo. Veamos qué es esto y cómo
usa uno
352 Capítulo 11
Machine Translated by Google
lambdas y funciones de orden superior
Use typealias para proporcionar un nombre
diferente para un tipo existente
Un alias de tipo le permite proporcionar un nombre alternativo para un tipo existente,
que luego puede usar en su código. Esto significa que si su código usa un tipo de
función como (Doble) > Doble, puede definir un alias de tipo que se usa en su lugar, lo
que hace que su código sea más legible.
Defina un alias de tipo utilizando la palabra clave typealias . Así es como, por
λ
ejemplo, lo usa para definir un alias de tipo llamado DoubleConversion que
podemos usar en lugar del tipo de función (Double) > Double:
(Doble) > Doble
Este tipo de alias
typealias DoubleConversion = (Doble) > Doble
significa que podemos usar
DoubleConversion en lugar de
Esto significa que nuestras funciones de conversión y combinación ahora pueden
(Double) > Double.
convertirse en:
conversión divertida (x: Doble,
convertidor: DoubleConversion) : Doble {
λ
val resultado = convertidor(x) Conversión doble
println("$x se convierte en $resultado") Podemos usar el
resultado devuelto alias de tipo
DoubleConversion en las
}
funciones de conversión y
combinación para hacer que el código sea más legible.
combinación divertida (lambda1: DoubleConversion,
lambda2: DoubleConversion): DoubleConversion {
volver { x: Doble > lambda2(lambda1(x)) }
Cada vez que el compilador ve el tipo DoubleConversion, sabe que es un marcador
de posición para el tipo (Double) > Double.
Las funciones de conversión y combinación anteriores hacen lo mismo que antes, pero el
código es más legible.
Puede usar typealias para proporcionar un nombre alternativo para cualquier tipo, no
solo para los tipos de funciones. Puedes, por ejemplo, usar:
typealias DuckArray = Array<Duck>
para que pueda referirse al tipo DuckArray en lugar de Array<Duck>.
Actualicemos el código en nuestro proyecto.
estas aqui 4 353
Machine Translated by Google
código de proyecto
Actualice el proyecto Lambdas
Agregaremos el alias de tipo DoubleConversion y las funciones
getConversionLambda y combine a nuestro proyecto Lambdas, junto con
algún código que las use.
Actualice su versión de Lambdas.kt en el proyecto para que coincida con la
nuestra a continuación (nuestros cambios están en negrita):
Agregue el typealias.
typealias DoubleConversion = (Doble) > Doble
Reemplace el tipo de función con el alias de tipo.
conversión divertida (x: Doble,
convertidor: (Double) > Double DoubleConversion) : Double {
val resultado = convertidor(x)
println("$x se convierte en $resultado")
resultado devuelto
}
Elimina esta función porque ya no la necesitamos.
diversión convertFive(convertidor: (Int) > Doble) : Doble {
val resultado = convertidor(5)
println("5 se convierte en $resultado")
resultado devuelto
}
Agregue la función getConversionLambda.
diversión getConversionLambda(str: String): DoubleConversion {
if (str == "Centígrados a Fahrenheit") {
devolver {es * 1.8 + 32}
} else if (str == "KgsToPounds") {
devolver {es * 2.204623} lambdas
} else if (str == "Libras a Toneladas") {
origen
volver { it / 2000.0 }
} demás {
Lambdas.kt
devolverlo }
}
Agregue la función de combinación.
combinación divertida (lambda1: DoubleConversion,
lambda2: DoubleConversion): DoubleConversion {
volver { x: Doble > lambda2(lambda1(x)) } El código continúa en la
} página siguiente.
354 Capítulo 11
Machine Translated by Google
lambdas y funciones de orden superior
El código continuó...
lambdas
diversión principal(argumentos: Array<String>) {
Eliminar estas líneas.
convert(20.0) { it * 1.8 + 32 }
origen
convertFive { it * 1.8 + 32 }
Lambdas.kt
//Convertir 2.5kg a Libras
println("Convertir 2,5 kg a libras: ${getConversionLambda("KgsToPounds"))(2,5)}")
Utilice getConversionLambda para obtener dos lambdas.
//Definir dos lambdas de conversión
val kgsToPoundsLambda = getConversionLambda("KgsToPounds")
val librasToUSTonsLambda = getConversionLambda("LibrasToUSTons")
Cree una lambda que convierta un Doble de
//Combina las dos lambdas para crear una nueva kilogramos a toneladas estadounidenses.
val kgsToUSTonsLambda = combine(kgsToPoundsLambda, librasToUSTonsLambda)
//Utilice la nueva lambda para convertir 17,4 a toneladas estadounidenses Utilice la lambda para convertir 17,4
valor valor = 17.4 kilogramos a toneladas estadounidenses.
println("$valor kg es ${convert(valor, kgsToUSTonsLambda)} toneladas estadounidenses")
}
Tomemos el código para una prueba de manejo.
P: He oído hablar de la programación funcional. ¿Qué es eso?
Prueba de conducción
R: Las lambdas son una parte importante del funcionamiento
programación. Mientras que la programación no funcional lee la
Cuando ejecutamos el código, el siguiente texto se imprime en
entrada de datos y genera la salida de datos, los programas funcionales
la ventana de salida del IDE:
pueden leer funciones como entrada y generar funciones como salida.
Si su código incluye funciones de orden superior, está haciendo
Convertir 2.5kg a Libras: 5.5115575
programación funcional.
17.4 se convierte a 0.0191802201
17,4 kg son 0,0191802201 toneladas estadounidenses P: ¿ La programación funcional es muy diferente de
¿programación orientada a objetos?
Ahora ha aprendido a usar lambdas para crear funciones de
orden superior. Pruebe los siguientes ejercicios y, en el próximo R: Ambas son formas de factorizar su código. en objeto
capítulo, le presentaremos algunas de las funciones integradas la programación orientada combina datos con funciones, y en
la programación funcional combina funciones con funciones.
de orden superior de Kotlin y le mostraremos cuán poderosas
Los dos estilos de programación no son incompatibles; son
y flexibles pueden ser.
simplemente formas diferentes de ver el mundo.
estas aqui 4 355
Machine Translated by Google
código imanes
Imanes de código
Alguien usó imanes de nevera para crear una función de búsqueda que imprima los
nombres de los artículos en una List<Grocery> que cumplen con algunos criterios.
La función va aquí.
Desafortunadamente, algunos de los imanes se cayeron. Vea si puede reconstruir la función.
clase de datos Supermercado (nombre de valor: cadena, categoría de valor: cadena,
Esta es la tienda de comestibles
clase de datos
unidad val: cadena, unidad valPrecio: doble)
diversión principal(argumentos: Array<String>) {
val comestibles = listaDe(Comestibles("Tomates", "Verduras", "lb", 3.0),
La función
Comestibles ("Hongos", "Verdura", "lb", 4.0),
principal
utiliza la Comestibles ("Bagels", "Panadería", "Paquete", 1.5),
función de Comestibles ("Aceite de oliva", "Despensa", "Botella", 6.0),
búsqueda.
Comestibles ("Helado", "Frozen", "Pack", 3.0))
println("Ingredientes caros:")
buscar (comestibles) {i: Comestibles > i.unitPrice> 5.0}
println("Todas las verduras:")
buscar(comestibles) {i: Comestibles > i.categoría == "Vegetal"}
println("Todos los paquetes:")
buscar(comestibles) {i: Comestibles > i.unidad == "Paquete"}
}
criterio(l) } buscar {
divertido } ) { (
Lista<comestibles> ) Respuestas en la página 358.
si ( criterios: } {
356 Capítulo 11
Machine Translated by Google
lambdas y funciones de orden superior
SER el compilador
Aquí hay cinco funciones. Tu trabajo es jugar
como si fueras el Compilador y determinar si
cada uno compilará. Si no se compila, ¿por
qué no?
A fun myFun1(x: Int = 6, y: (Int) > Int = 7): Int { return y(x)
B fun myFun2(x: Int = 6, y: (Int) > Int = { it }) { return y(x)
C fun myFun3(x: Int = 6, y: (Int) > Int = { x: Int > x + 6 }): Int {
devolver y(x)
}
D fun myFun4(x: Int, y: Int,
z: (Int, Int) > Int = {
x: Int, y: Int > x + y
}) {
z(x, y)
}
mi diversión myFun5(x: (Int) > Int = {
imprimir (es)
es + 7
}) {
x(4)
Respuestas en la página 359.
}
estas aqui 4 357
Machine Translated by Google
solución de imanes
Solución de imanes de código
Alguien usó imanes de nevera para crear una función de búsqueda que imprima los
nombres de los artículos en una List<Grocery> que cumplen con algunos criterios.
Desafortunadamente, algunos de los imanes se cayeron. Vea si puede reconstruir la función.
criterios: (g: Supermercado) > Booleano ) {
para ( yo en la lista ) {
}
}
clase de datos Supermercado (nombre de valor: cadena, categoría de valor: cadena,
unidad val: cadena, unidad valPrecio: doble)
diversión principal(argumentos: Array<String>) {
val comestibles = listaDe(Comestibles("Tomates", "Verduras", "lb", 3.0),
Comestibles ("Hongos", "Verdura", "lb", 4.0),
Comestibles ("Bagels", "Panadería", "Paquete", 1.5),
Comestibles ("Aceite de oliva", "Despensa", "Botella", 6.0),
Comestibles ("Helado", "Frozen", "Pack", 3.0))
println("Ingredientes caros:")
buscar (comestibles) {i: Comestibles > i.unitPrice> 5.0}
println("Todas las verduras:")
buscar(comestibles) {i: Comestibles > i.categoría == "Vegetal"}
println("Todos los paquetes:")
buscar(comestibles) {i: Comestibles > i.unidad == "Paquete"}
}
358 Capítulo 11
Machine Translated by Google
lambdas y funciones de orden superior
SER la solución del compilador
Aquí hay cinco funciones. Tu
trabajo es jugar como si fueras el
Compilador y determinar si cada
uno compilará. Si no
se compila, ¿por qué
no?
A fun myFun1(x: Int = 6, y: (Int) > Int = 7): Int { return y(x) Esto no se compilará, ya que asigna un
valor Int predeterminado de 7 a una lambda.
}
Esto no se compilará porque la
B fun myFun2(x: Int = 6, y: (Int) > Int = { it }) { return y(x)
función devuelve un Int que no
Esta línea devuelve un Int.
} está declarado.
C fun myFun3(x: Int = 6, y: (Int) > Int = { x: Int > x + 6 }): Int {
devolver y(x) Este código compila. Sus parámetros tienen valores predeterminados
}
del tipo correcto y su tipo de retorno está declarado correctamente.
D fun myFun4(x: Int, y: Int,
Este código compila. A la
z: (Int, Int) > Int = {
variable z se le asigna una
x: Int, y: Int > x + y
lambda válida como su valor predeterminado.
}) {
z(x, y)
}
mi diversión myFun5(x: (Int) > Int = { Este código compila. A la
imprimir (es) variable x se le asigna una
es + 7 lambda válida como su valor
}) {
predeterminado, y esta lambda abarca varias líneas.
x(4)
}
estas aqui 4 359
Machine Translated by Google
solución de rompecabezas de piscina
Solución de rompecabezas de piscina
Su trabajo es tomar fragmentos de código del grupo y colocarlos en las líneas en
blanco del código. No puede usar el mismo fragmento de código más de una
vez y no necesitará usar todos los fragmentos de código. Su objetivo es crear
una función nombrada a menos que sea llamada por la función principal a
continuación. La función a menos que tenga dos parámetros, una condición con
nombre booleano y un código con nombre lambda . La función debe
invoque el código lambda cuando la condición sea falsa.
si ( !condición ) {
código()
Si la condición es falsa, invoque el código lambda.
}
diversión principal(argumentos: Array<String>) {
val options = arrayOf("Rojo", "Ambar", "Verde")
var crossWalk = opciones[(Math.random() * opciones.tamaño).toInt()]
if (paso de peatones == "Verde") {
println("¡Camina!")
}
a menos que (paso de peatones == "Verde") {
println("¡Alto!")
} Tiene el formato de un bloque de código, pero en realidad
}
es una lambda. La lambda se pasa a la función a menos
que se ejecute si crossWalk no es "Green".
No necesitabas usar
estos fragmentos.
Vacío
condición
Nulo
360 Capítulo 11
Machine Translated by Google
lambdas y funciones de orden superior
Tu caja de herramientas de Kotlin
CAPÍTULO
11 Tiene el Capítulo 11 en su haber y ahora ha
agregado lambdas y funciones de orden superior
a su caja de herramientas.
Puede descargar
el código
completo del
capítulo desde https://
tinyurl.com/HFKotlin.
Una expresión lambda, o lambda, toma la forma: Ejecutas una lambda invocándola. Esto se hace
pasando a la lambda cualquier parámetro entre
paréntesis o llamando a su función de invocación.
{ x: Int > x + 5 }
Puede pasar una lambda a una función como
La lambda se define entre llaves y puede incluir
parámetro o usar una como valor de retorno de una
parámetros y un cuerpo.
función. Una función que usa una lambda de esta
Una lambda puede tener varias líneas. La última manera se conoce como función de orden superior.
expresión evaluada en el cuerpo se usa como
Si el parámetro final de una función es una
valor de retorno de la lambda.
lambda, puede mover la lambda fuera de los
Puede asignar una lambda a una variable. El tipo paréntesis de la función cuando llame a la función.
de la variable debe ser compatible con el tipo de
la lambda.
Si una función tiene un solo parámetro que es
El tipo de una lambda tiene el formato: una lambda, puede omitir los paréntesis cuando
llame a la función.
(parámetros) > tipo_retorno
Un alias de tipo le permite proporcionar un
nombre alternativo para un tipo existente. Defina
Siempre que sea posible, el compilador puede inferir un alias de tipo utilizando typealias.
los tipos de parámetros de la lambda.
Si la lambda tiene un solo parámetro, puede
reemplazarlo con él.
estas aqui 4 361
Machine Translated by Google
Machine Translated by Google
12 funciones integradas de orden superior
La colección se estaba volviendo
loca, había artículos por todas
partes, así que le di un mapa(), le
di el viejo foldRight() y luego ¡BAM!
Todo lo que quedó fue un Int de 42.
Kotlin tiene una gran cantidad de funciones integradas de orden superior.
Y en este capítulo, le presentaremos algunos de los más útiles. Conocerá a la familia de filtros
flexibles y descubrirá cómo pueden ayudarlo a reducir su colección al tamaño adecuado. Aprenderá
cómo transformar una colección usando el mapa, recorrer sus elementos con forEach y cómo agrupar
los elementos en su colección usando groupBy. Incluso usará fold para realizar cálculos complejos
usando solo una línea de código. Al final del capítulo, podrá escribir código más poderoso de lo que
nunca pensó posible.
este es un nuevo capitulo 363
Machine Translated by Google
clase de comestibles
Kotlin tiene un montón de
funciones integradas de orden superior
Como dijimos al comienzo del capítulo anterior, Kotlin viene con un
montón de funciones integradas de orden superior que toman un
parámetro lambda, muchas de las cuales se ocupan de las colecciones.
Le permiten filtrar una colección en función de algunos criterios, por
ejemplo, o agrupar los elementos de una colección por un valor de
propiedad particular.
Cada función de orden superior tiene una implementación generalizada
y su comportamiento específico está definido por la lambda que le
pasa. Por lo tanto, si desea filtrar una colección con la función de filtro
integrada, puede especificar los criterios que deben usarse pasando a
la función una lambda que la define.
Como muchas de las funciones de orden superior de Kotlin están
diseñadas para funcionar con colecciones, le presentaremos
algunas de las funciones de orden superior más útiles definidas en
el paquete de colecciones de Kotlin . Exploraremos estas funciones
usando una clase de datos Grocery y una Lista de artículos de
Grocery llamados groceries. Aquí está el código para definirlos:
clase de datos Supermercado (nombre de valor: cadena, categoría de valor: cadena,
Esta es la clase de val unidad: Cadena, val unidadPrecio: Doble,
datos Grocery. cantidad de valores: Int)
diversión principal(argumentos: Array<String>) {
val comestibles = listaDe(Comestibles("Tomates", "Verduras", "lb", 3.0, 3),
Comestibles ("Champiñones", "Verduras", "lb", 4.0, 1),
La lista de comestibles contiene
Comestibles ("Bagels", "Panadería", "Paquete", 1.5, 2),
cinco artículos de comestibles.
Supermercado("Aceite de oliva", "Despensa", "Botella", 6.0, 1),
Comestibles ("Helado", "Frozen", "Pack", 3.0, 2))
}
Comenzaremos viendo cómo encontrar el valor más bajo o más alto en
una colección de objetos.
364 Capítulo 12
Machine Translated by Google
funciones integradas de orden superior
Las funciones min y max funcionan con tipos básicos
Como ya sabe, si tiene una colección de tipos básicos, puede
usar las funciones min y max para encontrar el valor más bajo
o más alto. Si desea encontrar el valor más alto en List<Int>,
por ejemplo, puede usar el siguiente código:
valores enteros = listaDe(1, 2, 3, 4)
val maxInt = ints.max() //maxInt == 4
Los números y las cadenas
Las funciones min y max funcionan con los tipos básicos de tienen un orden natural, lo que
Kotlin porque tienen un orden natural. Los Ints se pueden 1, 2, 3, 4, 5... significa que puede usar las
organizar en orden numérico, por ejemplo, lo que facilita saber funciones min y max con ellos
qué Int tiene el valor más alto, y las cadenas se pueden "A B C"... para determinar el valor más bajo
organizar en orden alfabético. o más alto.
Sin embargo, las funciones min y max no se pueden usar
con tipos sin orden natural. No puede usarlos, por ejemplo, con
List<Grocery> o Set<Duck>, ya que las funciones no saben
automáticamente cómo se deben ordenar los artículos de Grocery
o los objetos Duck. Esto significa que para tipos más complejos,
necesita un enfoque diferente.
Las funciones minBy y maxBy funcionan con TODOS los tipos
Estos elementos no tienen un orden natural.
Si desea encontrar el valor más bajo o más alto de un tipo que es
Para encontrar el valor más alto o más bajo,
más complejo, puede usar las funciones minBy y maxBy .
necesitamos especificar algunos criterios,
Estas funciones funcionan de manera similar a min y max, excepto
como el precio unitario o la cantidad.
que puede pasarles criterios. Puede usarlos, por ejemplo, para
encontrar el artículo de comestibles con el precio unitario más bajo
o el pato con el tamaño más grande.
Cada una de las funciones minBy y maxBy toma un parámetro:
una lambda que le dice a la función qué propiedad debe usar
para determinar qué elemento tiene el valor más bajo o más
alto. Si, por ejemplo, quisiera encontrar el artículo en una Lista
<Comestibles> con el precio unitario más alto, podría hacerlo
usando la función maxBy de esta manera: Este código es como decir
"Encuentra el artículo en la tienda de
val precioUnitario más alto = comestibles.maxBy { it.UnitPrice }
comestibles con el precio unitario más alto".
Y si quisiera encontrar el artículo con el valor de cantidad más bajo,
usaría minBy:
Esta línea devuelve una referencia al
val cantidad más baja = comestibles.minBy { it.quantity }
artículo en comestibles con la cantidad
La expresión lambda que pasa a la función minBy o maxBy debe más baja.
tomar una forma específica para que el código se compile y
funcione correctamente. Veremos esto a continuación.
estas aqui 4 365
Machine Translated by Google
minBy y maxBy
Una mirada más cercana al parámetro lambda de minBy y maxBy
Cuando llama a la función minBy o maxBy, debe proporcionarle una lambda que
toma la siguiente forma:
{ i: tipo_elemento > criterios }
La lambda debe tener un parámetro, que hemos indicado anteriormente
usando i: item_type. El tipo del parámetro debe coincidir con el tipo de
minBy y maxBy
elemento que trata la colección, por lo que si desea utilizar cualquiera de las
funciones con List<Grocery>, el parámetro de la lambda debe tener un tipo de funcionan con
Grocery:
{ i: Supermercado > criterios } colecciones que
Como cada lambda tiene un solo parámetro de un tipo conocido, podemos contienen cualquier tipo
omitir la declaración del parámetro por completo y referirnos al parámetro
de objeto, lo que las
en el cuerpo de lambda usándolo.
El cuerpo lambda especifica los criterios que deben usarse para hace mucho más flexibles que min y
determinar el valor más bajo o más alto en la colección.
Este criterio suele ser el nombre de una propiedad, por ejemplo, { it.unitPrice }.
Puede ser de cualquier tipo, siempre y cuando la función pueda usarlo para
determinar qué artículo tiene el precio más bajo o el más bajo. Si llama a minBy o
mayor valor de la propiedad.
maxBy en una colección
¿Qué pasa con el tipo de retorno de minBy y maxBy? que no contiene
Cuando llama a la función minBy o maxBy, su tipo de devolución coincide
elementos, la función
con el tipo de los elementos que se encuentran en la colección. Si usa minBy
con List<Grocery>, por ejemplo, la función devolverá Grocery. Y si usa maxBy
devolverá un valor nulo.
con un Set<Duck>, devolverá un Duck.
Ahora que sabe cómo usar minBy y maxBy, veamos dos de sus parientes
cercanos: sumBy y sumByDouble.
366 Capítulo 12
Machine Translated by Google
funciones integradas de orden superior
Las funciones sumBy y sumByDouble
Como es de esperar, las funciones sumBy y sumByDouble devuelven
una suma de los elementos de una colección de acuerdo con algunos sumBy suma Ints y devuelve
criterios que se le pasan a través de una lambda. Puede usar estas
funciones para, por ejemplo, sumar los valores de cantidad para cada un Int.
artículo en List<Grocery>, o devolver la suma de cada precio de
unidad multiplicada por la cantidad.
Las funciones sumBy y sumByDouble son casi idénticas, excepto que
sumBy funciona con Ints y sumByDouble funciona con Doubles. Para
sumByDouble agrega Doubles
devolver la suma de los valores de cantidad de una tienda de y devuelve un Double.
comestibles, por ejemplo, usaría la función sumBy, ya que la cantidad
es un Int:
Esto devuelve la suma de todos los
val sumQuantity = comestibles.sumBy { it.quantity }
valores de cantidad en comestibles.
Y para devolver la suma de cada precio unitario multiplicado por
el valor de la cantidad, usaría sumByDouble, ya que precio
unitario * cantidad es un doble:
val PrecioTotal = comestibles.sumByDouble { it.quantity * it.unitPrice }
parámetro lambda de sumBy y sumByDouble
Al igual que minBy y maxBy, debe proporcionar sumBy y
sumByDouble con una lambda que tome esta forma:
{ i: tipo_elemento > criterios }
Como antes, item_type debe coincidir con el tipo de elemento que
trata la colección. En los ejemplos anteriores, estamos usando las
funciones con List<Grocery>, por lo que el parámetro de la lambda No puede usar
debe tener un tipo de Grocery. Como el compilador puede inferir esto, sumBy o
podemos omitir la declaración del parámetro lambda y referirnos al sumByDouble
parámetro en el cuerpo de lambda usándolo. directamente en un
El cuerpo lambda le dice a la función lo que quieres que sume. Como mapa.
dijimos anteriormente, debe ser un Int si está usando la función sumBy Sin embargo, puede usarlos en las
y un Double si está usando sumByDouble. sumBy devuelve un valor Int y claves, valores o propiedades de
sumByDouble devuelve un Double. las entradas de un mapa. El
Ahora que sabe cómo usar minBy, maxBy, sumBy y sumByDouble, siguiente código, por ejemplo,
creemos un nuevo proyecto y agreguemos código que use estas devuelve la suma de los valores
funciones. de un mapa:
myMap.values.sumBy { it }
estas aqui 4 367
Machine Translated by Google
crear proyecto
Crear el proyecto de comestibles
Cree un nuevo proyecto de Kotlin que se dirija a la JVM y asígnele el nombre
"Comestibles". Luego cree un nuevo archivo de Kotlin llamado Groceries.kt resaltando
la carpeta src , haciendo clic en el menú Archivo y eligiendo Nuevo → Archivo/clase
de Kotlin. Cuando se le solicite, nombre el archivo "Comestibles" y seleccione Archivo en
la opción Tipo.
A continuación, actualice su versión de Groceries.kt para que coincida con la nuestra a continuación:
clase de datos Supermercado (nombre de valor: cadena, categoría de valor: cadena,
val unidad: Cadena, val unidadPrecio: Doble,
cantidad de valores: Int)
diversión principal(argumentos: Array<String>) {
val comestibles = listaDe(Comestibles("Tomates", "Verduras", "lb", 3.0, 3),
Comestibles ("Champiñones", "Verduras", "lb", 4.0, 1),
Comestibles ("Bagels", "Panadería", "Paquete", 1.5, 2),
Supermercado("Aceite de oliva", "Despensa", "Botella", 6.0, 1),
Comestibles ("Helado", "Frozen", "Pack", 3.0, 2))
val precioUnitario más alto = comestibles.maxBy { it.UnitPrice * 5 }
println("PrecioUnitario mas alto: $PrecioUnitario mas alto") Comestibles
val cantidad más baja = comestibles.minBy { it.quantity }
println("Cantidad más baja: $Cantidad más baja") origen
val sumQuantity = comestibles.sumBy { it.quantity } Comestibles.kt
println("cantidadsuma: $cantidadsuma")
val PrecioTotal = comestibles.sumByDouble { it.quantity * it.unitPrice }
println("PrecioTotal: $PrecioTotal")
}
Prueba de conducción
Cuando ejecutamos el código, el siguiente texto se imprime en la ventana de salida
del IDE:
precioUnitario más alto: Supermercado(nombre=Aceite de oliva, categoría=Despensa, unidad=Botella, PrecioUnidad=6,0,
cantidad=1) Cantidad más baja: Supermercado(nombre=Setas, categoría=Vegetales, unidad=lb, Precio unitario=4,0,
cantidad=1) sumaCantidad : 9 Precio total: 28.0
368 Capítulo 12
Machine Translated by Google
funciones integradas de orden superior
SER el compilador
A continuación se muestra un archivo
fuente completo de Kotlin. Su trabajo es
jugar como si fuera el Compilador y
determinar si el archivo se
compilará. Si no se
compila, ¿por qué no?
¿Cómo lo corregirías?
pizza de clase de datos (nombre de valor: cadena, precio de valor por rebanada: doble, cantidad de valor: int)
diversión principal(argumentos: Array<String>) {
valores enteros = listaDe(1, 2, 3, 4, 5)
val pizzas = listaDe(Pizza("Pollo Soleado", 4.5, 4),
Pizza ("Cabra y nuez", 4.0, 1),
Pizza ("Tropical", 3.0, 2),
Pizza ("El jardín", 3.5, 3))
val minInt = ints.minBy({ it.value })
val minInt2 = ints.minBy({ int: Int > int })
val sumInts = ints.sum()
val sumInts2 = ints.sumBy { it }
val sumInts3 = ints.sumByDouble({ número: Doble > número })
val sumInts4 = ints.sumByDouble { int: Int > int.toDouble() }
val preciobajo = pizzas.min()
val preciobajo2 = pizzas.minBy({ it.pricePerSlice })
valcantidadalta = pizzas.maxBy { p: Pizza > p.cantidad }
valcantidadalta3 = pizzas.maxBy { it.quantity }
val PrecioTotal = pizzas.sumBy { it.pricePerSlice * it.quantity }
val PrecioTotal2 = pizzas.sumByDouble { it.pricePerSlice * it.quantity }
estas aqui 4 369
Machine Translated by Google
ser la solución del compilador
SER la solución del compilador
A continuación se muestra un archivo
fuente completo de Kotlin. Su trabajo es
jugar como si fuera el Compilador y
determinar si el archivo se
compilará. Si no se
compila, ¿por qué no?
¿Cómo lo corregirías?
pizza de clase de datos (nombre de valor: cadena, precio de valor por rebanada: doble, cantidad de valor: int)
diversión principal(argumentos: Array<String>) {
valores enteros = listaDe(1, 2, 3, 4, 5)
val pizzas = listaDe(Pizza("Pollo Soleado", 4.5, 4),
Pizza ("Cabra y nuez", 4.0, 1),
Pizza ("Tropical", 3.0, 2),
Pizza ("El jardín", 3.5, 3)) Como ints es List<Int>, 'it' es Int y
no tiene propiedad de valor.
val minInt = ints.minBy({ it.value })
Esta línea no se compilará, ya que el
val minInt2 = ints.minBy({ int: Int > int })
parámetro lambda debe ser un Int. Podemos
val sumInts = ints.sum()
reemplazar el lambda con { it.toDouble() }.
val sumInts2 = ints.sumBy { it }
val sumInts3 = ints.sumByDouble({ number: Double > number it.toDouble() })
val sumInts4 = ints.sumByDouble { int: Int > int.toDouble() }
La función min no funcionará con List<Pizza>.
val preciobajo = pizzas.min()
val preciobajo2 = pizzas.minBy({ it.pricePerSlice })
valcantidadalta = pizzas.maxBy { p: Pizza > p.cantidad }
valcantidadalta3 = pizzas.maxBy { it.quantity }
val PrecioTotal = pizzas.sumByDouble { it.pricePerSlice * it.quantity }
val PrecioTotal2 = pizzas.sumByDouble { it.pricePerSlice * it.quantity }
}
{ it.pricePerSlice * it.quantity } devuelve un Double, por lo que la función
sumBy no funcionará. Necesitamos usar sumByDouble en su lugar.
370 Capítulo 12
Machine Translated by Google
funciones integradas de orden superior
Conoce la función de filtro
La siguiente parada en nuestro recorrido por las funciones de orden
superior de Kotlin es el filtro. Esta función le permite buscar o filtrar una
colección de acuerdo con algunos criterios que le pasa mediante una lambda.
Para la mayoría de las colecciones, el filtro devuelve una Lista que
incluye todos los elementos que coinciden con sus criterios, que luego
puede usar en otra parte de su código. Sin embargo, si se usa con un
Esto devuelve una Lista que contiene
mapa, devuelve un mapa. El siguiente código, por ejemplo, usa la
los artículos de comestibles cuyo valor
función de filtro para obtener una lista de todos los artículos en
de categoría es "Vegetal".
comestibles cuyo valor de categoría es "Verdura":
val verduras = groceries.filter { it.category == "Vegetal" }
Al igual que las otras funciones que ha visto en este capítulo, la
lambda que pasa a la función de filtro toma un parámetro, cuyo tipo
debe coincidir con el de los elementos de la colección. Como el
parámetro de lambda tiene un tipo conocido, puede omitir la declaración
del parámetro y hacer referencia a él en el cuerpo de lambda usándolo.
El cuerpo de la lambda debe devolver un valor booleano, que se utiliza
para los criterios de la función de filtro. La función devuelve una
referencia a todos los elementos de la colección original donde el
cuerpo lambda se evalúa como verdadero. El siguiente código, por
ejemplo, devuelve una lista de artículos de comestibles cuyo precio
unitario es mayor que 3.0:
val unitPriceOver3 = groceries.filter { it.unitPrice > 3.0 }
Hay toda una FAMILIA de funciones de filtro
Puede obtener más
Kotlin tiene varias variaciones de la función de filtro que a veces pueden familia
información
de filtros
sobre
de la
ser útiles. La función filterTo, por ejemplo, funciona como la función de Kotlin en la
filtro, excepto que agrega los elementos que coinciden con los criterios
documentación en línea:
especificados a otra colección.
La función filterIsInstance devuelve una lista de todos los elementos
que son instancias de una clase dada. Y la función filterNot devuelve
los elementos de una colección que no coinciden con los criterios que le
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin. colecciones/index.html
pasa. Así es como, por ejemplo, usaría la función filterNot para devolver
una lista de todos los artículos de comestibles cuyo valor de categoría no
es "Congelado":
val notFrozen = comestibles.filterNot { it.category == "Frozen" }
Ahora que has visto cómo funciona la función de filtro, veamos otra de filterNot devuelve aquellos elementos en los
las funciones de orden superior de Kotlin: la función de mapa.
que el cuerpo lambda se evalúa como falso.
estas aqui 4 371
Machine Translated by Google
función de mapa
Usa el mapa para
aplicar una transformación a tu colección
La función map toma los elementos de una colección y transforma cada uno de
acuerdo con alguna fórmula que especifique. Devuelve los resultados de esta
¡Sí! La función de mapa devuelve una
transformación como una nueva Lista.
Lista, y no un Mapa.
Para ver cómo funciona esto, suponga que tiene una List<Int> que se ve así:
valores enteros = listaDe(1, 2, 3, 4)
Si desea crear una nueva List<Int> que contenga los mismos elementos
multiplicados por dos, puede hacerlo usando la función de mapa de esta manera:
Esto devuelve una lista que
val doubleInts = ints.map { it * 2 } contiene los elementos 2, 4, 6 y 8.
Y también puede usar el mapa para crear una nueva lista que contenga el nombre
de cada artículo de comestibles en comestibles:
Esto crea una nueva lista y la
val abarrotesNames = abarrotes.map { it.name }
completa con el nombre de cada
artículo de comestibles en comestibles.
En cada caso, la función map devuelve una nueva Lista y deja intacta la
colección original. Si, por ejemplo, usa el mapa para crear una lista de cada
precio unitario multiplicado por 0,5 usando el siguiente código, el precio unitario
de cada artículo de comestibles en la colección original permanece igual:
Esto devuelve una lista
que contiene cada precio de
val medioPrecioUnitario = comestibles.map { it.precioUnitario * 0.5 }
unidad multiplicado por 0,5.
Al igual que antes, la lambda que pasa a la función map tiene un único
parámetro cuyo tipo coincide con el de los elementos de la colección.
Puede usar este parámetro (generalmente referido a su uso) para especificar cómo
desea que se transforme cada elemento de la colección.
Puede encadenar llamadas de función juntas
Como las funciones de filtro y mapa devuelven cada una una colección,
puede encadenar llamadas a funciones de orden superior para realizar
operaciones más complejas de manera concisa. Si desea crear una lista de
cada precio unitario multiplicado por dos, donde el precio unitario original es
mayor que 3,0, puede hacerlo llamando primero a la función de filtro en la
colección original y luego usando el mapa para transformar el resultado:
Esto llama a la función de filtro y
val newPrices = comestibles.filter { it.unitPrice > 3.0 }
luego llama al mapa en la Lista
.map { it.precioUnitario * 2 } resultante.
Vayamos detrás de escena y veamos qué sucede cuando se ejecuta este código.
372 Capítulo 12
Machine Translated by Google
funciones integradas de orden superior
Qué sucede cuando se ejecuta el código
1 val newPrices = groceries.filter { it.unitPrice > 3.0 } .map
{ it.unitPrice * 2 }
La función de filtro se llama en comestibles, una List<Grocery>. Crea una nueva lista que contiene
referencias a aquellos artículos de comestibles cuyo precio unitario es mayor que 3.0.
"Tomates"
La llamada a la función de filtro
3.0
crea una nueva Lista que contiene
ÁRBITRO
0
Esta es la lista de Tienda de comestibles
referencias a los dos elementos con
comestibles original.
“Hongos” 4.0 un precio unitario superior a 3,0.
ÁRBITRO
ÁRBITRO Supermercado
ÁRBITRO
ÁRBITRO
"Bagels" 1.5 0
2
comestibles
Tienda de comestibles ÁRBITRO
ÁRBITRO
valor
3 “Aceite de 1
Lista<comestibles>
oliva” 6.0
Tienda de comestibles Lista<comestibles>
Lista<comestibles>
2 val newPrices = groceries.filter { it.unitPrice > 3.0 } .map
{ it.unitPrice * 2 }
La función de mapa se llama en la nueva Lista. Como la lambda { it.unitPrice * 2 } devuelve un
Double, la función crea una List<Double> que contiene una referencia a cada unitPrice
multiplicada por 2.
“Tomates” 3.0
8.0
ÁRBITRO
0
Tienda de comestibles
ÁRBITRO Doble
“Hongos” 4.0
ÁRBITRO 0
1 12.0
ÁRBITRO
"Bagels" 1.5 1
Doble
2
comestibles
Tienda de comestibles
Lista<Doble>
ÁRBITRO
valor
3 “Aceite de
La llamada a la función map
Lista<comestibles>
oliva” 6.0
crea una nueva Lista que
contiene referencias a dos Dobles.
Tienda de comestibles
Lista<comestibles>
estas aqui 4 373
Machine Translated by Google
que pasa
La historia continúa...
3 val newPrices = groceries.filter { it.unitPrice > 3.0 } .map
{ it.unitPrice * 2 }
Se crea una nueva variable, newPrices, y se le asigna la referencia a List<Double> devuelta
por la función de mapa.
“Tomates” 3.0
ÁRBITRO
8.0
0
Tienda de comestibles
“Hongos” 4.0
ÁRBITRO
ÁRBITRO Doble
ÁRBITRO
ÁRBITRO 1 0
nuevo 12.0
comestibles ÁRBITRO
Supermercado "Bagels" Precios ÁRBITRO
2 1.5
1
valor Doble
valor
Supermercado
Lista<Doble>
Lista<comestibles> ÁRBITRO
3 “Aceite de oliva” Lista<Doble>
6,0
La Lista creada por la
Tienda de comestibles
Lista<comestibles> llamada a la función map se
asigna a la variable newPrices.
Ahora que ha visto lo que sucede cuando se encadenan
funciones de orden superior, echemos un vistazo a nuestra
siguiente función: forEach.
P: Dijiste antes que la función de filtro tiene un P: Para las funciones de orden superior que hemos visto hasta ahora,
número de variaciones, como filterTo y filterNot. ha dicho que el tipo de parámetro de lambda debe coincidir con el de los
elementos de la colección. ¿Cómo se aplica eso?
¿Qué pasa con el mapa? ¿Hay variaciones de esa función también?
R: ¡ Sí! Las variaciones incluyen mapTo (que agrega los resultados R: Uso de genéricos.
de la transformación a una colección existente), mapNotNull (que omite
Como recordará del Capítulo 10, los genéricos le permiten escribir código
cualquier valor nulo) y mapValues (que trabaja con un Map y lo devuelve).
que usa tipos consistentemente. Le impide agregar una referencia de
Puedes encontrar más detalles aquí:
Cabbage a List<Duck>. Las funciones integradas de orden superior de
Kotlin usan genéricos para asegurarse de que solo acepten y devuelvan
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/index.html
valores cuyo tipo sea apropiado para la colección con la que se están
utilizando.
374 Capítulo 12
Machine Translated by Google
funciones integradas de orden superior
forEach funciona como un bucle for
La función forEach funciona de manera similar a un bucle for, ya que le permite Puede usar forEach con
realizar una o más acciones en cada elemento de una colección. Estas acciones
se especifican mediante una lambda.
matrices, listas, conjuntos
Para ver cómo funciona forEach, suponga que desea recorrer cada artículo en la
Lista de compras e imprimir el nombre de cada uno. Así es como podrías hacer y en las propiedades de
esto usando un bucle for:
entradas, claves y
para (artículo en comestibles) {
println(elemento.nombre) valores de un mapa.
}
Y aquí está el código equivalente usando la función forEach:
comestibles.forEach { println(it.name) } Tenga en cuenta que { println(it.name) } es una
lambda que estamos pasando a la función forEach.
Ambos ejemplos de código hacen lo mismo, pero usar forEach es un
El cuerpo lambda puede tener varias líneas.
poco más conciso.
Pero si forEach hace lo mismo que un bucle
for, ¿no me está dando una cosa más para
recordar? ¿ Cuál es el punto de tener otra función?
Como forEach es una función, puede usarla en cadenas de llamadas
de función.
Imagine que desea imprimir el nombre de cada artículo en comestibles cuyo
precio unitario es mayor que 3.0. Para hacer esto usando un bucle for, podrías
usar el código:
para (artículo en comestibles) {
if (artículo.precio unitario > 3.0) println(artículo.nombre)
}
Pero puedes hacer esto de manera más concisa usando:
comestibles.filter { it.unitPrice > 3.0 }
.forEach { println(it.nombre) }
Por lo tanto, forEach le permite encadenar llamadas de función para realizar
tareas poderosas de una manera concisa.
Echemos un vistazo más de cerca a forEach.
estas aqui 4 375
Machine Translated by Google
conseguir el cierre
forEach no tiene valor de retorno
Al igual que las otras funciones que ha visto en este capítulo, la lambda
que pasa a la función forEach tiene un único parámetro cuyo tipo coincide
con el de los elementos de la colección.
Y como este parámetro tiene un tipo conocido, puede omitir la
declaración del parámetro y hacer referencia al parámetro en el cuerpo
lambda usándolo.
Sin embargo, a diferencia de otras funciones, el cuerpo de la lambda tiene
un valor de retorno de unidad. Esto significa que no puede usar forEach
para devolver el resultado de algún cálculo, ya que no podrá acceder a él.
Sin embargo, hay una solución.
Las lambdas tienen acceso a las variables.
Como ya sabes, el cuerpo de un bucle for tiene acceso a las variables
que se han definido fuera del bucle. El siguiente código, por ejemplo,
define una variable de cadena denominada itemNames, que luego se
actualiza en el cuerpo de un bucle for:
var nombres de elementos = ""
para (artículo en comestibles) {
nombres de elementos += "${elemento.nombre} "
Puede actualizar la variable itemNames
} dentro del cuerpo de un bucle for.
println("Nombres de elementos: $ Nombres de elementos")
Cuando pasa una lambda a una función de orden superior como
forEach, la lambda tiene acceso a estas mismas variables, aunque se
hayan definido fuera de la lambda. Esto significa que en lugar de usar el
valor de retorno de la función forEach para obtener el resultado de algún
cálculo, puede actualizar una variable desde el interior del cuerpo lambda.
También puede actualizar la variable
El siguiente código, por ejemplo, es válido: itemNames dentro del cuerpo de la
""
lambda que se pasa a forEach.
var nombres de elementos =
comestibles.forEach({ itemNames += "${it.name} " })
println("Nombres de elementos: $ Nombres de elementos")
Las variables definidas fuera de lambda a las que puede acceder lambda El cierre significa que
a veces se denominan cierre de lambda. En palabras ingeniosas, decimos que
la lambda puede acceder a su cierre. Y como la lambda usa la variable una lambda puede
itemNames en su cuerpo, decimos que el cierre de la lambda ha capturado la
variable. acceder a cualquier
Ahora que ha aprendido a usar la función forEach, actualicemos el código
de nuestro proyecto.
variable local que capture.
376 Capítulo 12
Machine Translated by Google
funciones integradas de orden superior
Actualizar el proyecto de comestibles
Agregaremos algo de código a nuestro proyecto Groceries que usa las funciones
filter, map y forEach. Actualice su versión de Groceries.kt en el proyecto para
que coincida con la nuestra a continuación (nuestros cambios están en negrita):
clase de datos Supermercado (nombre de valor: cadena, categoría de valor: cadena,
val unidad: Cadena, val unidadPrecio: Doble,
cantidad de valores: Int)
diversión principal(argumentos: Array<String>) {
val comestibles = listOf(Supermercado("Tomates", "Vegetales", "lb", 3.0, 3), Supermercado("Setas",
"Vegetales", "lb", 4.0, 1), Supermercado("Bagels", " Panadería", "Paquete",
1.5, 2),
Supermercado("Aceite de oliva", "Despensa", "Botella", 6.0, 1),
Comestibles ("Helado", "Frozen", "Pack", 3.0, 2))
val precioUnitario más alto = comestibles.maxBy { it.UnitPrice * 5 }
println("PrecioUnitario mas alto: $PrecioUnitario mas alto") Comestibles
val cantidad más baja = comestibles.minBy { it.quantity }
Borra println("Cantidad más baja: $Cantidad más baja") origen
estas
líneas.
val sumaCantidad = comestibles.sumBy { it.cantidad } println("sumaCantidad: Comestibles.kt
$sumaCantidad")
val PrecioTotal = comestibles.sumByDouble { it.quantity * it.unitPrice }
println("PrecioTotal: $PrecioTotal")
Añade todas estas líneas.
val verduras = groceries.filter { it.category == "Vegetal" }
println("verduras: $verduras")
val noFrozen = comestibles.filterNot { it.category == "Frozen" } println("notFrozen: $notFrozen")
val nombres de comestibles = comestibles.map { it.name } println
("nombres de comestibles: $ nombres de comestibles") val
halfUnitPrice = comestibles.map { it.unitPrice * 0.5 } println ("mitad de precio unitario: $
mitad de precio unitario")
val newPrices = comestibles.filter { it.unitPrice > 3.0 }
.map { it.precioUnitario * 2 } El código continúa en la
println("nuevosPrecios: $nuevosPrecios") página siguiente.
estas aqui 4 377
Machine Translated by Google
prueba de manejo
El código continuó... Agregue estas líneas a la función principal.
println("Nombres de las tiendas de comestibles: ")
comestibles.forEach { println(it.name) }
println("Comestibles con precio unitario > 3.0: ")
comestibles.filter { it.unitPrice > 3.0 }
Comestibles
.forEach { println(it.nombre) }
var nombres de elementos = ""
origen
comestibles.forEach({ itemNames += "${it.name} " }) println("itemNames: $itemNames")
Comestibles.kt
Tomemos el código para una prueba de manejo.
Prueba de conducción
Cuando ejecutamos el código, el siguiente texto se imprime en la ventana de salida del IDE:
verduras: [Supermercado (nombre=Tomates, categoría=Verdura, unidad=lb, precio unitario=3,0, cantidad=3),
Supermercado(nombre=champiñones, categoría=Verdura, unidad=lb, precio unitario=4,0, cantidad=1)] no congeladas :
[Supermercado(nombre=Tomates, categoría=Vegetales, unidad=lb, precio unitario=3.0, cantidad=3), Supermercado(nombre=Setas,
categoría=Verduras, unidad=lb, precio unitario=4.0, cantidad=1), Supermercado( name=Bagels, categoría=Panadería,
unidad=Paquete, precio unitario=1.5, cantidad=2), Supermercado(nombre=Aceite de oliva, categoría=Despensa, unidad=Botella,
Precio unitario=6.0, cantidad=1)] Champiñones, Bagels, Aceite de oliva, Helado] HalfUnitPrice: [1.5, 2.0, 0.75, 3.0, 1.5] newPrices:
[8.0, 12.0]
Nombres de comestibles:
Tomates
Hongos
Bagels
Aceite de oliva
Helado
Comestibles con precio unitario > 3.0:
Hongos
Aceite de oliva
Nombres de los artículos: Tomates Champiñones Bagels Aceite de oliva Helado
Ahora que ha actualizado el código de su proyecto, realice el siguiente ejercicio y luego veremos
nuestra próxima función de orden superior.
378 Capítulo 12
Machine Translated by Google
funciones integradas de orden superior
Rompecabezas de piscina
Su trabajo es tomar fragmentos de código del
clase abstracta Mascota (var nombre: Cadena)
grupo y colocarlos en las líneas en blanco
del código. No puede usar el mismo
clase Gato(nombre: Cadena) : Mascota(nombre) fragmento de código más de una vez y no
necesitará usar todos los fragmentos de
código. Su objetivo es completar la
clase Perro(nombre: Cadena) : Mascota(nombre) función getWinners
en la clase Contest para que vuelva
un Set<T> de concursantes con la puntuación más
clase Pez (nombre: Cadena) : Mascota (nombre)
alta e imprime el nombre de cada ganador.
clase Concurso<T: Mascota>() {
var puntuaciones: MutableMap<T, Int> = mutableMapOf()
diversión addScore(t: T, puntuación: Int = 0) {
Si este código le resulta familiar,
if (puntuación >= 0) puntuaciones.put(t, puntuación)
es porque escribimos una
} versión diferente en el Capítulo 10.
diversión obtenerGanadores(): Establecer<T> {
val puntuación más alta =
val ganadores = puntajes { == puntuación más alta }
Nota: ¡cada cosa del grupo
solo se puede usar una vez!
puntuaciones
mapa
valores filtrar
llaves
máx()
valores para cada
. . .
nombre
valor maxBy()
. . . .
él él
estas aqui 4 379
Machine Translated by Google
solución de rompecabezas de piscina
Solución de rompecabezas de piscina
Su trabajo es tomar fragmentos de código del grupo y
clase abstracta Mascota (var nombre: Cadena)
colocarlos en las líneas en blanco del código. No
puede usar el mismo fragmento de código más
clase Gato(nombre: Cadena) : Mascota(nombre) de una vez y no necesitará usar todos los
fragmentos de código. Su objetivo es completar
la función getWinners
clase Perro(nombre: Cadena) : Mascota(nombre)
en la clase Contest para que devuelva un Set<T>
de concursantes con la puntuación más alta e imprima el
clase Pez (nombre: Cadena) : Mascota (nombre)
nombre de cada ganador.
clase Concurso<T: Mascota>() {
var puntuaciones: MutableMap<T, Int> = mutableMapOf()
diversión addScore(t: T, puntuación: Int = 0) {
if (puntuación >= 0) puntuaciones.put(t, puntuación)
} Las puntuaciones se mantienen como valores Int en un MutableMap
llamado puntuaciones, por lo que obtiene el valor de puntuación más alto.
diversión obtenerGanadores(): Establecer<T> { Filtre las puntuaciones para
obtener las entradas cuyo
val puntuación más alta = puntuaciones.valores.max()
valor sea HighScore.
val ganadores = puntajes .filtrar { es.valor == puntuación más alta } .keys
Luego use su propiedad
ganadores .para cada { println("Ganador: ${ }") } it.name
de claves para obtener
ganadores de regreso los ganadores.
Utilice la función forEach para imprimir
} el nombre de cada ganador.
No necesitabas usar estos
fragmentos.
mapa
valores
maxBy()
380 Capítulo 12
Machine Translated by Google
funciones integradas de orden superior
Use groupBy para dividir su colección en grupos
La siguiente función que veremos es groupBy. Esta función te permite
Tenga en cuenta que no puede usar groupBy en
agrupar los elementos de tu colección según algunos criterios, como el
un mapa directamente, pero puede llamarlo en sus
valor de una de sus propiedades. Puede usarlo (junto con otras llamadas
claves, valores o propiedades de entrada.
a funciones) para, por ejemplo, imprimir el nombre de los artículos de
Supermercado agrupados por valor de categoría:
Verdura
Tomates
Hongos
Estos son los
Panadería La tienda de comestibles
valores de categoría.
Bagels Los Nombres son
Despensa agrupados por
Aceite de oliva
valor de categoría.
Congelado
Helado
La función groupBy acepta un parámetro, un lambda, que se usa para
especificar cómo la función debe agrupar los elementos de la colección.
El siguiente código, por ejemplo, agrupa los artículos en comestibles (una
Lista<Comestibles>) por el valor de categoría:
Esto es como decir "agrupar cada
val groupByCategory = comestibles.groupBy { it.category } artículo en comestibles por su
valor de categoría".
groupBy devuelve un mapa. Utiliza los criterios pasados a través
del cuerpo lambda para las claves, y cada valor asociado es una lista de
elementos de la colección original. El código anterior, por ejemplo, crea un
Mapa cuyas claves son los valores de la categoría de artículos de
Supermercado, y cada valor es una List<Supermercado>:
Bagels Aceituna
Aceite
Tomates
Tienda de comestibles Tienda de comestibles
Tienda de comestibles
ÁRBITRO
Hielo
0 Hongos
ÁRBITRO ÁRBITRO Crema
0 0
Tienda de comestibles
ÁRBITRO
Tienda de comestibles
1
Lista<comestibles> Lista<comestibles>
ÁRBITRO
0
Lista<comestibles>
ÁRBITRO ÁRBITRO ÁRBITRO ÁRBITRO
Lista<comestibles>
Cada valor en el
Mapa es una
Los valores de categoría se
Lista<Comestibles>. "Verdura" "Panadería" "Despensa" "Congelado"
utilizan para las claves del mapa,
por lo que cada clave es una cadena.
Mapa<Cadena, Lista<Comestibles>>
estas aqui 4 381
Machine Translated by Google
cadenas de llamadas
Puede usar groupBy en cadenas de llamadas de función
Como la función groupBy devuelve un mapa con valores de lista, puede realizar
más llamadas a funciones de orden superior en su valor devuelto, tal como
puede hacerlo con las funciones de filtro y mapa.
Imagine que desea imprimir el valor de cada categoría para List<Grocery>,
junto con el nombre de cada artículo de Grocery cuya propiedad de categoría
tiene ese valor. Para hacer esto, puede usar la función groupBy para agrupar
los artículos de Supermercado por cada valor de categoría y luego usar la
función forEach para recorrer el mapa resultante:
groupBy devuelve un Map, lo que significa
que podemos llamar a la función forEach en su
comestibles.groupBy { it.category }.forEach {
valor de retorno.
//Más código va aquí
Como la función groupBy usa los valores de la categoría Grocery para sus
claves, podemos imprimirlos pasando el código println(it.key) a la función
forEach en su lambda:
comestibles.groupBy { it.category }.forEach {
println(it.clave)
Esto imprime las claves del mapa (los
//Más código va aquí valores de la categoría de comestibles).
Y como cada uno de los valores del Mapa es una List<Grocery>, podemos
hacer una llamada adicional a forEach para imprimir el nombre de cada
artículo de la tienda:
comestibles.groupBy { it.category }.forEach {
println(it.clave) Esta línea obtiene el valor
correspondiente a la clave del Mapa.
it.value.forEach { println(" ${it.nombre}") }
Como se trata de una List<Grocery>,
} podemos llamar a forEach para
Entonces, cuando ejecuta el código anterior, produce el siguiente resultado: imprimir el nombre del artículo de Grocery.
Verdura
Tomates
Hongos
Panadería
Bagels
Despensa
Aceite de oliva
Congelado
Helado
Ahora que sabe cómo usar groupBy, veamos la función final de nuestro
viaje por carretera: la función de plegado.
382 Capítulo 12
Machine Translated by Google
funciones integradas de orden superior
Cómo utilizar la función de plegado
Podría decirse que la función de pliegue es la función de orden superior más flexible fold se puede llamar en
de Kotlin. Con fold, puede especificar un valor inicial y realizar alguna operación en
él para cada elemento de una colección. Puede usarlo para, por ejemplo, multiplicar todos las claves, valores y
los elementos de List<Int> y devolver el resultado, o concatenar el nombre de cada
elemento de List<Grocery>, todo en una sola línea de código.
propiedades de las
entradas de un mapa,
A diferencia de las otras funciones que hemos visto en este capítulo, fold toma dos
parámetros: el valor inicial y la operación que desea realizar en él, especificada por una
pero no en un mapa directamente.
lambda. Entonces, si tiene la siguiente List<Int>:
valores enteros = listaDe(1, 2, 3)
puede usar fold para agregar cada uno de sus elementos a un valor inicial de 0 usando el
siguiente código:
val sumOfInts = ints.fold(0) { sumaAcumulativa, elemento > sumaAcumulativa + elemento}
Este es el valor inicial.
Esto le dice a la función que desea
El primer parámetro de la función de plegado es el valor inicial, en este caso, 0.
agregar el valor de cada elemento de la
Este parámetro puede ser de cualquier tipo, pero generalmente es uno de los tipos
colección al valor inicial.
básicos de Kotlin, como un número o una cadena.
El segundo parámetro es una lambda que describe la operación que desea realizar en el
valor inicial de cada elemento de la colección.
En el ejemplo anterior, queremos agregar cada elemento al valor inicial, por lo que
estamos usando la lambda: Aquí, hemos decidido nombrar los parámetros lambda
runningSum y item ya que estamos sumando el valor de cada
{ suma en ejecución, elemento > suma en ejecución + elemento }
elemento a una suma acumulada. Sin embargo, puede dar a
los parámetros cualquier nombre de variable válido.
La lambda que pasa para doblar tiene dos parámetros, que en este ejemplo hemos
llamado runningSum y item.
El primer parámetro lambda, runningSum, obtiene su tipo del valor inicial que especifique.
Se inicializa con este valor inicial, por lo que en el ejemplo anterior, runningSum es un Int
que se inicializa con 0.
El segundo parámetro lambda, item, tiene el mismo tipo que los elementos de la
colección. En el ejemplo anterior, llamamos a fold en List<Int>, por lo que el tipo de
elemento es Int.
El cuerpo de lambda especifica la operación que desea realizar para cada elemento
de la colección, cuyo resultado se asigna luego a la primera variable de parámetro de
lambda. En el ejemplo anterior, la función toma el valor de runningSum, lo suma al valor
del elemento actual y asigna este nuevo valor a runningSum. Cuando la función ha
recorrido todos los elementos de la colección, fold devuelve el valor final de esta variable.
Analicemos lo que sucede cuando llamamos a la función de plegado.
estas aqui 4 383
Machine Translated by Google
que pasa
Detrás de escena: la función de plegado
Esto es lo que sucede cuando ejecutamos el código:
val sumOfInts = ints.fold(0) { sumaAcumulativa, elemento > sumaAcumulativa + elemento}
donde ints se define como:
valores enteros = listaDe(1, 2, 3)
1 val sumOfInts = ints.fold(0) { sumaAcumulativa, elemento > sumaAcumulativa + elemento}
Esto crea una variable Int llamada runningSum que se inicializa con 0. Esta variable es
local para la función de plegado.
0
Este es el valor inicial que le hemos pasado a la
ÁRBITRO
función de plegado. Se asigna a una variable local
llamada runningSum.
En t
correr
Suma
var Int
2 val sumOfInts = ints.fold(0) { sumaAcumulativa, elemento > sumaAcumulativa + elemento }
La función toma el valor del primer elemento de la colección (un Int con un valor de 1)
y lo agrega al valor de runningSum. Este nuevo valor, 1, se asigna a runningSum.
1
La función
ÁRBITRO
de plegado
0 1
comienza En t
con el ÁRBITRO
ÁRBITRO 2
primer
En t
elemento de 1 correr
Suma El valor del primer elemento
la colección. En t
ÁRBITRO
se suma al valor de
3 var Int
2 runningSum.
Luego, este valor se
En t asigna a runningSum.
lista<int>
384 Capítulo 12
Machine Translated by Google
funciones integradas de orden superior
La historia continúa...
3 val sumOfInts = ints.fold(0) { sumaAcumulativa, elemento > sumaAcumulativa + elemento }
La función se mueve al segundo elemento de la colección, que es un Int con un valor de 2.
Lo agrega a runningSum, de modo que el valor de runningSum se convierte en 3.
1
ÁRBITRO
La función 0 3
de plegado En t
ÁRBITRO
se mueve ÁRBITRO 2
al segundo En t
1 correr
elemento de Suma La función suma el valor del
la colección. En t
ÁRBITRO
segundo elemento al valor de
3 var Int
2 runningSum.
Este nuevo valor se asigna a
En t runningSum.
lista<int>
4 val sumOfInts = ints.fold(0) { sumaAcumulativa, elemento > sumaAcumulativa + elemento }
La función pasa al tercer y último elemento de la colección: un Int con un valor de 3. Este
valor se agrega a runningSum, de modo que el valor de runningSum se convierte en 6.
1
ÁRBITRO
0 6
En t
ÁRBITRO
Finalmente, ÁRBITRO 2
la función En t
1 correr
de plegado Suma
En t La función agrega el
se mueve
valor del elemento final al
ÁRBITRO
al tercer 3 var Int
2 valor de runningSum. El
elemento de la colección.
nuevo valor de runningSum
En t ahora es 6
lista<int>
5 val sumOfInts = ints.fold(0) { sumaAcumulativa, elemento > sumaAcumulativa + elemento}
Como no hay más elementos en la colección, la función devuelve el valor final de
runningSum. Este valor se asigna a una nueva variable llamada sumOfInts.
ÁRBITRO
6 El valor final de runningSum es 6, por
suma lo que la función devuelve este valor.
OfInts Se asigna a sumOfInts.
En t
var Int
estas aqui 4 385
Machine Translated by Google
función de plegado
Algunos ejemplos más de pliegue
Ahora que ha visto cómo usar la función de plegado para
sumar los valores en List<Int>, veamos algunos ejemplos más.
Encuentra el producto de una List<Int>
Si desea multiplicar todos los números en List<Int> y devolver el
resultado, puede hacerlo pasando a la función de pliegue un valor
inicial de 1 y una lambda cuyo cuerpo realiza la multiplicación:
ints.fold(1) { producto en ejecución, elemento > producto en ejecución * elemento }
Multiplique runningSum por el valor de cada elemento.
Inicialice runningProduct con 1.
Concatenar el nombre de cada artículo en una Lista <Comestibles>
Para devolver un String que contiene el nombre de cada artículo
También hay una función joinToString que
de Grocery en List<Grocery>, puede pasar a la función de plegado
puede usar para realizar este tipo de tareas.
un valor inicial de "" y una lambda cuyo cuerpo realiza la
concatenación:
comestibles.fold("") { cadena, artículo > cadena + " ${elemento.nombre}" }
Inicialice la cadena con "". Esto es como decir:
cadena = cadena + p“ ara
${item.name}”
cada artículo
en comestibles.
Reste el precio total de los artículos de un valor inicial
También puede usar fold para calcular cuánto cambio le quedaría si
comprara todos los artículos en List<Grocery>. Para hacer esto,
establecería el valor inicial como la cantidad de dinero que tiene
disponible y usaría el cuerpo lambda para restar el precio unitario de
cada artículo multiplicado por la cantidad:
comestibles.fold(50.0) { cambio, artículo
> cambiar item.unitPrice * item.quantity }
Inicialice el cambio con 50.0.
Esto resta el precio total (precio
unitario * cantidad) del cambio de
Ahora que sabe cómo usar las funciones groupBy y fold, cada artículo en comestibles.
actualicemos el código de nuestro proyecto.
386 Capítulo 12
Machine Translated by Google
funciones integradas de orden superior
Actualizar el proyecto de comestibles
Agregaremos algo de código a nuestro proyecto Groceries que usa las funciones
groupBy y fold. Actualice su versión de Groceries.kt en el proyecto para que coincida
con la nuestra a continuación (nuestros cambios están en negrita):
clase de datos Supermercado (nombre de valor: cadena, categoría de valor: cadena,
val unit: Cadena, val unitPrice: Doble, val cantidad: Int)
diversión principal(argumentos: Array<String>) {
val comestibles = listaDe(Comestibles("Tomates", "Verduras", "lb", 3.0, 3),
Comestibles ("Champiñones", "Verduras", "lb", 4,0, 1), Comestibles
("Bagels", "Panadería", "Paquete", 1,5, 2),
Supermercado("Aceite de oliva", "Despensa", "Botella", 6.0, 1),
Comestibles ("Helado", "Frozen", "Pack", 3.0, 2))
val verduras = comestibles.filter { it.category == "Verdura" } println("verduras: $verduras")
val notFrozen = comestibles.filterNot { it.category == "Frozen" }
println("nocongelado: $nocongelado")
val nombresdecomestibles = comestibles.map { it.name }
println("nombresdecomestibles: $nombresdecomestibles")
val medioPrecioUnitario = comestibles.map { it.precioUnitario * 0.5 }
Nosotros no println("mitadPrecioUnitario: $mitadPrecioUnitario")
Ya no necesita
estas líneas, por
val newPrices = comestibles.filter { it.unitPrice > 3.0 }
lo que puede .map { it.precioUnitario * 2 }
eliminarlas.
println("nuevosPrecios: $nuevosPrecios")
Comestibles
println("Nombres de las tiendas de comestibles: ")
comestibles.forEach { println(it.name) }
origen
println("Comestibles con precio unitario > 3.0: ")
Comestibles.kt
comestibles.filter { it.unitPrice > 3.0 }
.forEach { println(it.nombre) }
var nombres de elementos = ""
comestibles.forEach({ itemNames += "${it.name} " }) El código continúa en
println("Nombres de elementos: $ Nombres de elementos") la página siguiente.
estas aqui 4 387
Machine Translated by Google
prueba de manejo
El código continuó...
Comestibles
comestibles.groupBy { it.category }.forEach {
println(it.clave)
origen
it.value.forEach { println(" ${it.nombre}") }
}
Comestibles.kt
valores enteros = listaDe(1, 2, 3)
val sumOfInts = ints.fold(0) { sumaAcumulativa, elemento > sumaAcumulativa + elemento } println("sumaOfInts:
$sumOfInts")
Agregue
estas
val productOfInts = ints.fold(1) { productoenejecución, elemento > productoenejecución * elemento } println("productoOfInts: $productoOfInts")
líneas a la función principal.
val nombres = comestibles.fold("") { cadena, elemento > cadena + println("nombres: $nombres") " ${elemento.nombre}" }
val changeFrom50 = comestibles.fold(50.0) { cambio, artículo
> cambiar item.unitPrice * item.quantity }
println("cambioDe50: $cambioDe50")
}
Tomemos el código para una prueba de manejo.
Prueba de conducción
Cuando ejecutamos el código, el siguiente texto se imprime en la ventana de salida del IDE:
Verdura
Tomates
Hongos
Panadería
Bagels
Despensa
Aceite de oliva
Congelado
Helado
suma de enteros: 6
productOfInts: 6
nombres: Tomates Champiñones Bagels Aceite de oliva Helado changeFrom50:
22.0
388 Capítulo 12
Machine Translated by Google
funciones integradas de orden superior
estas aqui 4 389
Machine Translated by Google
afila tu lápiz
El siguiente código define la clase de datos Grocery y un
List<Grocery> comestibles con nombre :
clase de datos Supermercado (nombre de valor: cadena, categoría de valor: cadena,
val unidad: Cadena, val unidadPrecio: Doble,
cantidad de valores: Int)
val comestibles = listaDe(Comestibles("Tomates", "Verduras", "lb", 3.0, 3),
Comestibles ("Champiñones", "Verduras", "lb", 4.0, 1),
Comestibles ("Bagels", "Panadería", "Paquete", 1.5, 2),
Supermercado("Aceite de oliva", "Despensa", "Botella", 6.0, 1),
Comestibles ("Helado", "Frozen", "Pack", 3.0, 2))
Escribe el siguiente código para saber cuánto se gastará en vegetales.
Cree una Lista que contenga el nombre de cada artículo cuyo precio total sea inferior a 5,0
Imprime el costo total de cada categoría.
Escribe el nombre de cada artículo que no viene en botella, agrupados por unidad.
Respuestas en la página 392.
390 Capítulo 12
Machine Translated by Google
funciones integradas de orden superior
A continuación se muestra un breve programa de Kotlin. Falta un
bloque del programa. Su desafío es hacer coincidir el bloque de código
candidato (a la izquierda), con el resultado que vería si se insertara el
bloque. No se usarán todas las líneas de salida y algunas líneas de
salida se pueden usar más de una vez. Dibuje líneas que conecten los
Mezclado
bloques de código candidatos con su salida correspondiente.
Mensajes
diversión principal(argumentos: Array<String>) {
val myMap = mapOf("A" a 4, "B" a 3, "C" a 2, "D" a 1, "E" a 2)
varx1 = ""
El código de
var x2 = 0
candidato va aquí.
Relaciona
cada candidato
con uno de los imprimir("$x1$x2")
}
posibles resultados.
Candidatos: Salida posible:
x1 = myMap.keys.fold("") { x, y > x + y} x2 =
10
myMap.entries.fold(0) { x, y > x * y.value }
ABCDE0
x2 = myMap.values.groupBy { it }.keys.sumBy { it }
ABCDE48
x1 = "ABCDE"
x2 = miMapa.valores.fold(12) { x, y > x y }
43210
x2 = myMap.entries.fold(1) { x, y > x * y.value } 432120
x1 = miMapa.valores.fold("") { x, y > x + y } 48
x1 = miMapa.valores.fold(0) { x, y > x + y } 125
.toString() x2
= myMap.keys.groupBy { it }.size Respuestas en la página 393.
391
estas aqui 4
Machine Translated by Google
afilar solución
El siguiente código define la clase de datos Grocery y un
List<Grocery> comestibles con nombre :
clase de datos Supermercado (nombre de valor: cadena, categoría de valor: cadena,
val unidad: Cadena, val unidadPrecio: Doble,
cantidad de valores: Int)
val comestibles = listaDe(Comestibles("Tomates", "Verduras", "lb", 3.0, 3),
Comestibles ("Champiñones", "Verduras", "lb", 4.0, 1),
Comestibles ("Bagels", "Panadería", "Paquete", 1.5, 2),
Supermercado("Aceite de oliva", "Despensa", "Botella", 6.0, 1),
Comestibles ("Helado", "Frozen", "Pack", 3.0, 2))
Filtre por categoría, luego sume
Escribe el siguiente código para saber cuánto se gastará en vegetales. el precio total.
groceries.filter { it.category == "Vegetable" }.sumByDouble { it.unitPrice * it.quantity }
Cree una Lista que contenga el nombre de cada artículo cuyo precio total sea inferior a 5,0
Filtre por
groceries.filter { it.unitPrice * it.quantity < 5.0 }.map { it.name } precio unitario * cantidad,
luego use el mapa para
transformar el resultado.
Imprime el costo total de cada categoría.
Para cada categoría...
comestibles.groupBy { it.category }.forEach
{ println("${it.key}: ${it.value.sumByDouble { it.unitPrice * it.quantity }}")
} ... imprime la clave, seguida del resultado
de sumByDouble para cada valor.
Escribe el nombre de cada artículo que no viene en botella, agrupados por unidad. Agrupa los resultados por unidad.
comestibles.filterNot { it.unit == "Bottle"}.groupBy { it.unit }.forEach {
Obtenga las entradas donde el Imprime cada clave en el Mapa resultante.
println(it.key)
valor de la unidad no es "Botella"
it.value.forEach { println(" ${it.name}") }
} Cada valor en el Mapa es una Lista<Comestibles>,
para que podamos usar
para que Cada uno recorra cada
Lista e imprima el nombre de cada artículo.
392 Capítulo 12
Machine Translated by Google
funciones integradas de orden superior
A continuación se muestra un breve programa de Kotlin. Falta un
bloque del programa. Su desafío es hacer coincidir el bloque de código
candidato (a la izquierda), con el resultado que vería si se insertara el
bloque. No se usarán todas las líneas de salida y algunas líneas de
salida se pueden usar más de una vez. Dibuje líneas que conecten los
Mezclado bloques de código candidatos con su salida correspondiente.
Mensajes
Solución
diversión principal(argumentos: Array<String>) {
val myMap = mapOf("A" a 4, "B" a 3, "C" a 2, "D" a 1, "E" a 2)
varx1 = ""
El código de var x2 = 0
candidato va aquí.
imprimir("$x1$x2")
}
Candidatos: Salida posible:
x1 = myMap.keys.fold("") { x, y > x + y} x2 =
10
myMap.entries.fold(0) { x, y > x * y.value }
ABCDE0
x2 = myMap.values.groupBy { it }.keys.sumBy { it }
ABCDE48
x1 = "ABCDE"
x2 = miMapa.valores.fold(12) { x, y > x y }
43210
x2 = myMap.entries.fold(1) { x, y > x * y.value } 432120
x1 = miMapa.valores.fold("") { x, y > x + y } 48
x1 = miMapa.valores.fold(0) { x, y > x + y } 125
.toString() x2
= myMap.keys.groupBy { it }.size
estas aqui 4 393
Machine Translated by Google
caja de herramientas
Tu caja de herramientas de Kotlin
Puede descargar
Tiene el Capítulo 12 en su haber y ahora ha el código
agregado funciones integradas de orden superior completo del
a su caja de herramientas. capítulo desde https://
tinyurl.com/HFKotlin.
CAPÍTULO
12
Use minBy y maxBy para encontrar el valor más bajo La función de mapa transforma los elementos de
o más alto en una colección. Estas funciones toman un una colección de acuerdo con algunos criterios que
parámetro, una lambda cuyo cuerpo especifica los criterios especifica mediante una lambda. Devuelve una Lista.
de la función. El tipo de devolución coincide con el tipo de
forEach funciona como un bucle for. Le permite realizar
elementos de la colección.
una o más acciones para cada elemento de una colección.
Utilice sumBy o sumByDouble para devolver la suma
de los elementos de una colección. Su parámetro, una
Use groupBy para dividir una colección en grupos.
lambda, especifica lo que desea sumar.
Toma un parámetro, una lambda, que define cómo la
Si es un Int, use sumBy, y si es un Double, use
función debe agrupar los elementos. La función devuelve
sumByDouble.
un Mapa, que utiliza los criterios lambda para las claves,
La función de filtro le permite buscar o filtrar una colección y una Lista para cada valor.
de acuerdo con algunos criterios. Este criterio se especifica
La función de plegado le permite especificar un valor
mediante una lambda, cuyo cuerpo de lambda debe
inicial y realizar alguna operación para cada elemento de
devolver un valor booleano. El filtro generalmente devuelve
una colección. Toma dos parámetros: el valor inicial y
una Lista. Sin embargo, si la función se utiliza con un
una lambda que especifica la operación que desea realizar.
mapa, devuelve un mapa en su lugar.
394 Capítulo 12
Machine Translated by Google
Saliendo de la ciudad...
Ha sido genial tenerte aquí en Kotlinville.
Nos entristece que te vayas, pero no hay nada como tomar lo que has aprendido.
y ponerlo en uso. Todavía hay algunas gemas más para usted en la parte posterior del libro y un
práctico índice, y luego es hora de tomar todas estas nuevas ideas y ponerlas en práctica. bon
¡viaje!
Machine Translated by Google
Machine Translated by Google
apéndice i: rutinas
puedo caminar y mascar
chicle al mismo tiempo?
¡Que interesante!
Algunas tareas se realizan mejor en segundo plano.
Si desea leer datos de un servidor externo lento, probablemente no desee que el resto de su código permanezca
esperando a que se complete el trabajo. En situaciones como estas, las corrutinas son tu nueva mejor amiga. Las
rutinas le permiten escribir código que se ejecuta de forma asincrónica.
Esto significa menos tiempo dando vueltas, una mejor experiencia de usuario y también puede hacer que su
aplicación sea más escalable. Sigue leyendo y aprenderás el secreto de cómo hablar con Bob mientras escuchas a
Suzy al mismo tiempo.
esto es un apéndice 397
Machine Translated by Google
crear proyecto
Construyamos una caja de ritmos
El código de este
Las corrutinas le permiten crear múltiples piezas de código que pueden
ejecutarse de forma asincrónica. En lugar de ejecutar piezas de código en secuencia,
una tras otra, las corrutinas le permiten ejecutarlas una al lado de la otra.
apéndice se aplica a
Kotlin 1.3 y versiones
El uso de rutinas significa que puede iniciar un trabajo en segundo plano, como
leer datos de un servidor externo, sin que el resto de su código tenga que esperar
posteriores. En
a que se complete el trabajo antes de hacer cualquier otra cosa.
Esto le brinda a su usuario una experiencia más fluida y también hace que su
versiones anteriores, las
aplicación sea más escalable.
Para ver la diferencia que el uso de rutinas puede hacer en su código, suponga
corrutinas se marcaban
que desea construir una caja de ritmos basada en algún código que reproduzca
una secuencia de ritmo de batería. Comencemos por crear el proyecto de Drum como experimentales.
Machine siguiendo los siguientes pasos.
1. Crear un nuevo proyecto GRADLE Gradle es una herramienta de compilación
que le permite compilar e implementar código
Para escribir código que use corrutinas, necesitamos crear un nuevo proyecto e incluir cualquier biblioteca de terceros que
Gradle para que podamos configurarlo para usar corrutinas. Para hacer esto, cree un necesite su código. Estamos usando Gradle
nuevo proyecto, seleccione la opción Gradle y marque Kotlin (Java). Luego haga clic aquí para poder agregar rutinas a nuestro
en el botón Siguiente.
proyecto unas páginas más adelante.
Elija la opción Kotlin (Java), ya que estamos
usando Kotlin para apuntar a la JVM.
Seleccione
la opción Gradle.
398 apéndice i
Machine Translated by Google
corrutinas
2. Ingrese una ID de artefacto
Cuando crea un proyecto de Gradle, debe especificar un ID de
artefacto. Este es básicamente el nombre del proyecto, excepto que,
por convención, debe estar en minúsculas. Ingrese una ID de artefacto
de "drummachine", luego haga clic en el botón Siguiente.
Estamos utilizando un ID de artefacto de "drummachine".
3. Especifique los detalles de configuración
A continuación, debe especificar cualquier cambio en la configuración
predeterminada del proyecto. Haga clic en el botón Siguiente para aceptar los valores predeterminados.
Acepte los valores predeterminados
haciendo clic en el botón Siguiente.
estas aqui 4 399
Machine Translated by Google
agregar archivos
4. Especifique el nombre del proyecto
Finalmente, necesitamos especificar un nombre de proyecto. Nombre el
proyecto "Drum Machine", luego haga clic en el botón Finalizar. IntelliJ IDEA
creará su proyecto.
Hemos llamado a nuestro proyecto “Drum Machine”.
Agregar los archivos de audio
Ahora que ha creado el proyecto Drum Machine, necesita agregarle un par de
archivos de audio. Descargue los archivos crash_cymbal.aiff y toms.aiff de
https://tinyurl.com/HFKotlin, luego arrástrelos a su proyecto. Cuando se le
solicite, confirme que desea moverlos a la carpeta Drum Machine .
Estamos agregando los archivos
al directorio raíz de nuestro proyecto.
400 apéndice i
Machine Translated by Google
corrutinas
Agregar el código al proyecto
Nos han dado un código que reproduce una secuencia de batería, que debemos
agregar al proyecto. Cree un nuevo archivo de Kotlin llamado Beats.kt
resaltando la carpeta src/main/kotlin , haciendo clic en el menú Archivo y
eligiendo Nuevo → Archivo/clase de Kotlin. Cuando se le solicite, nombre el
archivo "Beats" y seleccione Archivo en la opción Tipo. Luego actualice su versión
de Beats.kt para que coincida con la nuestra a continuación:
Estamos usando dos bibliotecas de Java, por lo que
importar java.io.File importar debemos importarlas. Puede obtener más información
javax.sound.sampled.AudioSystem sobre las declaraciones de importación en el Apéndice III.
diversión playBeats(ritmos: Cadena, archivo: Cadena) { val partes = El parámetro de tiempos especifica el patrón de
beats.split("x") tiempos. El parámetro de archivo especifica el archivo
recuento de variables = 0 de sonido a reproducir.
para (parte en partes) {
cuenta += parte.longitud + 1 if (parte ==
"") { reproducirSonido(archivo) } else {
Detiene el hilo de ejecución
actual para que el archivo de
Thread.sleep(100 * (parte.longitud + 1L)) if (recuento
Llame a playSound una sonido tenga tiempo de ejecutarse.
<latidos.longitud) {
vez por cada "x" en el
reproducir sonido (archivo)
parámetro de tiempos.
}
caja de ritmos
}
}
} src/principal/kotlin
Reproduce el archivo de audio especificado.
divertido playSound (archivo: Cadena) {
Beats.kt
val clip = AudioSystem.getClip() val audioInputStream
= AudioSystem.getAudioInputStream(
Archivo(
archivo
)
)
clip.open(audioInputStream) clip.start()
diversión principal () {
Reproduce los archivos de sonido de timbales y platillos.
playBeats("xxxxxx", "toms.aiff") playBeats("xx",
"crash_cymbal.aiff")
}
Veamos qué sucede cuando se ejecuta el código.
estas aqui 4 401
Machine Translated by Google
prueba de manejo
Prueba de conducción
Cuando ejecutamos el código, toca primero los timbales (toms.aiff), seguidos de
los platillos (crash_cymbal.aiff). Hace esto en secuencia, así que una vez que los
timbales han terminado, los platillos comienzan a sonar:
¡Bam! ¡Bam! ¡Bam! ¡Bam! ¡Bam! ¡Bam! Tish! Tish!
El código reproduce el archivo de Luego reproduce el
sonido de toms seis veces. archivo de sonido de
platillos dos veces.
Pero, ¿y si queremos tocar los timbales y los platillos en paralelo?
Usa rutinas para hacer que los ritmos suenen en paralelo
Como dijimos anteriormente, las corrutinas le permiten ejecutar múltiples piezas
de código de forma asíncrona. En nuestro ejemplo, esto significa que podemos
agregar nuestro código de tambor tom a una rutina para que suene al mismo
tiempo que los platillos.
Hay dos cosas que tenemos que hacer para lograr esto:
1 Agregue coroutines al proyecto como una dependencia.
Las rutinas están en una biblioteca Kotlin separada, que debemos
agregar a nuestro proyecto antes de poder usarlas.
2 Inicie una rutina.
La rutina incluirá el código que reproduce los timbales.
Hagamos esto ahora.
402 apéndice i
Machine Translated by Google
corrutinas
1. Agrega una dependencia de rutinas
Si desea utilizar rutinas en su proyecto, primero debe agregarlo a su proyecto como
una dependencia. Para hacer esto, abra build.gradle y actualice la sección de
dependencias así:
dependencias {
caja de ritmos
compilar "org.jetbrains.kotlin:kotlinstdlibjdk8"
implementación 'org.jetbrains.kotlinx:kotlinxcoroutinescore:1.0.1'
construir.gradle
}
Agregue esta línea a build.gradle
para agregar la biblioteca de rutinas
Luego haga clic en el indicador Importar cambios para que el cambio surta
efecto: a su proyecto.
Haga clic en Importar cambios
si se le solicita que lo haga.
A continuación, actualizaremos nuestra función principal para que use una rutina.
2. Inicie una rutina
Haremos que nuestro código reproduzca el archivo de sonido de los toms
en una rutina separada en segundo plano encerrando el código que lo
reproduce en una llamada a GlobalScope.launch desde kotlinx. biblioteca de
corrutinas. Detrás de escena, esto hace que el código que reproduce el archivo de
sonido de toms se ejecute en segundo plano para que los dos sonidos se
reproduzcan en paralelo.
Aquí está la nueva versión de nuestra función principal: actualice su código con
nuestros cambios (en negrita):
...
importar kotlinx.coroutines.* Agregue esta línea para que
caja de ritmos
... puedo usar funcionemos desde la
biblioteca coroutines en nuestro código.
src/principal/kotlin
diversión principal () {
Inicie una GlobalScope.lanzamiento { playBeats("xxxxxx", "toms.aiff") }
corrutina en Beats.kt
playBeats("xx", "crash_cymbal.aiff")
segundo plano.
}
Veamos esto en acción tomando el código para una prueba de manejo.
estas aqui 4 403
Machine Translated by Google
corrutinas vs hilos
Prueba de conducción
Cuando ejecutamos el código, toca los timbales y platillos en paralelo. El sonido de los
timbales se reproduce en una rutina separada de fondo.
¡Bam! ¡Bam! ¡Bam! ¡Bam! ¡Bam! ¡Bam!
Tish! Tish!
Esta vez, los timbales y los
platillos tocan en paralelo.
Ahora que ha visto cómo iniciar una corrutina en segundo plano y el efecto que esto tiene,
profundicemos un poco más en las corrutinas.
Una rutina es como un hilo ligero.
Detrás de escena, lanzar una corrutina es como comenzar un hilo de ejecución por
separado . Los subprocesos son muy comunes en otros lenguajes como Java, y tanto las
corrutinas como los subprocesos pueden ejecutarse en paralelo y comunicarse entre sí. La
diferencia clave, sin embargo, es que es más eficiente usar rutinas en su código que usar
subprocesos.
Comenzar un hilo y mantenerlo funcionando es bastante costoso en términos de
rendimiento. Por lo general, el procesador solo puede ejecutar una cantidad limitada de
subprocesos al mismo tiempo, y es más eficiente ejecutar la menor cantidad de
subprocesos posible. Las corrutinas, por otro lado, se ejecutan en un grupo compartido de
subprocesos de forma predeterminada, y el mismo subproceso puede ejecutar muchas
corrutinas. Como se usan menos subprocesos, esto hace que sea más eficiente usar
rutinas cuando desea ejecutar tareas de forma asíncrona.
En nuestro código, usamos GlobalScope.launch para ejecutar una nueva rutina
en segundo plano. Detrás de escena, esto crea un nuevo subproceso en el que se
ejecuta la rutina, de modo que toms.aiff y crash_cymbal.aiff se reproducen en subprocesos
separados. Como es más eficiente usar la menor cantidad de subprocesos posible,
busquemos cómo podemos reproducir los archivos de sonido en rutinas separadas, pero
en el mismo subproceso.
404 apéndice i
Machine Translated by Google
corrutinas
Use runBlocking para ejecutar rutinas en el mismo ámbito
Si desea que su código se ejecute en el mismo subproceso pero en rutinas separadas, puede usar la
función runBlocking . Esta es una función de orden superior que bloquea el subproceso actual hasta
que el código que se le pasa termina de ejecutarse. La función runBlocking define un alcance que es
heredado por el código que se le pasa; en nuestro ejemplo, podemos usar este alcance para ejecutar
rutinas separadas en el mismo hilo.
Aquí hay una nueva versión de nuestra función principal que hace esto: actualice su versión del
código para incluir nuestros cambios (en negrita):
diversión principal () { Envuelva el código que queremos
ejecutar en una llamada a runBlocking. caja de ritmos
ejecutarBlocking {
Quite la
GlobalScope.lanzamiento {playBeats("xxxxxx", "toms.aiff") }
referencia src/principal/kotlin
a GlobalScope. playBeats("xx", "crash_cymbal.aiff")
}
Beats.kt
}
Tenga en cuenta que ahora estamos iniciando una nueva corrutina utilizando el lanzamiento en lugar
de GlobalScope.launch. Esto se debe a que queremos lanzar una corrutina que se ejecute en el
mismo subproceso, en lugar de en un subproceso de fondo separado, y omitir la referencia a
GlobalScope permite que la corrutina use el mismo alcance que runBlocking.
Veamos qué sucede cuando ejecutamos el código.
Prueba de conducción
Cuando ejecutamos el código, los archivos de sonido se reproducen, pero en secuencia, no en paralelo.
Tish! Tish! ¡Bam! ¡Bam! ¡Bam! ¡Bam! ¡Bam! ¡Bam!
El código reproduce el
Luego reproduce el archivo de sonido de
archivo de sonido de platillos dos veces. toms seis veces.
Entonces, ¿qué salió mal?
estas aqui 4 405
Machine Translated by Google
Thread.sleep vs retraso
Thread.sleep pausa el THREAD actual
Como habrás notado, cuando agregamos la función playBeats a nuestro proyecto,
incluimos la siguiente línea:
Thread.sleep(100 * (part.length + 1L))
Esto utiliza una biblioteca de Java para pausar el hilo actual para que el archivo de sonido
que se está reproduciendo tenga tiempo de ejecutarse y bloquea el hilo para que no haga
nada más. Como ahora estamos reproduciendo los archivos de sonido en el mismo hilo,
ya no se pueden reproducir en paralelo, aunque estén en rutinas separadas.
La función de retardo pausa la CORUTINA actual
Un mejor enfoque en esta situación es utilizar la función de retardo de rutinas en su lugar.
Esto tiene un efecto similar a Thread.sleep, excepto que en lugar de pausar el hilo actual,
pausa la rutina actual. Suspende la rutina durante un período de tiempo específico y esto
permite que se ejecute otro código en el mismo subproceso. El siguiente código, por
ejemplo, retrasa la rutina durante 1 segundo:
La función de retraso agrega una pausa, pero
retraso (1000)
es más eficiente que usar Thread.sleep.
La función de retardo se puede utilizar en estas dos situaciones:
¥ Desde dentro de una rutina.
El siguiente código, por ejemplo, llama a la función de retraso dentro de una
rutina:
GlobalScope.lanzamiento {
Aquí, estamos lanzando la rutina y
retraso (1000) luego retrasando su código por 1 segundo.
// código que se ejecuta después de 1 segundo
¥ Desde dentro, una función que el compilador sabe puede pausar o
suspender.
Cuando llama a
En nuestro ejemplo, queremos usar la función de retraso dentro de la función playBeats, lo
que significa que debemos decirle al compilador que playBeats, y la función principal que lo una función
llama, puede suspenderse. Para hacer esto, agregaremos el prefijo de suspensión a ambas
funciones usando un código como este: suspendible (como un
suspender playBeats divertidos (ritmos: Cadena, archivo: Cadena) { retraso) desde otra
...
El prefijo de suspensión le dice al compilador
}
que la función puede suspenderse.
función, esa función
Le mostraremos el código completo del proyecto en la página siguiente. debe marcarse con suspensión
406 apéndice i
Machine Translated by Google
corrutinas
El código completo del proyecto.
Aquí está el código completo para el proyecto Drum Machine: actualice su
versión de Beats.kt para incluir nuestros cambios (en negrita):
importar java.io.Archivo
importar javax.sound.sampled.AudioSystem
importar kotlinx.coroutines.*
suspender playBeats divertidos (ritmos: Cadena, archivo: Cadena) {
val partes = beats.split("x")
Marque playBeats recuento de variables = 0
con suspender
para (parte en partes) {
para que pueda
cuenta += longitud parcial + 1
llamar a la función de retraso.
si (parte == "") {
reproducir sonido (archivo)
} demás {
Reemplace Thread.sleep Thread.retraso del sueño (100 * (part.length + 1L))
con retraso. if (cuenta < latidos.longitud) {
reproducir sonido (archivo)
}
caja de ritmos
}
}
src/principal/kotlin
}
divertido playSound (archivo: Cadena) { Beats.kt
val clip = AudioSystem.getClip()
val audioInputStream = AudioSystem.getAudioInputStream(
Archivo(
archivo
clip.open(flujo de entrada de audio)
clip.inicio()
}
Marque
suspender fun main() {
principal con
ejecutarBlocking {
suspender para
iniciar {playBeats("xxxxxx", "toms.aiff") }
que pueda llamar
a la función playBeats. playBeats("xx", "crash_cymbal.aiff")
}
}
Veamos qué sucede cuando se ejecuta el código.
estas aqui 4 407
Machine Translated by Google
prueba de manejo
Prueba de conducción
Cuando ejecutamos el código, toca los timbales y platillos en paralelo como antes. Esta vez, sin
embargo, los archivos de sonido se ejecutan en rutinas separadas en el mismo hilo.
¡Bam! ¡Bam! ¡Bam! ¡Bam! ¡Bam! ¡Bam!
Tish! Tish!
Los timbales y los platillos aún se reproducen en paralelo, pero esta vez
estamos usando una forma más eficiente de reproducir los archivos de sonido.
Puede obtener más información sobre el uso de rutinas aquí:
https://kotlinlang.org/docs/reference/coroutinesoverview.html
Las corrutinas le permiten ejecutar La función runBlocking bloquea el
código de forma asincrónica. Son útiles para subproceso actual hasta que el código que
ejecutar tareas en segundo plano. contiene haya terminado de ejecutarse.
Una rutina es como un hilo ligero. Las La función de retraso suspende el código
corrutinas se ejecutan en un grupo compartido durante un período de tiempo específico.
de subprocesos de forma predeterminada, y el Se puede usar dentro de una corrutina o dentro
mismo subproceso puede ejecutar muchas de una función marcada con suspensión.
corrutinas.
Para usar rutinas, cree un proyecto de Gradle
Puede descargar
y agregue la biblioteca de rutinas a build.gradle
el código completo
como una dependencia.
de este apéndice
Use la función de lanzamiento para lanzar una
nueva rutina.
desde https://
tinyurl.com/HFKotlin.
408 apéndice i
Machine Translated by Google
apéndice ii: pruebas
Sostener Su Código
No me importa
para Cuenta
quién eres, o qué tan duro
te ves. Si no sabes la
contraseña, no vas a
entrar.
Todo el mundo sabe que un buen código debe funcionar.
Pero cada cambio de código que realice corre el riesgo de introducir nuevos errores que impidan que su
código funcione como debería. Es por eso que las pruebas exhaustivas son tan importantes: significa que
puede conocer cualquier problema en su código antes de que se implemente en el entorno en vivo.
En este apéndice, analizaremos JUnit y KotlinTest, dos bibliotecas que puede usar para realizar pruebas
unitarias de su código para que siempre tenga una red de seguridad.
esto es un apéndice 409
Machine Translated by Google
JUnit
Kotlin puede usar bibliotecas de prueba existentes
Como ya sabe, el código de Kotlin se puede compilar en Java, JavaScript o código
nativo, por lo que puede usar las bibliotecas existentes en su plataforma de destino.
Cuando se trata de pruebas, esto significa que puede probar el código de Kotlin
utilizando las bibliotecas de prueba más populares en Java y JavaScript.
Veamos cómo usar JUnit para probar unitariamente su código Kotlin.
Agregar la biblioteca JUnit
La biblioteca JUnit (https://junit.org) es la biblioteca de pruebas de Java más utilizada.
Las pruebas unitarias se
utilizan para probar unidades
Para usar JUnit en su proyecto Kotlin, primero debe agregar las bibliotecas JUnit a su
proyecto. Puede agregar bibliotecas a su proyecto yendo al menú Archivo y eligiendo individuales de código fuente,
Estructura del proyecto → Bibliotecas o, si tiene un proyecto Gradle, puede agregar estas
como clases o funciones.
líneas a su archivo build.gradle :
dependencias {
.... Estas líneas
implementación de prueba 'org.junit.jupiter:junitjupiterapi:5.3.1' agregan la versión
5.3.1 de las
testRuntimeOnly 'org.junit.jupiter:junitjupiterengine:5.3.1'
prueba {usarJUnitPlatform()} bibliotecas JUnit al
proyecto. Cambie los
....
números si desea
} utilizar una versión diferente.
Una vez que se compila el código, puede ejecutar las pruebas haciendo clic con
el botón derecho en el nombre de la clase o función y luego seleccionando la
opción Ejecutar.
Para ver cómo usar JUnit con Kotlin, vamos a escribir una prueba para la siguiente
clase llamada Totaller: la clase se inicializa con un valor Int y mantiene un total
acumulado de los valores que se le agregan usando su add función:
clase Totalizador(var total: Int = 0) {
divertido agregar (num: Int): Int {
total += número
devolución total
}
}
Veamos cómo sería una prueba JUnit para esta clase.
410 apéndice ii
Machine Translated by Google
pruebas
Crear una clase de prueba JUnit
Aquí hay un ejemplo de clase de prueba JUnit llamada TotallerTest que se usa
para probar Totaller:
Estamos usando código de los paquetes JUnit, por lo que
importar org.junit.jupiter.api.Assertions.* debemos importarlos. Puede obtener más información
importar org.junit.jupiter.api.Test sobre las declaraciones de importación en el Apéndice III.
La clase TotallerTest se utiliza para probar Totaller.
clase TotallerTest {
@Prueba Esta es una anotación que marca la siguiente función como una prueba.
diversión debería ser capaz de agregar 3 y 4 () {
Cree un objeto Totaller.
val totalizador = totalizador()
Comprueba que si sumamos 3, el valor devuelto es 3.
afirmarEquals(3, totaler.add(3))
afirmarEquals(7, totaler.add(4)) Si ahora sumamos 4, el valor devuelto debería ser 7.
afirmarEquals(7, totalizador.total) Compruebe que el valor devuelto coincida
} con el valor de la variable total.
}
Cada prueba se lleva a cabo en una función, con el prefijo @Test.
Las anotaciones se utilizan para agregar información programática sobre su código,
y la anotación @Test es una forma de decirle a las herramientas "Esta es una
función de prueba".
Las pruebas se componen de acciones y afirmaciones. Las acciones son piezas de
código que hacen cosas, mientras que las aserciones son piezas de código que Puede obtener más
información sobre
verifican cosas. En el código anterior, estamos usando una aserción llamada
assertEquals que verifica que los dos valores que se le dan sean iguales. Si no lo el uso de JUnit
son, assertEquals lanzará una excepción y la prueba fallará.
aquí: https://junit.org
En el ejemplo anterior, nombramos nuestra función de prueba
shouldBeAbleToAdd3And4. Sin embargo, podemos usar una función de Kotlin
que se usa con poca frecuencia y que nos permite envolver los nombres de las
funciones en comillas invertidas (`) y luego agregar espacios y otros símbolos al
nombre de la función para que sea más descriptivo. Aquí hay un ejemplo:
Esto parece extraño, pero es un
....
nombre de función de Kotlin válido.
@Prueba
fun ̀debe poder sumar 3 y 4 y no debe salir mal`() {
val totalizador = totalizador()
...
En su mayor parte, usa JUnit en Kotlin casi de la misma manera que podría usarlo
con un proyecto Java. Pero si quieres algo un poco más Kotliny, hay otra biblioteca
que puedes usar, llamada KotlinTest.
estas aqui 4 411
Machine Translated by Google
Prueba de Kotlin
Usando KotlinTest
La biblioteca KotlinTest (https://github.com/kotlintest/kotlintest) ha sido diseñada
para usar la amplitud completa del lenguaje Kotlin para escribir pruebas de
una manera más expresiva. Al igual que JUnit, es una biblioteca separada que
debe agregarse a su proyecto si desea usarla.
KotlinTest es bastante amplio y le permite escribir pruebas en muchos estilos
diferentes, pero aquí hay una forma de escribir una versión de KotlinTest del
código JUnit que escribimos anteriormente:
Estamos usando estas funciones de las bibliotecas de
importar io.kotlintest.shouldBe KotlinTest, por lo que debemos importarlas.
importar io.kotlintest.specs.StringSpec
La función de prueba JUnit se reemplaza con una cadena.
clase OtraPruebaTotal: StringSpec({
"debería poder sumar 3 y 4, y no debe salir mal" {
val totalizador = totalizador()
totaler.add(3) debería ser 3
totaler.add(4) debería ser 7 Estamos usando shouldBe en lugar de assertEquals.
totaler.total debe ser 7
}
})
La prueba anterior se parece a la prueba JUnit que vio anteriormente, excepto
que la función de prueba se reemplaza con una cadena y las llamadas a
assertEquals se han reescrito como expresiones shouldBe.
Este es un ejemplo del estilo String Specification (o StringSpec) de
KotlinTest. Hay varios estilos de prueba disponibles en KotlinTest, y debe
elegir el que mejor se adapte a su código.
Pero KotlinTest no es solo una reescritura de JUnit (de hecho, KotlinTest usa
JUnit bajo el capó). KotlinTest tiene muchas más funciones que pueden permitirle
crear pruebas más fácilmente y con menos código de lo que puede hacer con
una simple biblioteca de Java. Puede, por ejemplo, usar filas para probar su
código con conjuntos completos de datos. Veamos un ejemplo.
412 apéndice ii
Machine Translated by Google
pruebas
Use filas para probar contra conjuntos de datos
Aquí hay un ejemplo de una segunda prueba que usa filas para sumar muchos
números diferentes (nuestros cambios están en negrita):
importar io.kotlintest.data.forall
importar io.kotlintest.shouldBe
importar io.kotlintest.specs.StringSpec
Estamos usando estas dos funciones
adicionales de las bibliotecas de KotlinTest.
importar io.kotlintest.tables.row
clase OtraPruebaTotal: StringSpec({
"debería poder sumar 3 y 4, y no debe salir mal" {
val totalizador = totalizador()
totaler.add(3) debería ser 3
totaler.add(4) debería ser 7
totaler.total debe ser 7
}
Esta es la segunda prueba.
"debería poder sumar muchos números diferentes" {
para todos(
Ejecutaremos la prueba para cada fila de datos.
fila (1, 2, 3), fila (19,
47, 66), fila (11, 21, 32)
Los valores de cada fila se asignarán
a las variables x, y y total esperado.
) { x, y, total esperado >
val totaler = Totaller(x) totaler.add(y) debe Estas dos líneas se ejecutarán para cada fila.
ser esperadoTotal
}
}
})
También puede usar KotlinTest para:
¥ Ejecutar pruebas en paralelo.
¥ Crear pruebas con propiedades generadas.
¥ Habilitar/deshabilitar pruebas dinámicamente. Es posible, por ejemplo, que desee que
algunas pruebas se ejecuten solo en Linux y otras en Mac.
¥ Poner pruebas en grupos.
y mucho, mucho más. Si planea escribir mucho código Kotlin, definitivamente vale
la pena echarle un vistazo a KotlinTest.
Puede obtener más información sobre KotlinTest aquí:
https://github.com/kotlintest/kotlintest
estas aqui 4 413
Machine Translated by Google
Machine Translated by Google
apéndice iii: sobras
Incluso después de todo eso, todavía hay un poco más.
Hay algunas cosas más que creemos que necesita saber. No nos sentiríamos bien acerca de
ignorándolos, y realmente queríamos darte un libro que pudieras levantar sin entrenar
en el gimnasio local. Antes de dejar el libro, lea estos datos.
esto es un apéndice 415
Machine Translated by Google
paquetes e importaciones
1. Paquetes e importaciones
Como dijimos en el Capítulo 9, las clases y funciones en la Biblioteca
estándar de Kotlin se agrupan en paquetes. Lo que no dijimos es que
puede agrupar su propio código en paquetes.
Poner su código en paquetes es útil por dos razones principales:
¥
Te permite organizar tu código.
Puede usar paquetes para agrupar su código en tipos específicos de funcionalidad,
como estructuras de datos o bases de datos.
¥
Evita conflictos de nombres.
Si escribe una clase llamada Duck, ponerla en un paquete le permite diferenciarla de
cualquier otra clase de Duck que se haya agregado a su proyecto.
Cómo agregar un paquete
Para agregar un paquete a su proyecto de Kotlin, resalte la
carpeta src y seleccione Archivo→Nuevo→Paquete. Cuando se
le solicite, ingrese el nombre del paquete (por ejemplo,
com.hfkotlin.mypackage), luego haga clic en Aceptar.
Declaraciones de paquetes
Cuando agrega un archivo de Kotlin a un paquete (resaltando el Este es el nombre del
nombre del paquete y eligiendo Archivo→Nuevo→Archivo/Clase de paquete que estamos creando.
Kotlin), se agrega automáticamente una declaración de paquete al
comienzo del archivo de origen como esta:
paquete com.hfkotlin.mipaquete
Su proyecto puede contener
La declaración del paquete le dice al compilador que todo en el
archivo fuente pertenece a ese paquete. El siguiente código, por varios paquetes y cada
ejemplo, especifica que com.hfkotlin.mypackage contiene la clase
Duck y la función doStuff: paquete puede tener varios
paquete com.hfkotlin.mipaquete archivos de origen. Sin
embargo, cada archivo
pato de clase
Este es un archivo fuente único, por
cosas divertidas() {
lo que Duck y doStuff se agregan al fuente solo puede tener una
...
paquete com.hfkotlin.mypackage
}
declaración de paquete.
Si el archivo de origen no tiene una declaración de paquete, el código se
agrega a un paquete predeterminado sin nombre.
416 apéndice iii
Machine Translated by Google
sobras
El nombre completo
Cuando agrega una clase a un paquete, su nombre completo, o completamente
calificado, es el nombre de la clase con el prefijo del nombre del paquete. Entonces,
si com.hfkotlin.mypackage contiene una clase llamada Duck, el nombre completo
de la clase Duck es com.hfkotlin.mypackage.Duck.
Todavía puede referirse a él como Duck en cualquier código dentro del mismo
paquete, pero si desea usar la clase en otro paquete, debe proporcionarle al
compilador su nombre completo.
Hay dos formas de proporcionar un nombre de clase completamente calificado:
usando su nombre completo en todas partes de su código o importándolo. Importaciones predeterminadas
Escriba el nombre completo... Los siguientes paquetes se
importan automáticamente
La primera opción es escribir el nombre completo de la clase cada vez que la en cada
use fuera de su paquete, por ejemplo: Archivo Kotlin por defecto:
paquete com.hfkotlin.myotherpackage kotlin.*
Este es un paquete diferente.
kotlin.anotación.*
diversión principal(argumentos: Array<String>) {
val pato = com.hfkotlin.mipaquete.Pato() kotlin.colecciones.*
...
Este es el nombre completamente calificado. kotlin.comparaciones.*
}
kotlin.io.*
Sin embargo, este enfoque puede ser engorroso si necesita hacer referencia a la
kotlin.ranges.*
clase muchas veces o hacer referencia a varios elementos en el mismo paquete.
kotlin.secuencias.*
...o importarlo kotlin.texto.*
Un enfoque alternativo es importar la clase o el paquete para que pueda hacer Si su plataforma de destino es la JVM,
referencia a la clase Duck sin tener que escribir el nombre completo cada vez. Aquí también se importa lo siguiente:
hay un ejemplo: java.lang.*
paquete com.hfkotlin.myotherpackage kotlin.jvm.*
Esta línea importa
importar com.hfkotlin.mypackage.Duck
la clase Duck... Y si está apuntando a JavaScript, lo
siguiente se importa en su lugar:
diversión principal(argumentos: Array<String>) {
val pato = pato()
... para que podamos referirnos a kotlin.js.*
...
él sin escribir su nombre completo.
}
También puede usar el siguiente código para importar un paquete completo:
importar com.hfkotlin.mipaquete.* El * significa "importar todo desde este paquete".
Y si hay un conflicto de nombre de clase, puede usar la palabra clave as :
importar com.hfkotlin.mypackage.Duck importar Aquí, puede referirse a la clase
com.hfKotlin.mypackage2.Duck como Duck2 Duck en mypackage2 usando "Duck2".
estas aqui 4 417
Machine Translated by Google
modificadores de visibilidad
2. Modificadores de visibilidad
Los modificadores de visibilidad le permiten establecer la visibilidad de cualquier
código que cree, como clases y funciones. Puede declarar, por ejemplo, que
una clase solo puede ser utilizada por el código en su archivo fuente, o que
una función miembro solo puede usarse dentro de su clase.
Kotlin tiene cuatro modificadores de visibilidad: público, privado, protegido e
interno. Veamos cómo funcionan estos.
Modificadores de visibilidad y código de nivel superior
Como ya sabe, el código como las clases, las variables y las funciones se pueden Recuerde: si no especifica un
declarar directamente dentro de un archivo o paquete fuente. De forma paquete, el código se agrega
predeterminada, todo este código es visible públicamente y se puede usar en automáticamente a un paquete sin
cualquier paquete que lo importe. Sin embargo, puede cambiar este comportamiento nombre de forma predeterminada.
anteponiendo las declaraciones con uno de los siguientes modificadores de visibilidad:
Modificador: Qué hace:
Tenga en
público Hace que la declaración sea visible en todas partes. Esto se aplica de forma predeterminada,
por lo que se puede omitir. cuenta que protected
no está disponible
privado Hace que la declaración sea visible para el código dentro de su archivo fuente, pero invisible en para declaraciones
cualquier otro lugar.
en el nivel superior
interno de un paquete o
Hace que la declaración sea visible dentro del mismo módulo, pero invisible
archivo fuente.
en otros lugares. Un módulo es un conjunto de archivos Kotlin que se compilan
juntos, como un módulo IntelliJ IDEA.
El siguiente código, por ejemplo, especifica que la clase Duck es pública y
se puede ver en cualquier lugar, mientras que la función doStuff es privada y
solo es visible dentro de su archivo fuente:
paquete com.hfkotlin.mipaquete
Duck no tiene modificador de visibilidad, lo que significa que es público.
pato de clase
diversión privada doStuff() {
doStuff() está marcado como privado, por lo que solo se puede
println("hola") usar dentro del archivo fuente donde está definido.
Los modificadores de visibilidad también se pueden aplicar a miembros de clases
e interfaces. Veamos cómo funcionan estos.
418 apéndice iii
Machine Translated by Google
sobras
Modificadores de visibilidad y clases/interfaces
Los siguientes modificadores de visibilidad se pueden aplicar a las
propiedades, funciones y otros miembros que pertenecen a una clase o interfaz:
Modificador: Qué hace:
público Hace que el miembro sea visible en todos los lugares donde la clase es visible. Esto se aplica de forma
predeterminada, por lo que se puede omitir.
privado Hace que el miembro sea visible dentro de la clase e invisible en cualquier otro lugar.
protected Hace que el miembro sea visible dentro de la clase y cualquiera de sus subclases.
interno Hace que el miembro sea visible para cualquier elemento del módulo que pueda ver la clase.
Aquí hay un ejemplo de una clase con modificadores de visibilidad en sus
propiedades y una subclase que la anula:
padre de clase abierta {
var a = 1 Como b es privado, solo se puede usar dentro de esta clase.
No puede ser visto por ninguna subclase de Parent.
var privado b = 2
abierto protegido var c = 3
var interno d = 4
La clase Child puede ver las propiedades a y c, y también puede
clase Hijo: Padre() { acceder a la propiedad d si Parent y Child están definidos en el mismo
módulo. Sin embargo, el niño no puede ver la propiedad b ya que su
anular var c = 6
modificador de visibilidad es privado.
}
Tenga en cuenta que si anula un miembro protegido, como en el ejemplo
anterior, la versión de subclase de ese miembro también estará protegida de forma
predeterminada. Sin embargo, puede cambiar su visibilidad, como en este ejemplo:
clase Hijo: Padre() {
La propiedad c ahora se puede ver en
anulación pública var c = 6
cualquier lugar donde la clase Child sea visible.
}
De forma predeterminada, los constructores de clases son públicos, por
lo que son visibles en todos los lugares donde la clase es visible. Sin
embargo, puede cambiar la visibilidad de un constructor especificando un modificador
de visibilidad y prefijando el constructor con la palabra clave constructor. Si, por
ejemplo, tiene una clase definida como:
De forma predeterminada, el constructor principal de MyClass es público.
clase MiClase(x: Int)
puede hacer que su constructor sea privado usando el siguiente código:
constructor privado de clase MyClass (x: Int)
Este código hace que el constructor principal sea privado.
estas aqui 4 419
Machine Translated by Google
enumeraciones
3. Clases de enumeración
Una clase de enumeración le permite crear un conjunto de valores que representan los únicos
valores válidos para una variable.
Supongamos que desea crear una aplicación para una banda y desea asegurarse de que
a una variable, selectedBandMember, solo se le pueda asignar un valor para un miembro
válido de la banda. Para realizar este tipo de tarea, podemos crear una clase de
enumeración llamada BandMember que contenga los valores válidos:
La clase enum tiene tres valores:
enum class miembro de la banda { JERRY, BOBBY, PHIL } JERRY, BOBBY y PHIL.
Luego podemos restringir la variable selectedBandMember a uno de estos valores
especificando su tipo como BandMember así:
diversión principal(argumentos: Array<String>) { El tipo de variable es
BandMember... Cada valor en una clase de
var seleccionadoBandMember: BandMember
miembroBandMiembroSeleccionado = MiembroBanda.JERRY enumeración es un
}
...para que podamos
asignarle uno de los valores
constante.
Constructores de enumeración de BandMember.
Una clase de enumeración puede tener un constructor, que se utiliza para inicializar
cada valor de enumeración. Esto funciona porque cada valor definido por la clase de
Cada enumeración
enumeración es una instancia de esa clase.
constante existe como
Para ver cómo funciona esto, supongamos que queremos especificar el instrumento que
toca cada miembro de la banda. Para hacer esto, podemos agregar una variable String
una única instancia de esa
llamada instrumento al constructor BandMember e inicializar cada valor en la clase con un
valor apropiado. Aquí está el código: clase de enumeración.
enum class BandMember(val instrument: String) {
JERRY ("guitarra solista"),
Esto define una propiedad llamada instrumento en el
BOBBY ("guitarra rítmica"), constructor BandMember. Cada valor de la clase de
PHIL("bajo") enumeración es una instancia de BandMember, por lo
} que cada valor tiene esta propiedad.
Luego podemos averiguar qué instrumento toca el miembro de la banda seleccionado
accediendo a su propiedad de instrumento de esta manera:
diversión principal(argumentos: Array<String>) {
var seleccionadoBandMember: BandMember
miembroBandMiembroSeleccionado = MiembroBanda.JERRY
println(miembrobandaseleccionado.instrumento) Esto produce la salida “guitarra solista”.
}
420 apéndice iii
Machine Translated by Google
sobras
propiedades y funciones de enumeración
En el ejemplo anterior, agregamos una propiedad a la clase BandMember
incluyéndola en el constructor de la clase enum. También puede agregar
propiedades y funciones al cuerpo principal de la clase. El siguiente código,
por ejemplo, agrega una función sings a la clase de enumeración
BandMember:
enum class BandMember(val instrument: String) {
JERRY ("guitarra solista"),
BOBBY ("guitarra rítmica"),
Tenga en cuenta que necesitamos un ";" para separar la función sings() de los valores de enumeración.
PHIL("bajo");
diversión canta() = "ocasionalmente"
Cada valor de enumeración tiene una función llamada
}
sings() que devuelve la cadena "ocasionalmente".
Cada valor definido en una clase de enumeración puede anular las
propiedades y funciones que hereda de la definición de clase. Así es
como, por ejemplo, puede anular la función de cantos para JERRY y
POLI:
enum class BandMember(val instrument: String) {
JERRY ("guitarra solista") {
anula los cantos divertidos () = "lastimeramente"
}, JERRY y BOBBY tienen su propia
BOBBY("guitarra rítmica") { implementación de sings().
anula los cantos divertidos() = "roncamente"
},
PHIL("bajo");
open fun sings() = "ocasionalmente"
Como estamos anulando sings() para dos
} valores, debemos marcarlo como abierto.
Luego podemos averiguar cómo canta el miembro de la banda seleccionado
llamando a su función sings de esta manera:
diversión principal(argumentos: Array<String>) {
var seleccionadoBandMember: BandMember
miembroBandMiembroSeleccionado = MiembroBanda.JERRY
println(miembrobandaseleccionado.instrumento)
Esta línea llama a la función sings() de JERRY y
println(miembro de la banda seleccionado. canta())
produce la salida "lastimeramente".
}
estas aqui 4 421
Machine Translated by Google
sello o no sello
4. Clases selladas
Ya ha visto que las clases de enumeración le permiten crear un conjunto
restringido de valores, pero hay algunas situaciones en las que necesita un poco
más de flexibilidad.
Suponga que desea poder utilizar dos tipos de mensajes diferentes en su
aplicación: uno para "éxito" y otro para "fracaso". Desea poder restringir los
mensajes a estos dos tipos.
Si tuviera que modelar esto usando una clase de enumeración, su código
podría verse así:
enum class MessageType(var msg: String) {
ÉXITO ("¡Sí!"),
La clase de enumeración MessageType
FRACASO("¡Buu!") tiene dos valores: SUCCESS y FAILURE.
}
Pero hay un par de problemas con este enfoque:
¥ Cada valor es una constante que solo existe como una única instancia.
Por ejemplo, no puede cambiar la propiedad msg del valor SUCCESS en una situación, ya que este
cambio se verá en todas partes de su aplicación.
¥ Cada valor debe tener las mismas propiedades y funciones.
Puede ser útil agregar una propiedad de Excepción al valor de FALLO para que pueda examinar qué
salió mal, pero una clase de enumeración no lo permitirá.
Entonces, ¿cuál es la solución?
¡Clases selladas al rescate!
Una solución a este tipo de problema es utilizar una clase sellada. Una clase
sellada es como una versión mejorada de una clase de enumeración. Le permite
restringir su jerarquía de clases a un conjunto específico de subtipos, cada uno de
los cuales puede definir sus propias propiedades y funciones. Y a diferencia de una
clase de enumeración, puede crear varias instancias de cada tipo.
Una clase sellada se crea anteponiendo el nombre de la clase con el prefijo sellada.
El siguiente código, por ejemplo, crea una clase sellada denominada
MessageType, con dos subtipos denominados MessageSuccess y
MessageFailure. Cada subtipo tiene una propiedad String denominada msg, y
el subtipo MessageFailure tiene una propiedad Exception adicional denominada MessageSuccess y MessageFailure
e: heredan de MessageType y definen
sus propias propiedades en sus
clase sellada MessageType MessageType está sellado. constructores
clase MessageSuccess(var msg: String) : MessageType()
class MessageFailure(var msg: String, var e: Exception) : MessageType()
422 apéndice iii
Machine Translated by Google
sobras
Cómo usar las clases selladas
Como dijimos, una clase sellada le permite crear múltiples instancias de cada
subtipo. El siguiente código, por ejemplo, crea dos instancias de MessageSuccess
y una sola instancia de MessageFailure:
diversión principal(argumentos: Array<String>) {
val mensajeÉxito = MensajeÉxito("¡Yay!")
val mensajeÉxito2 = MensajeÉxito("¡Funcionó!")
val messageFailure = MessageFailure("¡Boo!", Exception("Salió mal."))
A continuación, puede crear una variable MessageType y asignarle uno de estos
mensajes:
diversión principal(argumentos: Array<String>) {
val mensajeÉxito = MensajeÉxito("¡Yay!")
val mensajeÉxito2 = MensajeÉxito("¡Funcionó!")
val messageFailure = MessageFailure("¡Boo!", Exception("Salió mal."))
messageFailure es un subtipo de
var myMessageType: MessageType = messageFailure
MessageType, por lo que podemos
} asignarlo a myMessageType.
Y como MessageType es una clase sellada con un conjunto limitado de
subtipos, puede usar when para verificar cada subtipo sin requerir una
cláusula else adicional usando un código como este:
diversión principal(argumentos: Array<String>) {
val mensajeÉxito = MensajeÉxito("¡Yay!")
val mensajeÉxito2 = MensajeÉxito("¡Funcionó!")
val messageFailure = MessageFailure("¡Boo!", Exception("Salió mal."))
var myMessageType: MessageType = messageFailure myMessageType solo puede tener un tipo
val myMessage = cuando (myMessageType) { de MessageSuccess o MessageFailure, por lo
que no hay necesidad de una cláusula else adicional.
es MessageSuccess > myMessageType.msg
" "
es MessageFailure > myMessageType.msg + + myMessageType.e.message
println(miMensaje)
Puede obtener más información sobre la creación y el uso de clases selladas aquí:
https://kotlinlang.org/docs/reference/sealedclasses.html
estas aqui 4 423
Machine Translated by Google
clases anidadas e internas
5. Clases anidadas e internas
Una clase anidada es una clase que se define dentro de otra clase. Esto puede
ser útil si desea proporcionar a la clase externa una funcionalidad adicional
Una clase anidada
que está fuera de su propósito principal, o acercar el código a donde se está
utilizando.
en Kotlin es como una
Una clase anidada se define colocándola dentro de las llaves de la clase
exterior. El siguiente código, por ejemplo, define una clase llamada Outer clase anidada estática en Java.
que tiene una clase anidada llamada Nested:
clase exterior {
val x = "Esto está en la clase Exterior"
clase anidada {
Esta es la clase anidada. Está
val y = "Esto está en la clase anidada"
completamente encerrado por la clase exterior.
fun myFun() = "Esta es la función anidada"
}
}
A continuación, puede consultar la clase anidada y sus propiedades y
funciones mediante un código como este:
diversión principal(argumentos: Array<String>) {
val nested = Exterior.Anidado() Crea una instancia de Nested y la
println(anidado.y) println(anidado.myFun()) asigna a una variable.
Tenga en cuenta que no puede acceder a una clase anidada desde una
instancia de la clase externa sin crear primero una propiedad de ese tipo dentro
de la clase externa. El siguiente código, por ejemplo, no se compilará:
val anidado = Exterior().Anidado() Esto no se compilará ya que estamos usando Outer(), no Outer.
Otra restricción es que una clase anidada no tiene acceso a una instancia
de la clase externa, por lo que no puede acceder a sus miembros. No puede
acceder a la propiedad x de Outer desde la clase anidada, por ejemplo, por lo
que el siguiente código no se compilará:
clase exterior {
val x = "Esto está en la clase Exterior"
clase anidada {
divertido getX() = "El valor de x es: $x" Nested no puede ver x como está definido en la
} clase Outer, por lo que esta línea no se compilará.
}
424 apéndice iii
Machine Translated by Google
sobras
Una clase interna puede acceder a los miembros de la clase externa
Si desea que una clase anidada pueda acceder a las propiedades y
funciones definidas por su clase externa, puede hacerlo convirtiéndola en
una clase interna. Para ello, prefije la clase anidada con inner.
Aquí hay un ejemplo:
clase exterior {
val x = "Esto está en la clase Exterior"
clase interna interna {
Una clase interna es una clase anidada que
val y = "Esto está en la clase interna" tiene acceso a los miembros de la clase externa.
fun myFun() = "Esta es la función interna" Entonces, en este ejemplo, la clase Interior
divertido getX() = "El valor de x es: $x" tiene acceso a la propiedad x de Exterior.
}
}
Puede acceder a una clase interna creando una instancia de la clase externa y
luego usándola para crear una instancia de la clase interna. Aquí hay un ejemplo,
usando las clases Outer e Inner definidas anteriormente:
diversión principal(argumentos: Array<String>) {
val interior = Exterior().Interior() Como Inner es una clase interna, tenemos que usar Outer(), no Outer.
println(interior.y)
println(interior.miDiversión())
println(interior.getX())
}
Alternativamente, puede acceder a la clase interna instanciando una propiedad
de ese tipo en la clase externa, como en este ejemplo:
clase exterior {
val miInterior = Interior()
La propiedad myInner de
Outer contiene una referencia
clase interna interna { a una instancia de su clase Inner.
x: Cadena
...
}
y: Cadena
} Exterior
Interno
diversión principal(argumentos: Array<String>) {
Los objetos Interior y Exterior comparten un
val interior = Exterior().miInterior
vínculo especial. El Interior puede usar las
}
variables del Exterior, y viceversa.
La clave es que una instancia de clase interna siempre está vinculada a una
instancia específica de la clase externa, por lo que no puede crear un objeto
interno sin crear primero un objeto externo.
estas aqui 4 425
Machine Translated by Google
objetos
6. Declaraciones y expresiones de objetos
Hay momentos en los que desea asegurarse de que solo se pueda
crear una única instancia de un tipo determinado, como si desea usar
un solo objeto para coordinar acciones en toda una aplicación. En estas Si está familiarizado con los
situaciones, puede usar la palabra clave object para hacer una declaración patrones de diseño, una declaración
de objeto. de objeto es el equivalente Kotlin de un Singleton.
Una declaración de objeto define una declaración de clase y crea una
instancia de ella en una sola declaración. Y cuando lo incluya en el nivel
superior de un archivo o paquete fuente, solo se creará una instancia de
ese tipo.
Así es como se ve una declaración de objeto:
paquete com.hfkotlin.mipaquete
DuckManager es un objeto.
Una
objeto DuckManager { declaración de objeto
val allDucks = mutableListOf<Duck>()
define una clase y
Tiene una propiedad llamada
manada de patos divertidos () { allDucks y una función llamada herdDucks(). crea una instancia de
//Código para arrear los Patos
}
ella en una sola declaración.
}
Como puede ver, una declaración de objeto se parece a una definición
de clase, excepto que tiene el prefijo objeto, no clase.
Al igual que una clase, puede tener propiedades, funciones y bloques de
inicialización, y puede heredar de clases o interfaces. Sin embargo, no
puede agregar un constructor a una declaración de objeto. Esto se debe a
que el objeto se crea automáticamente tan pronto como se accede a él,
por lo que tener un constructor sería redundante.
Te refieres a un objeto que se crea usando una declaración de objeto
llamando directamente a su nombre, y esto te permite acceder a sus
miembros. Si quisiera llamar a la función herdDucks de DuckManager, por
ejemplo, podría hacerlo usando un código como este:
DuckManager.herdDucks()
Además de agregar una declaración de objeto al nivel superior de un
paquete o archivo fuente, también puede agregar uno a una clase. Veamos
cómo.
426 apéndice iii
Machine Translated by Google
sobras
Objetos de clase...
El siguiente código agrega una declaración de objeto, DuckFactory, a una clase
denominada Duck:
Cuando agrega una declaración de objeto a una clase, crea un objeto que pertenece a
para crear una única
esa clase. Se crea una instancia del objeto por clase y es compartida por todas las
instancias de esa clase.
instancia de ese tipo
Una vez que haya agregado una declaración de objeto, puede acceder al objeto desde la que pertenezca a la clase.
clase usando la notación de puntos. El siguiente código, por ejemplo, llama a la función de
creación de DuckFactory y asigna el resultado a una nueva variable llamada newDuck:
val newDuck = Duck.DuckFactory.create() Tenga en cuenta que accede
al objeto utilizando Duck, no Duck().
...y objetos complementarios
Se puede marcar un objeto por clase como objeto complementario mediante el prefijo
complementario . Un objeto complementario es como un objeto de clase, excepto que puede
omitir el nombre del objeto. El siguiente código, por ejemplo, convierte el objeto DuckFactory
anterior en un objeto complementario sin nombre:
pato de clase {
objeto complementario DuckFactory {
Sin embargo, puede incluir el
Si coloca el prefijo acompañante en una declaración de
objeto, ya no necesita proporcionar un nombre de objeto.
divertido crear(): Pato = Pato() nombre si lo desea.
}
}
Cuando crea un objeto complementario, accede a él simplemente haciendo
Se puede usar un objeto
referencia al nombre de la clase. El siguiente código, por ejemplo, llama a la complementario como el
función create() que está definida por el objeto complementario de Duck:
equivalente de Kotlin a los métodos
val nuevoPato = Pato.create()
estáticos en Java.
Para obtener una referencia a un objeto complementario sin nombre,
utilice la palabra clave Companion. El siguiente código, por ejemplo, crea Todas las instancias de clase
una nueva variable llamada x y le asigna una referencia al objeto complementario
de Duck: comparten todas las funciones que
val x = Pato.Compañero
agrega a un objeto complementario.
Ahora que ha aprendido acerca de las declaraciones de objetos y
los objetos complementarios, veamos las expresiones de objetos.
estas aqui 4 427
Machine Translated by Google
expresiones de objeto
Expresiones de objetos
Una expresión de objeto es una expresión que crea un objeto anónimo sobre la
marcha sin un tipo predefinido.
Suponga que desea crear un objeto que contenga un valor inicial para las
coordenadas x e y. En lugar de definir una clase de coordenadas y crear una instancia
de la misma, podría crear un objeto que use propiedades para contener los valores de
las coordenadas x e y. El siguiente código, por ejemplo, crea una nueva variable
denominada punto de partida y le asigna dicho objeto:
val punto de partida = objeto {
valor x = 0
valor y = 0 Esto crea un objeto con dos
propiedades, x e y.
}
Luego puede referirse a los miembros del objeto usando un código como este:
println("el punto de inicio es ${puntoInicio.x}, ${puntoInicio.y}")
Las expresiones de objetos se utilizan principalmente como el equivalente de las
clases internas anónimas en Java. Si está escribiendo código GUI y de repente se
da cuenta de que necesita una instancia de una clase que implemente una clase
abstracta MouseAdapter, puede usar una expresión de objeto para crear esa instancia
sobre la marcha. El siguiente código, por ejemplo, pasa un objeto a una función
llamada addMouseListener; el objeto implementa MouseAdapter y anula sus funciones
mouseClicked y mouseEntered:
La expresión del objeto en
Esta negrita es como decir "cree
ventana.addMouseListener(objeto : MouseAdapter() {
declaración... una instancia de una clase
anular diversión mouseClicked (e: MouseEvent) {
(sin nombre) que implemente
//Código que se ejecuta cuando se hace clic con el mouse
MouseAdapter y, por cierto,
} aquí está la implementación
de las funciones mouseClicked
y mouseReleased". En otras
anula la diversión mouseReleased (e: MouseEvent) {
palabras, estamos
//Código que se ejecuta cuando se suelta el mouse
...termina proporcionando la función
aquí } addMouseListener con una
abajo. }) implementación de clase y una
instancia de esa clase, justo
donde la necesitamos.
Puede encontrar más información sobre declaraciones y expresiones de objetos aquí:
https://kotlinlang.org/docs/reference/objectdeclarations.html
428 apéndice iii
Machine Translated by Google
sobras
7. Extensiones
Las extensiones le permiten agregar nuevas funciones y propiedades a un También hay bibliotecas de extensión de Kotlin
tipo existente sin tener que crear un subtipo completamente nuevo.
que puede usar para facilitar su vida de
Imagine que está escribiendo una aplicación en la que con frecuencia necesita codificación, como Anko y Android KTX para el
prefijar un Doble con "$" para formatearlo como dólares. En lugar de realizar la desarrollo de aplicaciones de Android.
misma acción una y otra vez, puede escribir una función de extensión llamada
toDollar que puede usar con Doubles. Aquí está el código para hacer esto:
Define una función denominada toDollar(), que amplía Double.
diversión Double.toDollar(): Cadena {
devolver "$$esto"
Devuelve el valor actual, con el prefijo $.
}
El código anterior especifica que una función llamada toDollar, que
devuelve una cadena, se puede usar con valores dobles. La función toma
el objeto actual (referido al uso de this), lo prefija con "$" y devuelve el
resultado.
Una vez que haya creado una función de extensión, puede usarla de la
Patrones de diseño
misma manera que usaría cualquier otra función. El siguiente código, por Los patrones de diseño
ejemplo, llama a la función toDollar en una variable Double que tiene un valor son soluciones de propósito
de 45,25: general para problemas
comunes, y Kotlin te ofrece
var doble = 45,25 formas sencillas de implementar
println(dbl.toDollar()) //imprime $45.25 algunos de estos patrones.
Las declaraciones de objetos proporcionan
Puede crear propiedades de extensión de forma similar a como crea una forma de implementar el patrón
funciones de extensión. El siguiente código, por ejemplo, crea una propiedad Singleton , ya que cada declaración crea
de extensión para cadenas denominada halfLength que devuelve la longitud una sola instancia de ese objeto.
de la cadena actual dividida por 2,0: Las extensiones se pueden usar en
lugar del patrón Decorator , ya que
val String.halfLength le permiten ampliar el comportamiento
Define una propiedad halfLength que
obtener () = longitud / 2.0 se puede usar con Strings. de las clases y los objetos. Y si está
interesado en utilizar el patrón
Y aquí hay un código de ejemplo que usa la nueva propiedad: Delegación como alternativa a la
herencia, puede obtener más
val prueba = "Esto es una prueba" información aquí:
println(prueba.halfLength) // imprime 7.0 https://kotlinlang.org/docs/reference/
delegation.html
Puede obtener más información sobre cómo usar las extensiones, incluido
cómo agregarlas a los objetos complementarios, aquí:
https://kotlinlang.org/docs/reference/extensions.html
Y puede obtener más información sobre el uso de esto aquí:
https://kotlinlang.org/docs/reference/thisexpressions.html
estas aqui 4 429
Machine Translated by Google
saltando _
8. Vuelve, rompe y continúa
Kotlin tiene tres formas de salir de un bucle. Estos son:
¥ return
Como ya sabes, esto regresa de la función envolvente.
¥ romper
Esto termina (o salta al final de) el bucle envolvente, por ejemplo:
var x = 0
var y = 0
mientras (x < 10) {
x++ Este código incrementa x, luego termina el
romper
bucle sin ejecutar la línea y++. x tiene un
y++ valor final de 1 y el valor de y sigue siendo 0.
}
¥ continuar
Esto pasa a la siguiente iteración del bucle envolvente, por ejemplo:
var x = 0
var y = 0
mientras (x < 10) {
x++
Esto incrementa x, luego vuelve a la línea while (x < 10) sin ejecutar
continuar
la línea y++. Sigue incrementando x hasta que la condición while
y++ (x < 10) es falsa. x tiene un valor final de 10 y el valor de y sigue
} siendo 0.
Usar etiquetas con pausa y continuar
Si tiene bucles anidados, puede especificar explícitamente de qué bucle
desea salir prefijándolo con una etiqueta. Una etiqueta se compone de un
nombre, seguido del símbolo @. El siguiente código, por ejemplo, presenta
dos bucles, donde un bucle está anidado dentro de otro. El ciclo externo
tiene una etiqueta denominada myloop@, que se usa en una expresión de ruptura:
myloop@ while (x < 20) {
mientras (y < 20) {
x++
romper@myloop Esto es como decir "salir del bucle
} etiquetado como myloop@ (el bucle exterior)".
}
Cuando usa break con una etiqueta, salta al final del ciclo envolvente con esta
etiqueta, por lo que en el ejemplo anterior, termina el ciclo externo. Cuando
usa continuar con una etiqueta, salta a la siguiente iteración de ese ciclo.
430 apéndice iii
Machine Translated by Google
sobras
Uso de etiquetas con retorno
También puede usar etiquetas para controlar el comportamiento de su código en
funciones anidadas, incluidas las funciones de orden superior.
Suponga que tiene la siguiente función, que incluye una llamada a forEach, que es
una función integrada de orden superior que acepta una lambda:
diversión miDiversión() {
lista("A", "B", "C", "D").paraCada {
si (es == "C") volver
Aquí, estamos usando return dentro de una lambda.
imprimir (es)
Cuando llegamos al retorno, sale de la función myFun().
}
println("Terminó miDiversión()")
}
En este ejemplo, el código sale de la función myFun cuando llega a la expresión de retorno,
por lo que la línea:
println("Terminó miDiversión()")
nunca corre
Si desea salir de la lambda pero continuar ejecutando myFun, puede agregar una etiqueta a
la lambda, a la que luego puede hacer referencia el retorno.
Aquí hay un ejemplo:
diversión miDiversión() {
listOf("A", "B", "C", "D").forEach myloop@{ if (it == "C") return@myloop
La lambda que estamos pasando a la función forEach
imprimir (es) está etiquetada como myloop@. La expresión de retorno
} de lambda usa esta etiqueta, por lo que cuando se
println("Terminó miDiversión()") alcanza, sale de lambda y regresa a su llamador (el bucle
} forEach).
Esto se puede reemplazar con una etiqueta implícita , cuyo nombre coincida con la función a
la que se pasa la lambda:
diversión miDiversión() {
lista("A", "B", "C", "D").paraCada {
si (es == "C") devuelve @ para cada uno
Aquí, estamos usando una etiqueta implícita
imprimir (es) para decirle al código que salga de la lambda y
} regrese a su llamador (el bucle forEach).
println("Terminó miDiversión()")
}
Puede encontrar más información sobre cómo usar etiquetas para controlar sus saltos de
código aquí:
https://kotlinlang.org/docs/reference/returns.html
estas aqui 4 431
Machine Translated by Google
más cosas de función
9. Más diversión con funciones
Ha aprendido mucho sobre funciones a lo largo del libro, pero hay
algunas cosas más que pensamos que debería saber.
Vararg
Si desea que una función acepte múltiples argumentos del mismo
tipo pero no sabe cuántos, puede prefijar el parámetro con vararg.
Esto le dice al compilador que el parámetro puede aceptar un número
Solo se
variable de argumentos. Aquí hay un ejemplo:
puede marcar
El prefijo vararg significa que podemos un parámetro
pasar múltiples valores para el parámetro ints.
diversión <T> valoresToList(vararg vals: T): MutableList<T> {
con vararg. Este
lista de valores: MutableList<T> = mutableListOf()
parámetro suele
para (i en vals) {
lista.añadir(i)
ser el último.
Los valores vararg se pasan a la función como una
}
matriz, por lo que podemos recorrer cada valor. Aquí,
lista de retorno
estamos agregando cada valor a MutableList.
}
Usted llama a una función con un parámetro vararg pasándole valores,
tal como lo haría con cualquier otro tipo de función. El siguiente código,
por ejemplo, pasa cinco valores Int a la función valuesToList:
val mList = valoresEnLista(1, 2, 3, 4, 5)
Si tiene una matriz de valores existente, puede pasarlos a la
función anteponiendo el nombre de la matriz con *. Esto se conoce
como el operador de propagación, y aquí hay un par de ejemplos en uso:
val miArray = arrayOf(1, 2, 3, 4, 5) Esto pasa los valores contenidos en myArray
a la función de valores a la lista.
val mList = valoresEnLista(*miArray)
val mList2 = valoresEnLista(0, *miArray, 6, 7)
Pasar 0 a la función... ...seguido por 6 y 7.
...seguido
por el
contenido de myArray...
432 apéndice iii
Machine Translated by Google
sobras
infijo
Si antepone una función con infijo, puede llamarla sin usar la notación de
punto. Aquí hay un ejemplo de una función infija:
perro de clase {
Hemos
infijo ladrido divertido (x: Int): String {
marcado
la función //Código para hacer que el Perro ladre x veces
bark() con infix. }
}
Como la función se ha marcado con un infijo, puede llamarla usando:
Ladrido de perro() 6
Esto crea un Perro y llama a su función
ladrar(), pasando a la función un valor de 6.
Una función se puede marcar con infijo si es una función miembro o de extensión
y tiene un solo parámetro que no tiene un valor predeterminado y no está
marcada con vararg.
en línea
Las funciones de orden superior a veces pueden ser un poco más lentas de
ejecutar, pero la mayoría de las veces, puede mejorar su rendimiento al
anteponer la función con el prefijo en línea, por ejemplo:
diversión en línea convertir (x: Doble, convertidor: (Doble) > Doble) : Doble {
val resultado = convertidor(x) Esta es una función que creamos
println("$x se convierte en $resultado") en el Capítulo 11, pero aquí la
resultado devuelto hemos marcado como una función en línea.
Cuando inserta una función de esta manera, el código generado elimina la
llamada a la función y la reemplaza con el contenido de la función. Elimina la
sobrecarga de llamar a la función, lo que a menudo hará que el código se ejecute
más rápido, pero detrás de escena genera más código.
Puede encontrar información adicional sobre el uso de estas técnicas, y más,
aquí:
https://kotlinlang.org/docs/reference/functions.html
estas aqui 4 433
Machine Translated by Google
interoperabilidad
10. Interoperabilidad
Como dijimos al comienzo del libro, Kotlin es interoperable con Java y el código de
Kotlin se puede transpilar a JavaScript. Si planea usar su código Kotlin con otros
idiomas, le recomendamos que lea las secciones de interoperabilidad de la
documentación en línea de Kotlin.
Interoperabilidad con Java
Puede llamar a casi todo el código Java desde Kotlin sin ningún problema.
Simplemente importe las bibliotecas que no se hayan importado automáticamente y
utilícelas. Puede leer sobre cualquier consideración adicional, como la forma en que
Kotlin trata los valores nulos provenientes de Java, aquí: https://kotlinlang.org/docs/
reference/javainterop.html De manera similar, puede obtener más información sobre
el uso de Kotlin código desde dentro de Java aquí: https://kotlinlang.org/docs/reference/
javatokotlininterop.html
Usando Kotlin con JavaScript
La documentación en línea también incluye una gran cantidad de información sobre
el uso de Kotlin con JavaScript. Si su aplicación tiene como objetivo JavaScript, por
ejemplo, puede usar el tipo dinámico de Kotlin que desactiva efectivamente el verificador
de tipos de Kotlin:
val miVariableDinámica: dinámica = ...
Puede encontrar más información sobre el tipo dinámico aquí:
https://kotlinlang.org/docs/reference/dynamictype.html
Si desea
De manera similar, la siguiente página le brinda información sobre el uso su
poder
múltiples
código
compartir
en
JavaScript de Kotlin: destino,
plataformas
le de
https://kotlinlang.org/docs/reference/jsinterop.html consulte
sugerimos
el que
Y puede obtener información sobre cómo acceder a su código Kotlin desde JavaScript
Kotlin
soporte
para
de proyectos mult
aquí: información
Puede obtener
sobre
más
https://kotlinlang.org/docs/reference/jstokotlininterop.html
Escribiendo código nativo con Kotlin proyectos multiplataforma aquí: https://kotlinlang.org/docs/reference/multiplatform.html
También puede usar Kotlin/Native para compilar código Kotlin en binarios
nativos. Para obtener más información sobre cómo hacer esto, consulte aquí:
https://kotlinlang.org/docs/reference/nativeoverview.html
434 apéndice iii
Machine Translated by Google
Índice
() (paréntesis)
simbolos argumentos y 13
expresiones booleanas y 82
&& (y operador) 81, 182, 224 @ parámetros lambda y 343
(anotación/etiqueta) 411, 430–431 // constructores de superclase y 173 ..
(comentario) 13, 16 {} (llaves) cuerpo de (operador de rango) 76–77 === (operador
clase y 94 cuerpo de función vacío y 160 de igualdad referencial) 200, 265–267 ?. (operador de
interfaces y 171 lambdas y 327 let llamada segura) 225, 231–232 > (separador) 327 ,
cuerpo y 232 función principal y 13
(separador) 65 * (operador de propagación) 432 $ (plantilla
clases anidadas y 424 Plantillas de
cadenas y 48 (operador decremento) de cadena) 48
76 . (operador de punto) 40, 96, 114,
144 ?: (operador de Elvis) 233 ==
(operador de igualdad) sobre 17,
200, 267, 271 función equals() y 192,
194–196, 265–266 funciones generadas
A
y 205 = (operador igual) 17 <> (genéricos) clases abstractas
sobre 158–161
49, 290 > (operador mayor que) 17 >=
declaración 157
(operador mayor o igual que) 17 ++
implementación 162–163
(operador de incremento) 76, 430 < herencia y 162 creación de
(operador menor que) 17 <= (operador menor o igual instancias y 157 consejos
que) 17 : (separador de nombre/tipo) 37, 135, 162,
al crear 175 funciones
173 != (operador no igual) 82, 224 !! (operador de aserción abstractas sobre 159–160, 175
no nulo) 234 ! (no operador) 82, 182 ? (tipo anulable) clases concretas y 173
222–223 || (u operador) 81, 182 implementación 162–163
interfaces y 171 polimorfismo
y 161 palabra clave abstracta
157, 159, 171 resumen
propiedades
alrededor de 159160
clases concretas y 173
implementando 162–163
inicialización y 159, 162
polimorfismo y 161 superclases
abstractas 157, 162–163 accesores
(getters) 111–113, 137, 163, 172
este es el indice 435
Machine Translated by Google
el índice
acciones 411 atributos de ejecución asincrónica 402–404
función agregarTodo() (objetos). Ver propiedades función promedio()
Interfaz MutableList 259
(Array) 252
Interfaz MutableSet 268
función agregar ()
Interfaz MutableList 257 B
Interfaz MutableSet 268 campos de respaldo 113, 172
y operador (&&) 81, 182, 224 clases base. Ver comportamiento de
dispositivos Android 3
superclases (objetos) 40, 125, 127. Ver también funciones números
corchetes angulares <> 49, 290 binarios 35 Expresiones booleanas 81–82 Pruebas booleanas,
anotaciones/etiquetas (@) 411, 430–431 Cualquier simples 17 Tipo booleano 36
superclase 193–194, 196, 202, 223 aplicaciones,
construcción. Ver aplicaciones de construcción
argumentos declaración de ruptura 430
sobre 13, 64
creación de aplicaciones
nombrado 208 y
adición de archivos a proyectos 11–12
orden de parámetros 65 funciones de
adición de funciones 13–16 descripción
sobrecarga 211 general básica 4
Clase de matriz 45, 77, 252–253, 271 construir herramientas 7
función arrayListOf() 282 función arrayOf() creación de proyectos 8–12
instalación del código de
45, 63, 146, 252–253 función arrayOfNulls() 252
prueba IDE 7 con REPL 4, 23–24 funciones
de actualización 17–21 funciones integradas
matrices
de orden superior sobre 363–364 función filter()
creando aplicaciones utilizando 46–47 364, 371–374 función filterIsInstance() 371
creando 45, 63 declarando 50–51 evaluando
filterNot() función 371, 374 filterTo() función
48 definiendo explícitamente el tipo 49
371, 374 fold() función 383–386, 389
infiriendo el tipo a partir de valores 49
foldRight() función 389 forEach() función
limitaciones de 253 recorriendo elementos
375–376, 382, 389 groupBy() función 381–
en 77 de tipos anulables 223, 253 referencias
382 map() función 372–374 función maxBy()
a objetos y 45, 49–51, 69 –71 valor de índice
365–366 función max() 365–366 función
inicial 45 almacenar valores en 45 formas de
minBy() 365–366 función min() 365–366 función
usar 252
reduceRight() 389 función sumByDouble() 367
función sumBy() 367
Array<Type> tipo 49 como
operador 185, 243, 417 aserción
Equals aserción 411 operadores
de aserción 234 aserciones 411
Tipo de byte 35
operadores de asignación 17
436 índice
Machine Translated by Google
el índice
Colección interfaz 389
C colecciones
alrededor de
función capitalize() 88
282 matrices y 252–253
conversión 184–185, 242–243
genéricos y 290–293, 297–300 funciones
atrapar bloque (intentar/atrapar) 240–241, 244 de orden superior y 364–367, 371–376, 381–386, 389
atrapar excepciones 239–240 características
Biblioteca estándar de Kotlin 254
(objetos). Ver propiedades caracteres (tipo) 36
Interfaz de lista 255–256, 263, 271
Char type 36 ClassCastException 242–243 Interfaz de mapa 255, 276–280
Interfaz MutableList 255, 257–259, 263, 291–293,
300
Interfaz MutableMap 255, 278–280, 297
clases Interfaz MutableSet 255, 264, 268–269, 298–299
acerca de 91–92 Establecer interfaz 255, 264–269, 271, 282 dos
resumen 157–163
puntos (:) 37, 135, 162, 173 coma (,) 65 comentarios,
adición a proyectos 133, 143
barra inclinada y 13, 16 palabra clave complementaria
construcción 133–140 protocolos
comunes para 145, 150, 156, 161, 171 datos concretos 158, 427
163, 173, 300. Consulte clases de datos que definen 92–94
que definen propiedades en el cuerpo principal 106 que
Interfaz comparable 366 operadores
definen sin constructores 109 que diseñan 93 enum 420–421
de comparación 17, 192, 194–196, 200 funciones de
genéricos y 293–296, 302, 308, 313, 315 herencia. Véase
herencia funciones miembro internas 425 y 94, 96 anidadas componenteN 199 clases concretas 158, 163, 173, 305
424–425 externas 424–425 prefijando con subclases abiertas
134, 137 selladas 422–423. Ver subclases superclases. Ver
funciones concretas 171
superclases como plantillas 91–92, 95, 175 consejos al crear
175 modificadores de visibilidad y 419 palabra clave de clase bifurcación condicional if
94 Función clear() Interfaz MutableList 259 Interfaz expresión 20, 48, 67, 233 if declaración
MutableMap 279 Interfaz MutableSet 268 17–19 función principal usando 16
pruebas condicionales 17–18
configurar proyectos 10
constantes
clases enum y 420–421 clases
selladas y 422–423
palabra clave del constructor 209
constructores
acerca de 99–102
con valores predeterminados 207–
208 definiendo clases sin 109
definiendo propiedades 100, 102, 205 vacío
109 clases de enumeración y 420 genéricos
y 314
cierre (lambdas) 376, 389 editores
de código 7, 14
@JvmOverloads anotación 214
estas aqui 4 437
Machine Translated by Google
el índice
D
primario. Ver constructores primarios
secundarios 209, 214 modificadores de
visibilidad 419 función contiene () Clase
clases de datos
de matriz 252 Interfaz de lista 256 Interfaz de
acerca de 196, 200, 202
conjunto 264
componentesN funciones y 199
constructores con valores predeterminados 207–
208 copiar objetos de datos 198 crear objetos a
función containsKey() (mapa) 277 función
partir de 196 definir 196, 205 funciones generadas
containsValue() (mapa) 277 y 205 inicializar muchas propiedades 206
continuar declaración 430 sobrecargar funciones 211 anular el
comportamiento heredado 197, 202 parámetros
tipos genéricos contravariantes 315–316, 319
con valores predeterminados 210, 214
funciones de conversión 40
constructores primarios 205–209 reglas para 217
conversión de valores 40–42 constructores secundarios 209 ocultación de
datos 114 palabra clave de datos 196 copia de
función copy() 198, 202
objetos de datos 198 creación 196 desestructuración
corrutinas
199 propiedades y 197 declaraciones
agregando dependencias 402–403
ejecución asíncrona 402 aplicación
de caja de ritmos 398–408 lanzando 402–
406 función runBlocking() 405 subprocesos
y 404–406
tipos genéricos covariantes 308, 319
creación de clases abstractas 175
matrices 45, 63 excepciones 242
clases abstractas 157
funciones 64 interfaces 175 objetos
95, 98–100, 196 proyectos 4, 8– arreglos 50–51 clases
12, 62 subclases 175 variables 32 134 funciones 66 objeto
98, 426–427, 429
paquetes 416 pasar valores
en orden de 207 propiedades
112 superclases 134, 139 variables
32–37, 98, 331
llaves {} cuerpo
de clase y 94 cuerpo
de función vacío y 160 interfaces y Patrón decorador 429
171 operador decremento () 76
lambdas y 327 cuerpo constructores
let y 232 función de valores predeterminados con
principal y 13 clases
207–208 parámetros con 210,
anidadas y 424
214 propiedades con 206 función
Plantillas de cadenas y 48
delay() 406
getters/setters personalizados 112
Patrón de delegación 429
clases derivadas. Ver subclases
438 índice
Machine Translated by Google
el índice
patrones de diseño 429 IllegalArgumentException 242, 244
desestructuración de objetos de datos IllegalStateException 242 NullPointerException
221, 234 reglas para 244 lanzar 234, 239,
199 signo de dólar ($) 48 operador de 244 bloque try/catch 240 Tipo de excepción
punto (.) 40, 96, 114, 144 Tipo doble 35 242 conversión explícita 185, 243 declarar
bucles dowhile 17 función downTo() 77 explícitamente variables 37 definir
código duplicado, evitando 122, 125 explícitamente el tipo de matriz 49 lanzar
valores duplicados Interfaz de lista y explícitamente excepciones 244 expresiones
263 Interfaz de mapa y 276, 280 Interfaz booleanas 81–82 encadenar llamadas seguras
de configuración y 255, 265, 269 juntas 226–227 si 20, 48, 67, 233 lambda. Ver
objetos lambdas 428 valores devueltos y 245
shouldBe 412 simplificación con let 232 Plantillas
de cadenas que evalúan 48 extensiones 429
mi
otra cláusula
si expresión 20 si
declaración 19
cuando declaración 183
Operador Elvis (?:) 233
constructores vacíos 109
F
cuerpo de función vacío 160
propiedades de entradas
Interfaz de mapa 280, 389
campo, respaldo 113, 172
Interfaz MutableMap 280 clases
administración de archivos 11–12, 14–
de enumeración 420–421
15 función filter() 364, 371–374 función
operador de igualdad (==)
sobre 17, 200, 267, 271 clase filterIsInstance() 371 función filterNot()
de datos y 205 función 371, 374 función filterTo() 371, 374
equals() y 192, 194–196, 265–266 función equals() sobre palabra clave final 139 bloque final
192–194 clase de datos y 205 anulando 196–197, 202 , 267
241, 244
Tipo flotante 35
Establecer interfaz y 265 función fold() 383–386, 389 función
operador igual (=) 17 foldRight() 389 función forall() 413
excepciones sobre 221, función forEach() 375–376, 382,
239, 242, 245 atrapando 239–
389 bucles for alrededor de 16–17, 76–77
240
comando println en 75 hasta la cláusula 76 –77
ClassCastException 242–243
creando 242 definiendo 242
finalmente bloque 240
estas aqui 4 439
Machine Translated by Google
el índice
barra diagonal (//) 13, 16
GRAMO
nombres completos 417
programación funcional 2, 355 funciones generadas, propiedades y 205
funciones. Consulte también funciones genéricos y tipos genéricos
específicas sobre 60 resumen 159–
alrededor de 290–291, 306,
163, 171, 173, 175 acceso para tipos 374 clases y 293–296, 302, 308, 313, 315
anulables 224–225 argumentos y 13, 64– colecciones y 290–293, 297–300 compilador
65 llamadas a referencias de objetos 144 que infiere 299–300 constructores y 314
funciones de componenteN 199 concretas
171 contravariante 315–316, 319
covariante 308, 319 funciones y
conversión 40
293, 297–298, 300 interfaces y 293,
creando 64 305–308, 315 invariante 316, 319
declarando 66
con valores predeterminados
Enfoque de Java versus Kotlin 319
210 clases de enumeración objetos anulables 302 y 299, 307, 314
y 421 extensiones polimorfismo y 293, 307 prefijo con en
agregando 429 generado 290, 315–316 prefijo sin 290, 308, 315
205 genéricos y 293, 297–298, 300 propiedades y 297 restricción a tipos
de orden superior 339–343, 355, 433 específicos 296 subtipos y 308, 315
infijo 433 herencia y 122, 125–127,
supertipos y 296, 308, 315 parámetros
144– 145 interfaz 171, 173, 175 lambdas y 339– de tipo y 292 formas de usar 293–294
343, 345, 347–352 función principal 13–14, 16, la función get()
21 miembro 94, 96 comportamiento de objeto y
40 de objetos 93 sobrecarga 150, 211 anulación.
Ver parámetros de funciones anuladas y 64–
65, 147, 150, 210, 214, 308 argumentos de
paso y 64–65 polimorfismo y 147 prefijo con Interfaz de lista 256
final 139 prefijo con tipos de retorno abiertos
Interfaz de mapa 277
137 y 66, 147, 211, 222 sin valores de retorno
captadores (accesorios) 111–113, 137, 163, 172
66, 376 con valores de retorno 66, 68 expresión única
66 Plantillas de cadena llamando 48 suspendible 406 función getValue() (mapa) 277 GlobalScope.launch
actualizando 4, 17–21 tipos de función 331, 352–354 403–406
palabra clave divertida 13 Herramienta de compilación Gradle 398–400
Operador mayor que (>) 17
Operador mayor o igual que (>=) 17 Función
groupBy() 381–382
H
Prueba HASA 129
función hashCode() 194, 196–197, 202, 266–267
códigos hash 265–267
función hashMapOf() 282
Índice 440
Machine Translated by Google
el índice
números hexadecimales 35 Prueba ISA 129–130, 169, 193
funciones de orden superior polimorfismo y 147 propiedades y
122, 125–127, 145 subtipos y 145
sobre 339, 389
colecciones integradas inicialización
363–394 y 364
programación funcional y 355 prefijo en propiedades abstractas y 159, 162
línea 433 lambdas y 339–343 propiedades de interfaz y 172 objetos y
99, 107 propiedades y 99, 106, 108
propiedad 206 superclases y 136 variables
I y 37
if expresión
sobre 20 bloques inicializadores 107, 136
else cláusula 20
init palabra clave 107
tipos anulables y 233 simple
en palabra clave 290, 315–316
67
Plantillas de cadenas que evalúan arreglos 48 palabra clave en línea 433
si declaración clases internas 425
about 19 en operator 81
else cláusula 19
instalando IntelliJ IDEA IDE 4, 7 instancias.
es operador y 182
Ver variables de instancia de objetos. ver
excepción de argumento ilegal 242, 244
propiedades
IllegalStateException 242
instanciación
inmutabilidad de clases 202 de
clases abstractas y 157
tipos de colección 255–256,
interfaces y 170
263–264, 282 etiquetas implícitas 431 declaración de
IntelliJ IDEA IDE
importación 401, 417 operador de incremento (++) 76, 430
instalación 4, 7
índice (índices) 45, 58, 77, 255–258 indexOf( ) función procesamiento Ejecutar comando 15
(Lista) 256 palabra clave infija 433 Menú de herramientas 23
concha interactiva. Ver REPL
interfaces
acerca de
170 definición
herencia
de 171 funciones en 171, 173,
unas 122
175 genéricos y 293, 305–308, 315
clases abstractas y 162
implementación de 174 herencia y 175
Cualquier superclase y 193–194, 196 evitar creación de instancias y 170 convenciones
código duplicado con 122, 125 construir jerarquía de nomenclatura 175 polimorfismo y 170,
de clase 133–140 jerarquía de clase usando 144–
181 propiedades en 171–175 consejos
150 diseñar estructura de clase 123–130
al crear 175 modificadores de visibilidad
funciones y 122, 125–127, 144–145
y 419
HASA prueba 129
interfaces y 175
estas aqui 4 441
Machine Translated by Google
el índice
modificador interno 418–419 etiquetado 431
interoperabilidad 434 parámetros y 327, 331–332, 339–343 atajos para
328, 342, 345 variables y 328, 331–333, 376
Tipo int 35
palabra clave lateinit 108 función de lanzamiento
tipos genéricos invariantes 316, 319
403–406
función de invocación () 328
Prueba ISA 129–130, 169, 193 es
operador menor que (<) 17
operador 181–184, 242
operador menor o igual que (<=) 17 palabra
Interfaz iterable 389
clave let 231–232, 333 vinculación de variables
palabra clave it 231–232, 332–333, 340, 376
a objetos. Consulte las referencias de objetos. Interfaz de
j lista 255–256, 263, 271, 371–374, 386, 389 Función listOf() (Lista)
256, 263 Tipo genérico localmente contravariante 316 Tipo
Bibliotecas Java 401 genérico localmente covariante 319 Variables locales 64, 72
Lenguaje de programación Java 319, 434
JavaScript 3, 434
Máquinas virtuales Java (JVM) 3 Construcciones
Biblioteca JUnit 410–412 de bucle de tipo largo
35 dowhile 17
@JvmOverloads anotación 214
para 16–17, 75–77
JVM (Java Virtual Machines) 3
etiquetando 430
k función principal usando 16
while 17–18
propiedad de claves (mapa) 280, 389
METRO
pares clave/valor 276–277, 280, 297 paquete
función principal
kotlin.collections 254
acerca de
Bibliotecas de extensión de Kotlin 429
13 agregando a la aplicación
paquete kotlin 254
14 bifurcación condicional en 16
Lenguaje de programación Kotlin 2–3 bucles en 16 declaraciones sin
Biblioteca estándar de Kotlin 254 parámetros 13 en 16 actualización
21 ) 276 Función Math.random() 47
Biblioteca KotlinTest 412–413
Función maxBy() 365–366 Función
extensión de archivo kt 12
max() 252, 365–366 Funciones miembro
L (métodos) 94, 96 Función minBy() 365–366
etiquetas/anotaciones (@) 411, 430–431
lambdas
sobre 325–327, 345 cierre
y 376, 389 programación
funcional y 355 funciones y 339–343, 345,
347–352 invocación 328–330
442 Índice
Machine Translated by Google
el índice
función min() 252, 365–366
modificadores, visibilidad 418–419
O
mutabilidad de arreglos 253 de tipos declaraciones de objeto 98, 426–427, 429
de colección 255, 282
expresiones de objeto 428 palabra clave de
objeto 426 referencias de objetos matrices y 45,
Interfaz MutableList 255, 257–259, 263, 291–293, 300 Función
49–51, 69–71 asignación de funciones 33–34,
mutableListOf() (MutableList) 257, 291, 300
38, 98 llamadas a 144 eliminación de
Interfaz mutableMap 255, 278–280, 297 Función variables 220–221 eliminación usando nulo
mutableMapOf() (mapa) 278 221 objetos clases abstractas y 157
Interfaz MutableSet 255, 264, 268–269, 298–299 Función constructores y 99–102 creación 95, 98–
100 creación a partir de clases de datos
mutableSetOf() (MutableSet) 268 mutadores (establecedores)
196 definición de tipos 92 función igual y 192
111–113, 137, 163, 172 funciones de 93 genéricos y 299, 307, 314
inicialización 99, 107 propiedades de. Ver
norte propiedades abriendo REPL 23 palabra
clave abierta 134, 137, 139, 159 u operador
argumentos con nombre 208 (||) 81, 182
convenciones de nomenclatura para interfaces
175 variables de nomenclatura 16, 32, 38 código
nativo 3, 434
clases anidadas 424–425
función nextInt() (aleatoria) 48 operador
no igual (!=) 82, 224 tipo Nothing 245
operador de aserción no nulo (!!) 234
operador no (!) 82, 182 tipos anulables clases externas 424–425
funciones de acceso 224–225 propiedades out palabra clave 290, 308, 315
de acceso 224– 225 matrices de 223, 253 funciones de sobrecarga 150, 211
código de ejecución condicional 231 funciones anuladas
genéricos y 302 llamadas seguras y clases de datos y 197
225–228 formas de usar 222–223 interfaces y 173 palabra
NullPointerException 221, 234, 242 clave abierta y 134, 139 funciones
sobrecargadas versus 211 reglas para
138, 267 subclases y 122, 126, 150
formas de usar 138 propiedades anuladas
valor nulo
alrededor de 78 interfaces y 173
comprobando 81 palabra clave abierta y 134, 139
tipos anulables y 221–224 llamadas subclases y 122, 126 palabras clave
seguras y 225–228 val y var 137, 150 formas de usar 136–
137 palabra clave anulada 136–138
estas aqui 4 443
Machine Translated by Google
el índice
comando de impresión
PAG
18 comando println
aproximadamente
paquetes 254, 259, 416–418
13 en bucle for 75
ejecución paralela 402–404
imprimir frente a
parámetros 18 función printStackTrace() 242
alrededor de
modificador privado 418–419
64–65 con valores predeterminados
210, 214 constructores vacíos y proyectos agregando clases a 133,
109 funciones y 64–65, 147, 150, 210, 214, 308 143 agregando archivos a 11–
lambdas y 327, 331–332, 339–343 variables locales y 12 configurando 10 creando 4,
72 tipos anulables 222 orden de argumentos y 65 8–12, 62 especificando tipos de
prefijo con val/var 105, 120, 206 propiedades como 9 carpeta src y 11–12
105 separando múltiples 65 constructores de
superclase y 135 tipo 292 tipos de variables que
coinciden 65 paréntesis () argumentos y 13 propiedades
sobre 40, 93, 102
resumen 159–163, 173
acceso 96 asignación de
valores predeterminados a 206
constructores que definen 100, 102, 205
valores de ocultación de datos 114 objetos
Expresiones booleanas y 82 de datos y 197 declaración 112 definición
parámetros lambda y 343 en el cuerpo principal de la clase 106
constructores de superclase y 173 clases de enumeración y 421 extensiones
agregando 429 inicialización flexible 106
pasar valores para argumentos sin valores
funciones generadas y 205 genéricos y
predeterminados 208 en orden de declaración 297 herencia y 122, 125–127, 145
207
inicialización 99, 106, 108, 206 interfaz
plataformas
171–175 tipos anulables 222, 224–225
que especifican para anulación. Ver propiedades anuladas
proyectos 9 compatibles con como parámetros 105 prefijar con final
la función Kotlin 3 plus() (Array) 139 prefijar con abierto 137 Plantillas de
253 polimorfismo sobre 147, cadena que hacen referencia 48 validar
150, 161 funciones valores 111–113 modificador protegido 419
abstractas y 161 propiedades modificador público 418–419 función
putAll() (mapa) 278 función put() (mapa)
abstractas y 161
278
cualquier superclase y 193
genéricos y 293, 307 clases
independientes y 169 interfaces y
170, 181 constructores primarios
sobre 99, 214 clases de datos y 205–
209 modificador privado 419
superclases y 135–136, 173
444 Índice
Machine Translated by Google
el índice
q funciones con 66, 68
funciones sin 66, 376 propiedades
de interfaz y 172 lambdas y 331 valor
signo de interrogación (?) 222–223 nulo 78, 81 función invertida() 259
R función inversa()
Random.nextInt() función 48 generación
Clase de matriz 252
de números aleatorios 47–48 rango de números
MutableList subtipo 259
en bucle en orden inverso 77 bucle a través de Juego de piedra, papel o tijera
76 saltar números 77 operador de rango elección del juego 63–71
(..) 76–77 lectura de la entrada del usuario diseño de alto nivel 61–62
78 función readLine() 78, 81 reduce() resultado 87–88 reglas de 60
función 389 reduceRight() función 389 operador elección del usuario 75–78, 81–
de igualdad referencial (===) 200, 265–267 84 función fila() 413
función removeAll() interfaz MutableList 259
reglas
interfaz MutableSet 268
para clases de datos 217
para excepciones 244
para funciones anuladas 138, 267 función
runBlocking() 405
Ejecutar comando 15
función removeAt() (MutableList) 258 función S
remove()
Interfaz MutableList 258 operador de llamada segura (?.) 225, 231–232
Interfaz MutableMap 279 llamadas seguras
Interfaz MutableSet 268 sobre 225
REPL (shell interactivo) asignando valores con 228
alrededor de 7 abriendo encadenando juntos 226 evaluando
23 código de prueba en cadenas 226–227 moldes explícitos
4, 23–24 función de retención () seguros 243 clases selladas 422–423
Interfaz MutableList 259
constructores secundarios 209, 214 función
Interfaz MutableSet 268
declaración de devolución 430–431 set() (MutableList) 258
Establecer interfaz 255, 264–269, 271, 282, 389 función
funciones de
tipo de retorno y 66, 147, 211 tipos setOf() (Set) 264 setters (mutadores) 111–113, 137,
genéricos y 315 funciones de orden 163, 172 cortocircuito 81
superior y 366 lambdas y 347–348 tipos
anulables 222
Tipo corto 35
Unidad 66, 333 expresión shouldBe 412 función
valores devueltos shuffled() (MutableList) 259
expresiones y 20, 183, 245
estas aqui 4 445
Machine Translated by Google
el índice
función shuffle() (MutableList) 259 funciones polimorfismo y 150, 161 clases
selladas y 422–423
de expresión única 67
función sumByDouble() 367 función
Propiedad de tamaño de
sumBy() 367 función sum() (Array)
patrón Singleton 429
Clase de matriz 45, 252 252 superclases sobre 122 abstractas
Interfaz de lista 256, 263 157, 162 que declaran 134, 139
Interfaz mutableSet 269 Función funciones y 122, 125–126, 138–
sleep() 406 Smart Casts 184, 243 139, 144–145 herencia. ver
herencia
función sortBy() (MutableList) 326 función
sorted() (MutableList) 259 función sort() polimorfismo y 161 constructores
primarios 135–136, 173 propiedades y 122,
Clase de matriz 252 125–126, 136–137, 139, 145
MutableList subtipo 259 operador supertipos
de propagación (*) 432 genéricos 296, 308, 315
herencia y 145–147 polimorfismo
carpeta src
y 161 funciones suspendibles 406
agregar archivos al proyecto 11–12
archivos de código fuente en 11
T
declaraciones
if 17–19, 182
import 401, 417
clases de
función principal usando 16
when 182–183 plantillas como 91–92, 95, 175
Cuerda 47–48
estado (objetos) 40, 124. Consulte también propiedades
@Anotación de prueba 411
que almacenan valores en matrices 45 Plantillas de
pruebaintroducción xxiii
cadena 47–48 tipo de cadena 13, 36
pruebas y pruebas
Prueba HASA 129
subclases
Prueba ISA 129–130, 169, 193
sobre 122 Biblioteca JUnit 410–412
añadiendo constructores a 135 Biblioteca KotlinTest 412–413
definiendo 135 funciones y 122, Ejecutar comando y 15
125–126, 138–139, 144–147 herencia. ver herencia hilos 404–406
bloques inicializadores en 136 lanzar excepciones 234, 239, 244 lanzar
polimorfismo y 147, 161 propiedades palabra clave 244–245 función toByte() 40
y 122, 125–126, 137, 145 función toDouble() 40 función toFloat() 40
función toInt() 40, 47 función toList()
Clase de matriz 271
Interfaz de mapa 280
446 Índice
Machine Translated by Google
el índice
Interfaz MutableList 259
Interfaz MutableMap 280
Establecer interfaz 269
tu
Tipo de retorno de unidad 66, 333
función toLong() 40–41 función
prueba de unidad 410–411 hasta
toLowerCase() 88 función toMap()
la cláusula (para) 76–77 funciones
(MutableMap) 280 función toMutableList()
de actualización 4, 17–21 entrada de
Clase de matriz 271 usuario 78, 81
Interfaz MutableList 259, 271
Interfaz MutableMap 280 Función
toMutableMap() (MutableMap) 280 Función toMutableSet()
V
validación
(Array) 271
de valores de propiedad 111–113
Menú Herramientas (IntelliJ IDEA) 23 entrada del usuario 81 palabra
Función toSet() sobre 282 clave val sobre 16, 282 asignación de
lambdas a variables 328
Clase de matriz 271 declaración de matrices usando 51 definición
Interfaz de mapa 280 de propiedades y 102 captadores y definidores
Interfaz MutableSet 269 Función
114 anulación de propiedades y 137, 150
toShort() 40 Función toString() 194, variables de parámetros y 72 prefijo de
parámetros con 105, 120, 206 var contra 16,
196–197, 202 Función toTypedArray()
34, 102
Interfaz de lista 271
Establecer interfaz 271
valores
función toUpperCase() 88, 106 bloque de
asignar 32–34, 37–39 asignar
prueba (try/catch) 138, 240–241, 244–245 complemento a a llamadas seguras 228 convertir
dos 42 palabra clave typealias 353 parámetros de tipo 292 40–42 propiedad de ocultar datos
114 duplicar 255, 263, 265, 269
clases de enumeración 420 inferir tipo
de matriz de 49 inicializar para
tipos variables 37 estado de objeto y 95,
de colecciones 255–256, 263–264, 282 conversión 124 devolver 20, 66 reutilización de
de valores de 40–42 función 331, 352–354 16, 32, 34, 50–51 almacenamiento en
genérico. Ver genéricos y tipos genéricos que arreglos 45 propiedad de validación
infieren para matrices 49 que aceptan valores 111–113 propiedad de valores (mapa)
NULL 223–228, 231, 253, 302 devuelven 66, 147, 280, 389 palabra clave vararg 432
211, 222, 315 subtipos. Ver subtipos supertipos. variables
Ver supertipos variable 32–38
sobre 32, 34
asignación de valores 32–34, 37–39
estas aqui 4 447
Machine Translated by Google
el índice
Pruebas booleanas en control de versiones, IntelliJ IDEA y 7
17 comparación de opciones modificadores de visibilidad 418–419
para 183 conversión de valores
40–41 creación 32 declaración
32–37, 98, 331 inicialización W
37 instancia 102 lambdas y
cuando expresión 183
328, 331–333, 376 local 64, 72
declaración cuando 182–183
tipo de parámetro coincidente 65
nomenclatura 16, 32, 38 referencias while bucles
a objetos y Ver referencias de objetos sobre 16–17, 76, 81
prefijar con $ 48 reutilización de 16, pruebas condicionales 17–
32, 34, 50–51 tipos de 32–38 var palabra clave 18 es operador y 182
sobre 16, 282 asignar lambdas a variables 328 espacio en blanco 16 función
declarar matrices usando 50 definir propiedades
withIndex() (Array) 77 escribir getters/
y 102 getters y setters 114 lateinit palabra clave y
108 anulando propiedades y 137, 150 parámetros de setters personalizados 112
prefijo con 105, 120, 206 fundición inteligente y
184 propiedades de actualización y 96 val versus
16, 34, 102
448 Índice