Sobre este codelab
1. Olá!
Neste codelab, você aprenderá a converter seu código de Java para Kotlin. Você também aprenderá quais são as convenções da linguagem Kotlin e como garantir que o código que está escrevendo siga essas convenções.
Este codelab é adequado para qualquer desenvolvedor que usa Java e está pensando em migrar o projeto para Kotlin. Começaremos com algumas classes Java que serão convertidas em Kotlin usando o ambiente de desenvolvimento integrado. Em seguida, analisaremos o código convertido e veremos como melhorá-lo, tornando-o mais idiomático (link em inglês), e como evitar armadilhas comuns.
O que você vai aprender
Você aprenderá a converter Java em Kotlin. Ao fazer isso, você aprenderá sobre os seguintes recursos e conceitos da linguagem Kotlin:
- Como lidar com a nulidade
- Como implementar Singletons
- Classes de dados
- Como lidar com strings
- Operador Elvis
- Desestruturação
- Propriedades comuns e de apoio
- Argumentos padrão e parâmetros nomeados
- Como trabalhar com coleções
- Funções de extensão
- Funções e parâmetros de nível superior
- Palavras-chave
let
,apply
,with
erun
Suposições
Você provavelmente já tem conhecimento sobre Java.
Pré-requisitos
- Android Studio 3.6 ou IntelliJ IDEA (em inglês)
2. Etapas da configuração
Criar um novo projeto
Se você estiver usando o IntelliJ IDEA, crie um novo projeto Java usando Kotlin/JVM.
Se estiver usando o Android Studio, crie um novo projeto sem atividades.
O código
Criaremos um objeto modelo User
e uma classe Singleton Repository
que funciona com objetos User
e expõe listas de usuários e nomes de usuário formatados.
Crie um novo arquivo com o nome User.java
em app/java/<nomedoseupacote> e cole o seguinte código:
public class User {
@Nullable
private String firstName;
@Nullable
private String lastName;
public User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
Dependendo do tipo de projeto, importe androidx.annotation.Nullable
se estiver usando um projeto Android. Caso contrário, importe org.jetbrains.annotations.Nullable
.
Crie um novo arquivo com o nome Repository.java
e cole o seguinte código:
import java.util.ArrayList;
import java.util.List;
public class Repository {
private static Repository INSTANCE = null;
private List<User> users = null;
public static Repository getInstance() {
if (INSTANCE == null) {
synchronized (Repository.class) {
if (INSTANCE == null) {
INSTANCE = new Repository();
}
}
}
return INSTANCE;
}
// keeping the constructor private to enforce the usage of getInstance
private Repository() {
User user1 = new User("Jane", "");
User user2 = new User("John", null);
User user3 = new User("Anne", "Doe");
users = new ArrayList();
users.add(user1);
users.add(user2);
users.add(user3);
}
public List<User> getUsers() {
return users;
}
public List<String> getFormattedUserNames() {
List<String> userNames = new ArrayList<>(users.size());
for (User user : users) {
String name;
if (user.getLastName() != null) {
if (user.getFirstName() != null) {
name = user.getFirstName() + " " + user.getLastName();
} else {
name = user.getLastName();
}
} else if (user.getFirstName() != null) {
name = user.getFirstName();
} else {
name = "Unknown";
}
userNames.add(name);
}
return userNames;
}
}
3. Como declarar classes de nulidade, val, var e de dados
Nosso ambiente de desenvolvimento integrado pode fazer uma boa refatoração automática do código Java em código Kotlin, mas, às vezes, precisa de uma ajudinha. Faremos isso primeiro e, em seguida, veremos o código refatorado para entender como e por que ele foi refatorado dessa maneira.
Acesse o arquivo User.java
e converta-o em Kotlin: Barra de menus -> Code -> Convert Java File to Kotlin File.
Se o ambiente de desenvolvimento integrado solicitar uma correção após a conversão, pressione Yes.
Você verá o seguinte código Kotlin:
class User(var firstName: String?, var lastName: String?)
Observe que User.java
foi renomeado como User.kt
. Os arquivos Kotlin têm a extensão .kt.
Nossa classe Java User
tinha duas propriedades: firstName
e lastName
. Cada uma tinha um método getter e setter, tornando o valor mutável. A palavra-chave Kotlin para variáveis mutáveis é var
. Portanto, o conversor usa var
para cada uma dessas propriedades. Se nossas propriedades Java tivessem apenas getters, elas seriam imutáveis e declaradas como variáveis val
. val
é semelhante à palavra-chave final
em Java.
Uma das principais diferenças entre Kotlin e Java é que a linguagem Kotlin especifica explicitamente se uma variável pode aceitar um valor nulo. Ele faz isso anexando um"?
"à declaração de tipo.
Como marcamos firstName
e lastName
como anuláveis, o conversor marcou automaticamente as propriedades como anuláveis com String?
. Se você anotar os membros Java como não nulos (usando org.jetbrains.annotations.NotNull
ou androidx.annotation.NonNull
), o conversor reconhecerá isso e os campos também serão não nulos em Kotlin.
A refatoração básica já foi concluída. No entanto, podemos escrever o código de maneira mais idiomática. Veja como fazer isso.
Classe de dados
A classe User
apenas retém dados. O Kotlin tem uma palavra-chave para classes com essa finalidade: data
. Ao marcar essa classe como data
, o compilador criará getters e setters automaticamente. Ele também deriva as funções equals()
, hashCode()
e toString()
.
Vamos adicionar a palavra-chave data
à classe User
:
data class User(var firstName: String, var lastName: String)
O Kotlin, assim como Java, pode ter um construtor principal e um ou mais construtores secundários. O usado no exemplo acima é o construtor principal da classe User. Se você estiver convertendo uma classe Java que tem vários construtores, o conversor também criará vários construtores em Kotlin automaticamente. Eles são definidos com a palavra-chave constructor
.
Se quisermos criar uma instância dessa classe, podemos fazer o seguinte:
val user1 = User("Jane", "Doe")
Igualdade
O Kotlin tem dois tipos de igualdade:
- A igualdade estrutural usa o operador
==
e chamaequals()
para determinar se duas instâncias são iguais. - A igualdade referencial usa o operador
===
e verifica se duas referências apontam para o mesmo objeto.
As propriedades definidas no construtor principal da classe de dados serão usadas para verificações de igualdade estrutural.
val user1 = User("Jane", "Doe")
val user2 = User("Jane", "Doe")
val structurallyEqual = user1 == user2 // true
val referentiallyEqual = user1 === user2 // false
4. Argumentos padrão e nomeados
Em Kotlin, podemos atribuir valores padrão a argumentos em chamadas de função. O valor padrão é usado quando o argumento é omitido. Em Kotlin, os construtores também são funções. Por isso, podemos usar argumentos padrão para especificar que o valor padrão de lastName
é null
. Para fazer isso, basta atribuir null
a lastName
.
data class User(var firstName: String?, var lastName: String? = null)
// usage
val jane = User("Jane") // same as User("Jane", null)
val joe = User("John", "Doe")
Os parâmetros da função podem ser nomeados ao chamar funções:
val john = User(firstName = "John", lastName = "Doe")
Como um caso de uso diferente, digamos que firstName
tenha null
como valor padrão e lastName
não. Nesse caso, como o parâmetro padrão precede um parâmetro sem valor padrão, você precisa chamar a função com argumentos nomeados:
data class User(var firstName: String? = null, var lastName: String?)
// usage
val jane = User(lastName = "Doe") // same as User(null, "Doe")
val john = User("John", "Doe")
5. Inicialização de objetos, objeto complementar e Singletons
Antes de continuar, confira se a classe do User
é uma data
. Vamos converter a classe Repository
em Kotlin. O resultado da conversão automática será semelhante a este:
import java.util.*
class Repository private constructor() {
private var users: MutableList<User?>? = null
fun getUsers(): List<User?>? {
return users
}
val formattedUserNames: List<String?>
get() {
val userNames: MutableList<String?> =
ArrayList(users!!.size)
for (user in users) {
var name: String
name = if (user!!.lastName != null) {
if (user!!.firstName != null) {
user!!.firstName + " " + user!!.lastName
} else {
user!!.lastName
}
} else if (user!!.firstName != null) {
user!!.firstName
} else {
"Unknown"
}
userNames.add(name)
}
return userNames
}
companion object {
private var INSTANCE: Repository? = null
val instance: Repository?
get() {
if (INSTANCE == null) {
synchronized(Repository::class.java) {
if (INSTANCE == null) {
INSTANCE =
Repository()
}
}
}
return INSTANCE
}
}
// keeping the constructor private to enforce the usage of getInstance
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
users = ArrayList<Any?>()
users.add(user1)
users.add(user2)
users.add(user3)
}
}
Vejamos o que o conversor automático fez:
- Um bloco
init
foi adicionado (Repository.kt#L50) - O campo
static
agora faz parte de um blococompanion object
(Repository.kt#L33) - A lista de
users
é anulável, já que o objeto não foi instanciado no momento da declaração (Repository.kt#L7). - O método
getFormattedUserNames()
agora é uma propriedade chamadaformattedUserNames
(Repository.kt#L11). - A iteração na lista de usuários (que inicialmente fazia parte de
getFormattedUserNames(
) tem uma sintaxe diferente da Java (Repository.kt#L15).
Antes de prosseguir, vamos limpar o código. O conversor tornou nossa lista de users
mutável que contém objetos anuláveis. A lista pode ser realmente nula, mas vamos supor que ela não pode conter usuários nulos. Então, faremos o seguinte:
- Remova
?
emUser?
da declaração de tipousers
. getUsers
precisa retornarList<User>?
O conversor automático também se divide em duas linhas de forma desnecessária as declarações de variáveis do usuário e as definidas no bloco init. Vamos colocar cada declaração de variável em uma linha. Veja como ficará nosso código:
class Repository private constructor() {
private var users: MutableList<User>? = null
fun getUsers(): List<User>? {
return users
}
val formattedUserNames: List<String?>
get() {
val userNames: MutableList<String?> =
ArrayList(users!!.size)
for (user in users) {
var name: String
name = if (user!!.lastName != null) {
if (user!!.firstName != null) {
user!!.firstName + " " + user!!.lastName
} else {
user!!.lastName
}
} else if (user!!.firstName != null) {
user!!.firstName
} else {
"Unknown"
}
userNames.add(name)
}
return userNames
}
companion object {
private var INSTANCE: Repository? = null
val instance: Repository?
get() {
if (INSTANCE == null) {
synchronized(Repository::class.java) {
if (INSTANCE == null) {
INSTANCE =
Repository()
}
}
}
return INSTANCE
}
}
// keeping the constructor private to enforce the usage of getInstance
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
users = ArrayList<Any?>()
users.add(user1)
users.add(user2)
users.add(user3)
}
}
Bloco de inicialização
Em Kotlin, o construtor principal não pode conter nenhum código. Assim, o código de inicialização é colocado em blocos init
. A funcionalidade é a mesma.
class Repository private constructor() {
...
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
users = ArrayList<Any?>()
users.add(user1)
users.add(user2)
users.add(user3)
}
}
Grande parte do código init
lida com propriedades de inicialização. Isso também pode ser feito na declaração da propriedade. Por exemplo, na versão Kotlin da classe Repository
, vemos que a propriedade dos usuários foi inicializada na declaração.
private var users: MutableList<User>? = null
Propriedades e métodos do static
Kotlin
Em Java, a palavra-chave static
é usada para campos ou funções para dizer que pertencem a uma classe, mas não a uma instância da classe. Por isso, criamos o campo estático INSTANCE
na classe Repository
. O equivalente em Kotlin para isso é o bloco companion object
. Nele, você também declararia os campos e as funções estáticas. O conversor criou e moveu o campo INSTANCE
para ele.
Como lidar com Singletons
Como precisamos de apenas uma instância da classe Repository
, usamos o padrão Singleton em Java. Com Kotlin, é possível aplicar esse padrão no nível do compilador substituindo a palavra-chave class
por object
.
Remova o construtor particular e o objeto complementar e substitua a definição da classe por object Repository
.
object Repository {
private var users: MutableList<User>? = null
fun getUsers(): List<User>? {
return users
}
val formattedUserNames: List<String>
get() {
val userNames: MutableList<String> =
ArrayList(users!!.size)
for (user in users) {
var name: String
name = if (user!!.lastName != null) {
if (user!!.firstName != null) {
user!!.firstName + " " + user!!.lastName
} else {
user!!.lastName
}
} else if (user!!.firstName != null) {
user!!.firstName
} else {
"Unknown"
}
userNames.add(name)
}
return userNames
}
// keeping the constructor private to enforce the usage of getInstance
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
users = ArrayList<Any?>()
users.add(user1)
users.add(user2)
users.add(user3)
}
}
Ao usar a classe object
, chamamos apenas funções e propriedades diretamente no objeto deste modo:
val users = Repository.users
Desestruturação
O Kotlin permite desestruturar um objeto em diversas variáveis, usando uma sintaxe chamada declaração de desestruturação. Criamos diversas variáveis e podemos usá-las de forma independente.
Por exemplo, as classes de dados são compatíveis com a desestruturação. Dessa forma, podemos desestruturar o objeto User
no loop for
como (firstName, lastName)
. Isso permite trabalhar diretamente com os valores firstName
e lastName
. Vamos atualizar o loop for
desta forma:
for ((firstName, lastName) in users!!) {
val name: String?
if (lastName != null) {
if (firstName != null) {
name = "$firstName $lastName"
} else {
name = lastName
}
} else if (firstName != null) {
name = firstName
} else {
name = "Unknown"
}
userNames.add(name)
}
6. Como lidar com a nulidade
Ao converter a classe Repository
em Kotlin, o conversor automático tornou a lista de usuários anulável porque ela não foi inicializada em um objeto quando foi declarada. Para todos os usos do objeto users
, é usado o operador de declaração não nulo !!
. Ele converte qualquer variável para um tipo não nulo e gera uma exceção se o valor for nulo. Ao usar !!
, você corre o risco de gerar exceções no tempo de execução.
Em vez disso, prefira lidar com a nulidade com um destes métodos:
- Faça uma verificação de valores nulos (
if (users != null) {...}
). - Use o operador Elvis (link em inglês)
?:
que será abordado posteriormente no codelab. - Use algumas das funções padrão do Kotlin, abordadas posteriormente no codelab.
Em nosso caso, sabemos que a lista de usuários não precisa ser anulável, já que ela é inicializada logo após a criação do objeto. Assim, podemos instanciá-lo diretamente quando o declararmos.
Ao criar instâncias de tipos de coleção, o Kotlin fornece várias funções auxiliares para tornar seu código mais legível e flexível. Estamos usando uma MutableList
para users
:
private var users: MutableList<User>? = null
Para simplificar, podemos usar a função mutableListOf()
, fornecer o tipo de elemento de lista, remover a chamada de construtor ArrayList
do bloco init
e remover a declaração de tipo explícito da propriedade users
.
private val users = mutableListOf<User>()
Também mudamos "var" para "val" porque os usuários terão uma referência imutável da lista de usuários. A referência é imutável, mas a lista é mutável (é possível adicionar ou remover elementos).
Com essas alterações, a propriedade users
agora não é nula, e podemos remover todas as ocorrências desnecessárias do operador !!
.
val userNames: MutableList<String?> = ArrayList(users.size)
for ((firstName, lastName) in users) {
...
}
Além disso, como a variável de usuários já foi inicializada, precisamos remover a inicialização do bloco init
:
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
users.add(user1)
users.add(user2)
users.add(user3)
}
Como lastName
e firstName
podem ser null
, precisamos lidar com a nulidade quando criamos a lista de nomes de usuário formatados. Como queremos exibir "Unknown"
se faltar um nome, podemos tornar o nome não nulo removendo ?
da declaração de tipo.
val name: String
Se lastName
for nulo, name
será firstName
ou "Unknown"
:
if (lastName != null) {
if (firstName != null) {
name = "$firstName $lastName"
} else {
name = lastName
}
} else if (firstName != null) {
name = firstName
} else {
name = "Unknown"
}
Isso pode ser programado de maneira mais idiomática com o operador Elvis ?:
(link em inglês). O operador Elvis retornará a expressão do lado esquerdo, se não for nula, ou a expressão do lado direito, se o lado esquerdo for nulo.
No código a seguir, user.firstName
será retornado se não for nulo. Se user.firstName
for nulo, a expressão retornará o valor do lado direito, "Unknown"
:
if (lastName != null) {
...
} else {
name = firstName ?: "Unknown"
}
7. Modelos de string e expressão if
O Kotlin facilita trabalhar com String
s com modelos de string (link em inglês). Os modelos de string permitem referenciar variáveis nas declarações de string.
O conversor automático atualizou a concatenação do nome e do sobrenome para fazer referência ao nome da variável diretamente na string usando o símbolo $
e colocar a expressão entre { }
.
// Java
name = user.getFirstName() + " " + user.getLastName();
// Kotlin
name = "${user.firstName} ${user.lastName}"
No código, substitua a concatenação de strings por:
name = "$firstName $lastName"
Em Kotlin, if
, when
, for
e while
são expressões, ou seja, retornam um valor. O ambiente de desenvolvimento integrado está mostrando um aviso de que a atribuição precisa ser retirada da if
:
Vamos seguir a sugestão do ambiente de desenvolvimento integrado e levantar a atribuição para as duas instruções if
. A última linha da instrução if será atribuída. Dessa forma, fica mais claro que a única finalidade desse bloco é inicializar o valor do nome:
name = if (lastName != null) {
if (firstName != null) {
"$firstName $lastName"
} else {
lastName
}
} else {
firstName ?: "Unknown"
}
Em seguida, enviaremos um aviso informando que a declaração name
pode ser mesclada na atribuição. Vamos aplicar isso também. Como o tipo da variável de nome pode ser deduzido, podemos remover a declaração de tipo explícito. Agora, a formattedUserNames
ficará assim:
val formattedUserNames: List<String?>
get() {
val userNames: MutableList<String?> = ArrayList(users.size)
for ((firstName, lastName) in users) {
val name = if (lastName != null) {
if (firstName != null) {
"$firstName $lastName"
} else {
lastName
}
} else {
firstName ?: "Unknown"
}
userNames.add(name)
}
return userNames
}
8. Operações em coleções
Vamos ver em detalhes o getter de formattedUserNames
e ver como podemos torná-lo mais idiomático. Agora, o código faz o seguinte:
- Cria uma nova lista de strings.
- Itera a lista de usuários.
- Cria o nome formatado de cada usuário com base no nome e no sobrenome dele.
- Retorna a lista recém-criada.
val formattedUserNames: List<String>
get() {
val userNames = ArrayList<String>(users.size)
for ((firstName, lastName) in users) {
val name = if (lastName != null) {
if (firstName != null) {
"$firstName $lastName"
} else {
lastName
}
} else {
firstName ?: "Unknown"
}
userNames.add(name)
}
return userNames
}
O Kotlin oferece uma lista extensa de transformações de coleções (link em inglês) que tornam o desenvolvimento mais rápido e seguro, expandindo os recursos da API Java Collection. Uma delas é a função map
(link em inglês). Essa função retorna uma nova lista que contém os resultados da aplicação de uma determinada função de transformação em cada elemento da lista original. Então, em vez de criar uma nova lista e iterar a lista de usuários manualmente, podemos usar a função map
e mover a lógica que tínhamos no loop for
para o corpo de map
. Por padrão, o nome do item atual da lista usado em map
é it
(link em inglês), mas, para facilitar a legibilidade, você pode substituir it
pelo nome da sua variável. No nosso caso, vamos nomeá-lo user
:
val formattedUserNames: List<String>
get() {
return users.map { user ->
val name = if (user.lastName != null) {
if (user.firstName != null) {
"${user.firstName} ${user.lastName}"
} else {
user.lastName ?: "Unknown"
}
} else {
user.firstName ?: "Unknown"
}
name
}
}
Para simplificar ainda mais, podemos remover a variável name
completamente:
val formattedUserNames: List<String>
get() {
return users.map { user ->
if (user.lastName != null) {
if (user.firstName != null) {
"${user.firstName} ${user.lastName}"
} else {
user.lastName ?: "Unknown"
}
} else {
user.firstName ?: "Unknown"
}
}
}
9. Propriedades comuns e de apoio
Notamos que o conversor automático substituiu a função getFormattedUserNames()
por uma propriedade chamada formattedUserNames
, que tem um getter personalizado. Internamente, o Kotlin ainda gera um método getFormattedUserNames()
que retorna uma List
.
Em Java, exporíamos as propriedades de classe usando funções getter e setter. O Kotlin nos permite uma diferenciação melhor entre as propriedades de uma classe, expressas com campos e funcionalidades, e as ações que uma classe pode fazer, expressas em funções. No nosso caso, a classe Repository
é muito simples e não executa nenhuma ação. Portanto, ela tem apenas campos.
A lógica que foi acionada na função getFormattedUserNames()
Java agora é acionada ao chamar o getter da propriedade formattedUserNames
Kotlin.
Embora não tenhamos um campo explícito correspondente à propriedade formattedUserNames
, o Kotlin fornece um campo de apoio automático com o nome field
que pode ser acessado por getters e setters personalizados, se necessário.
No entanto, às vezes, você quer adicionar funcionalidades extras que não são fornecidas pelo campo de apoio automático. Veja o exemplo abaixo.
Na classe Repository
, temos uma lista mutável de usuários que está sendo exposta na função getUsers()
, que foi gerada do código Java:
fun getUsers(): List<User>? {
return users
}
O problema é que, ao retornar users
, qualquer consumidor da classe do repositório pode modificar a lista de usuários, o que não é uma boa ideia. Para corrigir isso, use uma propriedade de apoio.
Primeiro, vamos renomear users
como _users
. Agora, adicione uma propriedade pública imutável que retorna uma lista de usuários. Vamos chamá-lo de users
:
private val _users = mutableListOf<User>()
val users: List<User>
get() = _users
Com essa mudança, a propriedade _users
particular se torna a propriedade de apoio da propriedade users
pública. Fora da classe Repository
, a lista _users
não pode ser modificada, porque os consumidores da classe só podem acessar a lista dos users
.
Código completo:
object Repository {
private val _users = mutableListOf<User>()
val users: List<User>
get() = _users
val formattedUserNames: List<String>
get() {
return _users.map { user ->
if (user.lastName != null) {
if (user.firstName != null) {
"${user.firstName} ${user.lastName}"
} else {
user.lastName ?: "Unknown"
}
} else {
user.firstName ?: "Unknown"
}
}
}
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
_users.add(user1)
_users.add(user2)
_users.add(user3)
}
}
10. Funções e propriedades de nível superior e de extensão
Agora, a classe Repository
sabe como calcular o nome de usuário formatado para um objeto User
. No entanto, se quisermos reutilizar a mesma lógica de formatação em outras classes, será necessário copiá-la e colá-la ou movê-la para a classe User
.
O Kotlin oferece a possibilidade de declarar funções e propriedades fora de qualquer classe, objeto ou interface. Por exemplo, a função mutableListOf()
que usamos para criar uma nova instância de um List
é definida diretamente em Collections.kt
(link em inglês) na biblioteca padrão.
Em Java, sempre que você precisar de alguma função utilitária, provavelmente criará uma classe Util
e declarará essa funcionalidade como uma função estática. Em Kotlin, você pode declarar funções de nível superior sem uma classe. No entanto, o Kotlin também permite criar funções de extensão. São funções que estendem um determinado tipo, mas são declaradas fora dele. Assim sendo, eles têm uma afinidade com esse tipo.
A visibilidade de funções e propriedades de extensão pode ser restrita usando modificadores de visibilidade. Eles restringem o uso apenas a classes que precisam das extensões e não poluem o namespace.
Para a classe User
, podemos adicionar uma função de extensão para calcular o nome formatado ou mantê-lo em uma propriedade de extensão. Ela pode ser adicionada fora da classe Repository
, no mesmo arquivo:
// extension function
fun User.getFormattedName(): String {
return if (lastName != null) {
if (firstName != null) {
"$firstName $lastName"
} else {
lastName ?: "Unknown"
}
} else {
firstName ?: "Unknown"
}
}
// extension property
val User.userFormattedName: String
get() {
return if (lastName != null) {
if (firstName != null) {
"$firstName $lastName"
} else {
lastName ?: "Unknown"
}
} else {
firstName ?: "Unknown"
}
}
// usage:
val user = User(...)
val name = user.getFormattedName()
val formattedName = user.userFormattedName
Em seguida, podemos usar as funções e propriedades de extensão como se fizessem parte da classe User
.
Como o nome formatado é uma propriedade do usuário, e não uma funcionalidade da classe Repository
, vamos usar a propriedade de extensão. Nosso arquivo Repository
ficará assim:
val User.formattedName: String
get() {
return if (lastName != null) {
if (firstName != null) {
"$firstName $lastName"
} else {
lastName ?: "Unknown"
}
} else {
firstName ?: "Unknown"
}
}
object Repository {
private val _users = mutableListOf<User>()
val users: List<User>
get() = _users
val formattedUserNames: List<String>
get() {
return _users.map { user -> user.formattedName }
}
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
_users.add(user1)
_users.add(user2)
_users.add(user3)
}
}
A biblioteca padrão do Kotlin (link em inglês) usa funções de extensão para estender a funcionalidade de várias APIs Java. Muitas funcionalidades em Iterable
e Collection
são implementadas como funções de extensão. Por exemplo, a função map
que usamos em uma etapa anterior é uma função de extensão em Iterable
.
11. Funções de escopo: let, apply, with, run, also
No código da classe Repository
, adicionamos vários objetos de usuário à lista _users
. Essas chamadas podem ser mais idiomáticas com a ajuda de funções de escopo.
Para executar o código apenas no contexto de um objeto específico, sem precisar acessar o objeto com base no nome, o Kotlin criou cinco funções de escopo: let
, apply
, with
, run
e also
. Curtos e eficientes, todas essas funções têm um receptor (this
), podem ter um argumento (it
) e podem retornar um valor. Você precisa decidir qual usar, dependendo do que deseja alcançar.
Veja uma folha de referência útil para ajudar você a se lembrar disso:
Como estamos configurando o objeto _users
no nosso Repository
, podemos tornar o código mais idiomático usando a função apply
:
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
_users.apply {
// this == _users
add(user1)
add(user2)
add(user3)
}
}
12. Resumo
Neste codelab, abordamos os princípios básicos necessários para começar a refatorar seu código do Java para o Kotlin. Essa refatoração é independente da plataforma de desenvolvimento e ajuda a garantir que o código programado seja idiomático.
O Kotlin idiomático torna a escrita do código curta e agradável. Com todos os recursos que o Kotlin oferece, há muitas maneiras de tornar seu código mais seguro, conciso e legível. Por exemplo, podemos otimizar a classe Repository
instanciando a lista _users
com usuários diretamente na declaração, eliminando o bloco init
:
private val users = mutableListOf(User("Jane", ""), User("John", null), User("Anne", "Doe"))
Falamos sobre uma grande variedade de tópicos, desde como lidar com nulidade, Singletons, strings e coleções a tópicos como funções de extensão, funções de nível superior, propriedades e funções de escopo. Passamos de duas classes Java para duas outras Kotlin que agora são assim:
User.kt
class User(var firstName: String?, var lastName: String?)
Repository.kt
val User.formattedName: String
get() {
return if (lastName != null) {
if (firstName != null) {
"$firstName $lastName"
} else {
lastName ?: "Unknown"
}
} else {
firstName ?: "Unknown"
}
}
object Repository {
private val _users = mutableListOf(User("Jane", ""), User("John", null), User("Anne", "Doe"))
val users: List<User>
get() = _users
val formattedUserNames: List<String>
get() = _users.map { user -> user.formattedName }
}
Este é um resumo das funcionalidades Java e do mapeamento para Kotlin:
Java | Kotlin |
Objeto | Objeto |
|
|
|
|
Classe que apenas retém dados | Classe |
Inicialização no construtor | Inicialização no bloco |
Campos e funções | Campos e funções declarados em um |
Classe Singleton |
|
Para saber mais sobre o Kotlin e como usá-lo na sua plataforma, confira estes recursos:
- Kotlin Koans (em inglês)
- Tutoriais sobre Kotlin (em inglês)
- Desenvolver apps Android com Kotlin: curso sem custo financeiro
- Bootcamp de Kotlin para programadores
- Kotlin para desenvolvedores de Java (em inglês): curso sem custo financeiro no modo de auditoria