Kotlin introduced the property concept, which translates to Java getters, optional setters, fields, and delegates.
import kotlin.properties.Delegates.notNull
class User {
var name = "ABC" // getter, setter, field
var surname: String by notNull() //getter, setter, delegate
val fullName: String // only getter
get() = "$name $surname"
}
// Compiles to the analog of the following Java codepublicfinalclassUser{// $FF: synthetic fieldstaticfinalKProperty[] $$delegatedProperties =...@NotNullprivateString name ="ABC";@NotNullprivatefinalReadWriteProperty surname$delegate;@NotNullpublicfinalStringgetName(){returnthis.name;}publicfinalvoidsetName(@NotNullString var1){Intrinsics.checkNotNullParameter(var1,"<set-?>");this.name = var1;}@NotNullpublicfinalStringgetSurname(){return(String)this.surname$delegate
.getValue(this, $$delegatedProperties[0]);}publicfinalvoidsetSurname(@NotNullString v){Intrinsics.checkNotNullParameter(v,"<set-?>");this.surname$delegate
.setValue(this, $$delegatedProperties[0], v);}@NotNullpublicfinalStringgetFullName(){returnthis.name +' '+this.getSurname();}publicUser(){this.surname$delegate =Delegates.INSTANCE.notNull();}}
This creates a problem when we annotate a property because if a property translates to many elements under the hood, like a getter or a field, how can we annotate a concrete one on JVM? In other words, how can we annotate a getter or a field?
class User {
@SomeAnnotation
var name = "ABC"
}
When you annotate a Kotlin element that is used to generate multiple Java elements, you can specify a use-side target for an annotation. For instance, to mark that SomeAnnotation should be used for a property field, we should use @field:SomeAnnotation.
class User {
@field:SomeAnnotation
var name = "ABC"
}
For a property, the following targets are supported:
property (annotations with this target are not visible to Java)
field (property field)
get (property getter)
set (property setter)
setparam (property setter parameter)
delegate (the field storing the delegate instance for a delegated property)
annotation class A
annotation class B
annotation class C
annotation class D
annotation class E
class User {
@property:A
@get:B
@set:C
@field:D
@setparam:E
var name = "ABC"
}
// Compiles to the analog of the following Java codepublicfinalclassUser{@D@NotNullprivateString name ="ABC";@Apublicstaticvoid getName$annotations(){}@B@NotNullpublicfinalStringgetName(){returnthis.name;}@CpublicfinalvoidsetName(@E@NotNullString var1){Intrinsics.checkNotNullParameter(var1,"<set-?>");this.name = var1;}}
When a property is defined in a constructor, an additional param target is used to annotate the constructor parameter.
class User(
@param:A val name: String
)
By default, the annotation target is chosen according to the @Target annotation of the annotation being used. If there are multiple applicable targets, the first applicable target from the following list is used:
param
property
field
Note that property annotations without an annotation target will by default use property, so they will not be visible from Java reflection.
annotation class A
class User {
@A
val name = "ABC"
}
// Compiles to the analog of the following Java codepublicfinalclassUser{@NotNullprivateString name ="ABC";@Apublicstaticvoid getName$annotations(){}@NotNullpublicfinalStringgetName(){returnthis.name;}}
An annotation in front of a class annotates this class. To annotate the primary constructor, we need to use the constructor keyword and place the annotation in front of it.
annotation class A
annotation class B
@A
class User @B constructor(
val name: String
)
// Compiles to the analog of the following Java code@ApublicfinalclassUser{@NotNullprivatefinalString name;@NotNullpublicfinalStringgetName(){returnthis.name;}@BpublicUser(@NotNullString name){Intrinsics.checkNotNullParameter(name,"name");super();this.name = name;}}
We can also annotate a file using the file target and place an annotation at the beginning of the file (before the package). An example will be shown in the JvmName section.
When you annotate an extension function or an extension property, you can also use the receiver target to annotate the receiver parameter.
annotation class Positive
fun @receiver:Positive Double.log() = ln(this)
// Java alternative
public static final double log(@Positive double $this$log) {
return Math.log($this$log);
}
Static elements
In Kotlin, we don’t have the concept of static elements, so use object declarations and companion objects instead. Using them in Kotlin is just like using static elements in Java.
import java.math.BigDecimal
class Money(val amount: BigDecimal, val currency: String) {
companion object {
fun usd(amount: Double) =
Money(amount.toBigDecimal(), "PLN")
}
}
object MoneyUtils {
fun parseMoney(text: String): Money = TODO()
}
fun main() {
val m1 = Money.usd(10.0)
val m2 = MoneyUtils.parseMoney("10 EUR")
}
However, using these objects from Java is not very convenient. To use an object declaration, we need to use the static INSTANCE field, which is called Companion for companion objects.
It’s important to know that to use any Kotlin element in Java, a package must be specified. Kotlin allows elements without packages, but Java does not.
To simplify the use of these object declaration[^53_1] methods, we can annotate them with JvmStatic, which makes the compiler generate an additional static method to support easier calls to non-Kotlin JVM languages.
// Kotlin
class Money(val amount: BigDecimal, val currency: String) {
companion object {
@JvmStatic
fun usd(amount: Double) =
Money(amount.toBigDecimal(), "PLN")
}
}
object MoneyUtils {
@JvmStatic
fun parseMoney(text: String): Money = TODO()
}
fun main() {
val money1 = Money.usd(10.0)
val money2 = MoneyUtils.parseMoney("10 EUR")
}
As we’ve discussed already, each property is represented by its accessors. This means that if you have a property name in Kotlin, you need to use the getName getter to use it in Java; if a property is read-write, you need to use the setNamesetter.
The name field is private in Box, so we can only access it using accessors. However, some libraries that use reflection require the use of public fields, which are provided using the JvmField annotation for a property. Such properties cannot have custom accessors, use a delegate, be open, or override another property.
Kotlin properties are represented by Java accessors. A typical accessor is just a property name capitalized with a get or set prefix. The only exception is Boolean properties prefixed with "is", whose getter name is the same as the property name, and its setter skips the "is" prefix.
class User {
var name = "ABC"
var isAdult = true
}
Java getters and setters can be treated as properties in Kotlin. A getter without a setter is treated like a val property. When there is both a getter and a setter, they are treated together like a var property. A setter without a getter cannot be interpreted as a property because every property needs a getter.
Marcin Moskala is a highly experienced developer and Kotlin instructor as the founder of Kt. Academy, an official JetBrains partner specializing in Kotlin training, Google Developers Expert, known for his significant contributions to the Kotlin community. Moskala is the author of several widely recognized books, including "Effective Kotlin," "Kotlin Coroutines," "Functional Kotlin," "Advanced Kotlin," "Kotlin Essentials," and "Android Development with Kotlin."
Beyond his literary achievements, Moskala is the author of the largest Medium publication dedicated to Kotlin. As a respected speaker, he has been invited to share his insights at numerous programming conferences, including events such as Droidcon and the prestigious Kotlin Conf, the premier conference dedicated to the Kotlin programming language.
Software architect with 15 years of experience, currently working on building infrastructure for AI. I think Kotlin is one of the best programming languages ever created.
Owen has been developing software since the mid 1990s and remembers the productivity of languages such as Clipper and Borland Delphi.
Since 2001, He moved to Web, Server based Java and the Open Source revolution.
With many years of commercial Java experience, He picked up on Kotlin in early 2015.
After taking detours into Clojure and Scala, like Goldilocks, He thinks Kotlin is just right and tastes the best.
Owen enthusiastically helps Kotlin developers continue to succeed.