Note: the list below is not exhaustive and only expresses my humble opinion. Moreover, some language features should be used with extreme caution. When abused, they can make the code less readable. For example, when you are trying to compress everything into one unreadable expression.
Philipp Hauer
Java idioms or patterns | Implementation in Kotlin |
---|---|
Optional | Nullable values |
Getters, setters, Backing field | Properties (properties) |
Static class for utilities | Top-level functions, extension functions |
Immutability, Value Objects | data class with immutable properties, copy() |
Fluent Setter (Wither) | Named arguments, and arguments with a default value, apply() |
Method Chaining | Arguments with default value |
Singleton | object |
Delegation | Delegating properties by |
Lazy initialization (thread safe) | Delegating properties by: lazy() |
Observer | Delegating properties by: Delegates.observable() |
val
for variables and properties, immutable data classes
, copy()
if
, when
and try-catch
are expressions. You can combine them with other expressions and functions.
// Don't fun getDefaultLocale(deliveryArea: String): Locale { val deliverAreaLower = deliveryArea.toLowerCase() if (deliverAreaLower == "germany" || deliverAreaLower == "austria") { return Locale.GERMAN } if (deliverAreaLower == "usa" || deliverAreaLower == "great britain") { return Locale.ENGLISH } if (deliverAreaLower == "france") { return Locale.FRENCH } return Locale.ENGLISH }
// Do fun getDefaultLocale2(deliveryArea: String) = when (deliveryArea.toLowerCase()) { "germany", "austria" -> Locale.GERMAN "usa", "great britain" -> Locale.ENGLISH "france" -> Locale.FRENCH else -> Locale.ENGLISH }
if
keep in mind that it can be replaced by a shorter entry with when
.
try-catch
is also a useful expression:
val json = """{"message":"HELLO"}""" val message = try { JSONObject(json).getString("message") } catch (ex: JSONException) { json }
//Don't object StringUtil { fun countAmountOfX(string: String): Int{ return string.length - string.replace("x", "").length } } StringUtil.countAmountOfX("xFunxWithxKotlinx")
//Do fun String.countAmountOfX(): Int { return length - replace("x", "").length } "xFunxWithxKotlinx".countAmountOfX()
//Don't val config = SearchConfig() .setRoot("~/folder") .setTerm("kotlin") .setRecursive(true) .setFollowSymlinks(true)
//Do val config2 = SearchConfig2( root = "~/folder", term = "kotlin", recursive = true, followSymlinks = true )
apply()
to combine object initialization calls //Don't val dataSource = BasicDataSource() dataSource.driverClassName = "com.mysql.jdbc.Driver" dataSource.url = "jdbc:mysql://domain:3309/db" dataSource.username = "username" dataSource.password = "password" dataSource.maxTotal = 40 dataSource.maxIdle = 40 dataSource.minIdle = 4
apply()
extension function helps to merge object initialization code. In addition, we do not need to repeat the name of the variable over and over again.
//Do val dataSource = BasicDataSource().apply { driverClassName = "com.mysql.jdbc.Driver" url = "jdbc:mysql://domain:3309/db" username = "username" password = "password" maxTotal = 40 maxIdle = 40 minIdle = 4 }
apply()
is also very useful when interacting with Java libraries from Kotlin .
//Don't fun find(name: String){ find(name, true) } fun find(name: String, recursive: Boolean){ }
//Do fun (name: String, recursive: Boolean = true){ }
if-null
checks.null
cumbersome and makes it easy to miss the error.
//Don't if (order == null || order.customer == null || order.customer.address == null){ throw IllegalArgumentException("Invalid Order") } val city = order.customer.address.city
null
check, stop. Kotlin provides an easier way to handle these situations. Most often, can you use a secure call ?.
or just an operator "elvis" ?:
//Do val city = order?.customer?.address?.city ?: throw IllegalArgumentException("Invalid Order")
//Don't if (service !is CustomerService) { throw IllegalArgumentException("No CustomerService") } service.getCustomer()
as?
and ?:
you can check the type, automatically convert it to a smart cast, or throw an exception if the type is not the one we expect. All in one expression!
//Do service as? CustomerService ?: throw IllegalArgumentException("No CustomerService") service.getCustomer()
!!
//Don't order!!.customer!!.address!!.city
Surely you noticed that!!
look pretty rough. It's almost like you shout at the compiler. So it does not look random. The Kotlin language developers are trying to slightly push you to find the best solution so as not to use an expression that cannot be verified by the compiler.
Kotlin in Action, Dmitry Zhemerov and Svetlana Isakova.
let()
let()
allows you to replace if
. But you need to use it with care so that the code remains readable. However, I really want you to think about using let()
.
val order: Order? = findOrder() if (order != null){ dun(order.customer) }
let()
no additional variable is needed. So further we deal with one expression:
findOrder()?.let { dun(it.customer) } //or findOrder()?.customer?.let(::dun)
data classes
. Even if they contain only one property. There is no longer any reason not to use them.
//Don't fun send(target: String){}
//Do fun send(target: EmailAddress){} // expressive, readable, type-safe data class EmailAddress(val value: String)
// Don't fun mapToDTO(entity: SnippetEntity): SnippetDTO { val dto = SnippetDTO( code = entity.code, date = entity.date, author = "${entity.author.firstName} ${entity.author.lastName}" ) return dto }
// Do fun mapToDTO(entity: SnippetEntity) = SnippetDTO( code = entity.code, date = entity.date, author = "${entity.author.firstName} ${entity.author.lastName}" ) val dto = mapToDTO(entity)
// Do fun SnippetEntity.toDTO() = SnippetDTO( code = code, date = date, author = "${author.firstName} ${author.lastName}" ) val dto = entity.toDTO()
// Don't class UsersClient(baseUrl: String, appName: String) { private val usersUrl: String private val httpClient: HttpClient init { usersUrl = "$baseUrl/users" val builder = HttpClientBuilder.create() builder.setUserAgent(appName) builder.setConnectionTimeToLive(10, TimeUnit.SECONDS) httpClient = builder.build() } fun getUsers(){ //call service using httpClient and usersUrl } }
init
block). apply()
can also help group initialization code and get by with one expression.
// Do class UsersClient(baseUrl: String, appName: String) { private val usersUrl = "$baseUrl/users" private val httpClient = HttpClientBuilder.create().apply { setUserAgent(appName) setConnectionTimeToLive(10, TimeUnit.SECONDS) }.build() fun getUsers(){ //call service using httpClient and usersUrl } }
object
for stateless implementationsobject
from Kotlin is useful when you need to implement a framework interface that does not store state. For example, the interface Converter from Vaadin 8 .
//Do object StringToInstantConverter : Converter<String, Instant> { private val DATE_FORMATTER = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm:ss Z") .withLocale(Locale.UK) .withZone(ZoneOffset.UTC) override fun convertToModel(value: String?, context: ValueContext?) = try { Result.ok(Instant.from(DATE_FORMATTER.parse(value))) } catch (ex: DateTimeParseException) { Result.error<Instant>(ex.message) } override fun convertToPresentation(value: Instant?, context: ValueContext?) = DATE_FORMATTER.format(value) }
data class
(which is preferable), or use Pair
(which is less expressive, due to the fact that the pair does not preserve the semantics)
//Do data class ServiceConfig(val host: String, val port: Int) fun createServiceConfig(): ServiceConfig { return ServiceConfig("api.domain.io", 9389) } //destructuring in action: val (host, port) = createServiceConfig()
map
elements:
//Do val map = mapOf("api.domain.io" to 9389, "localhost" to 8080) for ((host, port) in map){ //... }
listOf
, mapOf
, and infix function to
can be used to quickly create structures (such as JSON ). Of course, this is still not as compact as in Python and JavaScript , but better than in Java.
//Do val customer = mapOf( "name" to "Clair Grube", "age" to 30, "languages" to listOf("german", "english"), "address" to mapOf( "city" to "Leipzig", "street" to "Karl-Liebknecht-Straße 1", "zipCode" to "04107" ) )
data class
or object mapping to create JSON. But sometimes (including in tests), such a record is very useful.
Source: https://habr.com/ru/post/328218/