Kotlin and Java Interoperability
https://kotlinlang.org/docs/reference/java-to-kotlin-interop.html
https://kotlinlang.org/docs/reference/java-interop.html
Calling Java code from Kotlin
Kotlin is designed with Java Interoperability in mind. Existing Java code can be called from Kotlin in a natural way, and Kotlin code can be used from
Java rather smoothly as well. In this section we describe some details about calling Java code from Kotlin.
Pretty much all Java code can be used without any issues:
import java.util.*
fun demo(source: List<Int>) {
val list = ArrayList<Int>()
// 'for'-loops work for Java collections:
for (item in source) {
list.add(item)
}
// Operator conventions work as well:
for (i in 0..source.size - 1) {
list[i] = source[i] // get and set are called
}
}
Getters and Setters
Methods that follow the Java conventions for getters and setters (no-argument methods with names starting with get
and single-argument methods with names starting with set
) are represented as properties in Kotlin.
Boolean
accessor methods (where the name of the getter starts with is
and the name of the setter starts with set
)
are represented as properties which have the same name as the getter method.
For example:
import java.util.Calendar
fun calendarDemo() {
val calendar = Calendar.getInstance()
if (calendar.firstDayOfWeek == Calendar.SUNDAY) { // call getFirstDayOfWeek()
calendar.firstDayOfWeek = Calendar.MONDAY // call setFirstDayOfWeek()
}
if (!calendar.isLenient) { // call isLenient()
calendar.isLenient = true // call setLenient()
}
}
Note that, if the Java class only has a setter, it will not be visible as a property in Kotlin, because Kotlin does not support set-only properties at this time.
Methods returning void
If a Java method returns void, it will return Unit
when called from Kotlin.
If, by any chance, someone uses that return value, it will be assigned at the call site by the Kotlin compiler,
since the value itself is known in advance (being Unit
).
Escaping for Java identifiers that are keywords in Kotlin
Some of the Kotlin keywords are valid identifiers in Java: in{: .keyword }, object{: .keyword }, is{: .keyword }, etc.
If a Java library uses a Kotlin keyword for a method, you can still call the method
escaping it with the backtick (`) character:
foo.`is`(bar)
Null-Safety and Platform Types
Any reference in Java may be null{: .keyword }, which makes Kotlin's requirements of strict null-safety impractical for objects coming from Java.
Types of Java declarations are treated specially in Kotlin and called platform types. Null-checks are relaxed for such types,
so that safety guarantees for them are the same as in Java (see more below).
Consider the following examples:
val list = ArrayList<String>() // non-null (constructor result)
list.add("Item")
val size = list.size // non-null (primitive int)
val item = list[0] // platform type inferred (ordinary Java object)
When we call methods on variables of platform types, Kotlin does not issue nullability errors at compile time,
but the call may fail at runtime, because of a null-pointer exception or an assertion that Kotlin generates to
prevent nulls from propagating:
item.substring(1) // allowed, may throw an exception if item == null
Platform types are non-denotable, meaning that one can not write them down explicitly in the language.
When a platform value is assigned to a Kotlin variable, we can rely on type inference (the variable will have an inferred platform type then,
as item
has in the example above), or we can choose the type that we expect (both nullable and non-null types are allowed):
val nullable: String? = item // allowed, always works
val notNull: String = item // allowed, may fail at runtime
If we choose a non-null type, the compiler will emit an assertion upon assignment. This prevents Kotlin's non-null variables from holding
nulls. Assertions are also emitted when we pass platform values to Kotlin functions expecting non-null values etc.
Overall, the compiler does its best to prevent nulls from propagating far through the program (although sometimes this is
impossible to eliminate entirely, because of generics).
Notation for Platform Types
As mentioned above, platform types cannot be mentioned explicitly in the program, so there's no syntax for them in the language.
Nevertheless, the compiler and IDE need to display them sometimes (in error messages, parameter info etc), so we have a
mnemonic notation for them:
-
T!
means "T
orT?
", -
(Mutable)Collection<T>!
means "Java collection ofT
may be mutable or not, may be nullable or not", -
Array<(out) T>!
means "Java array ofT
(or a subtype ofT
), nullable or not"
Nullability annotations
Java types which have nullability annotations are represented not as platform types, but as actual nullable or non-null
Kotlin types. The compiler supports several flavors of nullability annotations, including:
-
JetBrains
(@Nullable
and@NotNull
from theorg.jetbrains.annotations
package) - Android (
com.android.annotations
andandroid.support.annotations
) - JSR-305 (
javax.annotation
, more details below) - FindBugs (
edu.umd.cs.findbugs.annotations
) - Eclipse (
org.eclipse.jdt.annotation
) - Lombok (
lombok.NonNull
).
You can find the full list in the Kotlin compiler source code.
Annotating type parameters
It is possible to annotate type arguments of generic types to provide nullability information for them as well. For example, consider these annotations on a Java declaration:
@NotNull
Set<@NotNull String> toSet(@NotNull Collection<@NotNull String> elements) { ... }
It leads to the following signature seen in Kotlin:
fun toSet(elements: (Mutable)Collection<String>) : (Mutable)Set<String> { ... }
Note the @NotNull
annotations on String
type arguments. Without them, we get platform types in the type arguments:
fun toSet(elements: (Mutable)Collection<String!>) : (Mutable)Set<String!> { ... }
Annotating type arguments works with Java 8 target or higher and requires the nullability annotations to support the TYPE_USE
target (org.jetbrains.annotations
supports this in version 15 and above).
JSR-305 Support
The @Nonnull
annotation defined
in JSR-305 is supported for denoting nullability of Java types.
If the @Nonnull(when = ...)
value is When.ALWAYS
, the annotated type is treated as non-null; When.MAYBE
and
When.NEVER
denote a nullable type; and When.UNKNOWN
forces the type to be platform one.
A library can be compiled against the JSR-305 annotations, but there's no need to make the annotations artifact (e.g. jsr305.jar
)
a compile dependency for the library consumers. The Kotlin compiler can read the JSR-305 annotations from a library without the annotations
present on the classpath.
Since Kotlin 1.1.50,
custom nullability qualifiers (KEEP-79)
are also supported (see below).
Type qualifier nicknames (since 1.1.50)
If an annotation type is annotated with both
@TypeQualifierNickname
and JSR-305 @Nonnull
(or its another nickname, such as @CheckForNull
), then the annotation type is itself used for
retrieving precise nullability and has the same meaning as that nullability annotation:
@TypeQualifierNickname
@Nonnull(when = When.ALWAYS)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyNonnull {
}
@TypeQualifierNickname
@CheckForNull // a nickname to another type qualifier nickname
@Retention(RetentionPolicy.RUNTIME)
public @interface MyNullable {
}
interface A {
@MyNullable String foo(@MyNonnull String x);
// in Kotlin (strict mode): `fun foo(x: String): String?`
String bar(List<@MyNonnull String> x);
// in Kotlin (strict mode): `fun bar(x: List<String>!): String!`
}
Type qualifier defaults (since 1.1.50)
@TypeQualifierDefault
allows introducing annotations that, when being applied, define the default nullability within the scope of the annotated
element.
Such annotation type should itself be annotated with both @Nonnull
(or its nickname) and @TypeQualifierDefault(...)
with one or more ElementType
values:
-
ElementType.METHOD
for return types of methods; -
ElementType.PARAMETER
for value parameters; -
ElementType.FIELD
for fields; and -
ElementType.TYPE_USE
(since 1.1.60) for any type including type arguments, upper bounds of type parameters and wildcard types.
The default nullability is used when a type itself is not annotated by a nullability annotation, and the default is
determined by the innermost enclosing element annotated with a type qualifier default annotation with the
ElementType
matching the type usage.
@Nonnull
@TypeQualifierDefault({ElementType.METHOD, ElementType.PARAMETER})
public @interface NonNullApi {
}
@Nonnull(when = When.MAYBE)
@TypeQualifierDefault({ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE_USE})
public @interface NullableApi {
}
@NullableApi
interface A {
String foo(String x); // fun foo(x: String?): String?
@NotNullApi // overriding default from the interface
String bar(String x, @Nullable String y); // fun bar(x: String, y: String?): String
// The List<String> type argument is seen as nullable because of `@NullableApi`
// having the `TYPE_USE` element type:
String baz(List<String> x); // fun baz(List<String?>?): String?
// The type of `x` parameter remains platform because there's an explicit
// UNKNOWN-marked nullability annotation:
String qux(@Nonnull(when = When.UNKNOWN) String x); // fun baz(x: String!): String?
}
Note: the types in this example only take place with the strict mode enabled, otherwise, the platform types remain. See the
@UnderMigration
annotation and Compiler configuration sections.
Package-level default nullability is also supported:
// FILE: test/package-info.java
@NonNullApi // declaring all types in package 'test' as non-nullable by default
package test;
@UnderMigration
annotation (since 1.1.60)
The @UnderMigration
annotation (provided in a separate artifact kotlin-annotations-jvm
) can be used by library
maintainers to define the migration status for the nullability type qualifiers.
The status value in @UnderMigration(status = ...)
specifies how the compiler treats inappropriate usages of the
annotated types in Kotlin (e.g. using a @MyNullable
-annotated type value as non-null):
-
MigrationStatus.STRICT
makes annotation work as any plain nullability annotation, i.e. report errors for
the inappropriate usages and affect the types in the annotated declarations as they are seen in Kotlin; -
with
MigrationStatus.WARN
, the inappropriate usages are reported as compilation warnings instead of errors,
but the types in the annotated declarations remain platform; and -
MigrationStatus.IGNORE
makes the compiler ignore the nullability annotation completely.
A library maintainer can add @UnderMigration
status to both type qualifier nicknames and type qualifier defaults:
@Nonnull(when = When.ALWAYS)
@TypeQualifierDefault({ElementType.METHOD, ElementType.PARAMETER})
@UnderMigration(status = MigrationStatus.WARN)
public @interface NonNullApi {
}
// The types in the class are non-null, but only warnings are reported
// because `@NonNullApi` is annotated `@UnderMigration(status = MigrationStatus.WARN)`
@NonNullApi
public class Test {}
Note: the migration status of a nullability annotation is not inherited by its type qualifier nicknames but is applied
to its usages in default type qualifiers.
If a default type qualifier uses a type qualifier nickname and they are both @UnderMigration
, the status
from the default type qualifier is used.
Compiler configuration
The JSR-305 checks can be configured by adding the -Xjsr305
compiler flag with the following options (and their combination):
-
-Xjsr305={strict|warn|ignore}
to set up the behavior for non-@UnderMigration
annotations.
Custom nullability qualifiers, especially
@TypeQualifierDefault
, are already spread among many well-known libraries, and users may need to migrate smoothly when
updating to the Kotlin version containing JSR-305 support. Since Kotlin 1.1.60, this flag only affects non-@UnderMigration
annotations. -
-Xjsr305=under-migration:{strict|warn|ignore}
(since 1.1.60) to override the behavior for the@UnderMigration
annotations.
Users may have different view on the migration status for the libraries:
they may want to have errors while the official migration status isWARN
, or vice versa,
they may wish to postpone errors reporting for some until they complete their migration. -
-Xjsr305=@<fq.name>:{strict|warn|ignore}
(since 1.1.60) to override the behavior for a single annotation, where<fq.name>
is the fully qualified class name of the annotation. May appear several times for different annotations. This is useful
for managing the migration state for a particular library.
The strict
, warn
and ignore
values have the same meaning as those of MigrationStatus
, and only the strict
mode affects the types in the annotated declarations as they are seen in Kotlin.
Note: the built-in JSR-305 annotations
@Nonnull
,@Nullable
and@CheckForNull
are always enabled and affect the types of the annotated declarations in Kotlin, regardless of compiler configuration with the-Xjsr305
flag.
For example, adding -Xjsr305=ignore -Xjsr305=under-migration:ignore -Xjsr305=@org.library.MyNullable:warn
to the
compiler arguments makes the compiler generate warnings for inappropriate usages of types annotated by
@org.library.MyNullable
and ignore all other JSR-305 annotations.
For kotlin versions 1.1.50+/1.2, the default behavior is the same to -Xjsr305=warn
. The
strict
value should be considered experimental (more checks may be added to it in the future).
Mapped types
Kotlin treats some Java types specially. Such types are not loaded from Java "as is", but are mapped to corresponding Kotlin types.
The mapping only matters at compile time, the runtime representation remains unchanged.
Java's primitive types are mapped to corresponding Kotlin types (keeping platform types in mind):
Java type | Kotlin type |
---|---|
byte |
kotlin.Byte |
short |
kotlin.Short |
int |
kotlin.Int |
long |
kotlin.Long |
char |
kotlin.Char |
float |
kotlin.Float |
double |
kotlin.Double |
boolean |
kotlin.Boolean |
Some non-primitive built-in classes are also mapped:
Java type | Kotlin type |
---|---|
java.lang.Object |
kotlin.Any! |
java.lang.Cloneable |
kotlin.Cloneable! |
java.lang.Comparable |
kotlin.Comparable! |
java.lang.Enum |
kotlin.Enum! |
java.lang.Annotation |
kotlin.Annotation! |
java.lang.Deprecated |
kotlin.Deprecated! |
java.lang.CharSequence |
kotlin.CharSequence! |
java.lang.String |
kotlin.String! |
java.lang.Number |
kotlin.Number! |
java.lang.Throwable |
kotlin.Throwable! |
Java's boxed primitive types are mapped to nullable Kotlin types:
Java type | Kotlin type |
---|---|
java.lang.Byte |
kotlin.Byte? |
java.lang.Short |
kotlin.Short? |
java.lang.Integer |
kotlin.Int? |
java.lang.Long |
kotlin.Long? |
java.lang.Character |
kotlin.Char? |
java.lang.Float |
kotlin.Float? |
java.lang.Double |
kotlin.Double? |
java.lang.Boolean |
kotlin.Boolean? |
Note that a boxed primitive type used as a type parameter is mapped to a platform type:
for example, List<java.lang.Integer>
becomes a List<Int!>
in Kotlin.
Collection types may be read-only or mutable in Kotlin, so Java's collections are mapped as follows
(all Kotlin types in this table reside in the package kotlin.collections
):
Java type | Kotlin read-only type | Kotlin mutable type | Loaded platform type |
---|---|---|---|
Iterator<T> |
Iterator<T> |
MutableIterator<T> |
(Mutable)Iterator<T>! |
Iterable<T> |
Iterable<T> |
MutableIterable<T> |
(Mutable)Iterable<T>! |
Collection<T> |
Collection<T> |
MutableCollection<T> |
(Mutable)Collection<T>! |
Set<T> |
Set<T> |
MutableSet<T> |
(Mutable)Set<T>! |
List<T> |
List<T> |
MutableList<T> |
(Mutable)List<T>! |
ListIterator<T> |
ListIterator<T> |
MutableListIterator<T> |
(Mutable)ListIterator<T>! |
Map<K, V> |
Map<K, V> |
MutableMap<K, V> |
(Mutable)Map<K, V>! |
Map.Entry<K, V> |
Map.Entry<K, V> |
MutableMap.MutableEntry<K,V> |
(Mutable)Map.(Mutable)Entry<K, V>! |
Java's arrays are mapped as mentioned below:
Java type | Kotlin type |
---|---|
int[] |
kotlin.IntArray! |
String[] |
kotlin.Array<(out) String>! |
Note: the static members of these Java types are not directly accessible on the companion objects of the Kotlin types. To call them, use the full qualified names of the Java types, e.g. java.lang.Integer.toHexString(foo)
.
Java generics in Kotlin
Kotlin's generics are a little different from Java's (see Generics). When importing Java types to Kotlin we perform some conversions:
-
Java's wildcards are converted into type projections,
-
Foo<? extends Bar>
becomesFoo<out Bar!>!
, -
Foo<? super Bar>
becomesFoo<in Bar!>!
;
-
-
Java's raw types are converted into star projections,
-
List
becomesList<*>!
, i.e.List<out Any?>!
.
-
Like Java's, Kotlin's generics are not retained at runtime, i.e. objects do not carry information about actual type arguments passed to their constructors,
i.e. ArrayList<Integer>()
is indistinguishable from ArrayList<Character>()
.
This makes it impossible to perform is{: .keyword }-checks that take generics into account.
Kotlin only allows is{: .keyword }-checks for star-projected generic types:
if (a is List<Int>) // Error: cannot check if it is really a List of Ints
// but
if (a is List<*>) // OK: no guarantees about the contents of the list
Java Arrays
Arrays in Kotlin are invariant, unlike Java. This means that Kotlin does not let us assign an Array<String>
to an Array<Any>
,
which prevents a possible runtime failure. Passing an array of a subclass as an array of superclass to a Kotlin method is also prohibited,
but for Java methods this is allowed (through platform types of the form Array<(out) String>!
).
Arrays are used with primitive datatypes on the Java platform to avoid the cost of boxing/unboxing operations.
As Kotlin hides those implementation details, a workaround is required to interface with Java code.
There are specialized classes for every type of primitive array (IntArray
, DoubleArray
, CharArray
, and so on) to handle this case.
They are not related to the Array
class and are compiled down to Java's primitive arrays for maximum performance.
Suppose there is a Java method that accepts an int array of indices:
public class JavaArrayExample {
public void removeIndices(int[] indices) {
// code here...
}
}
To pass an array of primitive values you can do the following in Kotlin:
val javaObj = JavaArrayExample()
val array = intArrayOf(0, 1, 2, 3)
javaObj.removeIndices(array) // passes int[] to method
When compiling to JVM byte codes, the compiler optimizes access to arrays so that there's no overhead introduced:
val array = arrayOf(1, 2, 3, 4)
array[1] = array[1] * 2 // no actual calls to get() and set() generated
for (x in array) { // no iterator created
print(x)
}
Even when we navigate with an index, it does not introduce any overhead:
for (i in array.indices) { // no iterator created
array[i] += 2
}
Finally, in{: .keyword }-checks have no overhead either:
if (i in array.indices) { // same as (i >= 0 && i < array.size)
print(array[i])
}
Java Varargs
Java classes sometimes use a method declaration for the indices with a variable number of arguments (varargs):
public class JavaArrayExample {
public void removeIndicesVarArg(int... indices) {
// code here...
}
}
In that case you need to use the spread operator *
to pass the IntArray
:
val javaObj = JavaArrayExample()
val array = intArrayOf(0, 1, 2, 3)
javaObj.removeIndicesVarArg(*array)
It's currently not possible to pass null{: .keyword } to a method that is declared as varargs.
Operators
Since Java has no way of marking methods for which it makes sense to use the operator syntax, Kotlin allows using any
Java methods with the right name and signature as operator overloads and other conventions (invoke()
etc.)
Calling Java methods using the infix call syntax is not allowed.
Checked Exceptions
In Kotlin, all exceptions are unchecked, meaning that the compiler does not force you to catch any of them.
So, when you call a Java method that declares a checked exception, Kotlin does not force you to do anything:
fun render(list: List<*>, to: Appendable) {
for (item in list) {
to.append(item.toString()) // Java would require us to catch IOException here
}
}
Object Methods
When Java types are imported into Kotlin, all the references of the type java.lang.Object
are turned into Any
.
Since Any
is not platform-specific, it only declares toString()
, hashCode()
and equals()
as its members,
so to make other members of java.lang.Object
available, Kotlin uses extension functions.
wait()/notify()
Effective Java, 3rd Edition Item 81 kindly suggests to prefer concurrency utilities to wait()
and notify()
.
Thus, these methods are not available on references of type Any
.
If you really need to call them, you can cast to java.lang.Object
:
(foo as java.lang.Object).wait()
getClass()
To retrieve the Java class of an object, use the java
extension property on a class reference:
val fooClass = foo::class.java
The code above uses a bound class reference, which is supported since Kotlin 1.1. You can also use the javaClass
extension property:
val fooClass = foo.javaClass
clone()
To override clone()
, your class needs to extend kotlin.Cloneable
:
class Example : Cloneable {
override fun clone(): Any { ... }
}
Do not forget about Effective Java, 3rd Edition, Item 13: Override clone judiciously.
finalize()
To override finalize()
, all you need to do is simply declare it, without using the override{:.keyword} keyword:
class C {
protected fun finalize() {
// finalization logic
}
}
According to Java's rules, finalize()
must not be private{: .keyword }.
Inheritance from Java classes
At most one Java class (and as many Java interfaces as you like) can be a supertype for a class in Kotlin.
Accessing static members
Static members of Java classes form "companion objects" for these classes. We cannot pass such a "companion object" around as a value,
but can access the members explicitly, for example:
if (Character.isLetter(a)) { ... }
To access static members of a Java type that is mapped to a Kotlin type, use the full qualified name of the Java type: java.lang.Integer.bitCount(foo)
.
Java Reflection
Java reflection works on Kotlin classes and vice versa. As mentioned above, you can use instance::class.java
,
ClassName::class.java
or instance.javaClass
to enter Java reflection through java.lang.Class
.
Other supported cases include acquiring a Java getter/setter method or a backing field for a Kotlin property, a KProperty
for a Java field, a Java method or constructor for a KFunction
and vice versa.
SAM Conversions
Just like Java 8, Kotlin supports SAM conversions. This means that Kotlin function literals can be automatically converted
into implementations of Java interfaces with a single non-default method, as long as the parameter types of the interface
method match the parameter types of the Kotlin function.
You can use this for creating instances of SAM interfaces:
val runnable = Runnable { println("This runs in a runnable") }
...and in method calls:
val executor = ThreadPoolExecutor()
// Java signature: void execute(Runnable command)
executor.execute { println("This runs in a thread pool") }
If the Java class has multiple methods taking functional interfaces, you can choose the one you need to call by
using an adapter function that converts a lambda to a specific SAM type. Those adapter functions are also generated
by the compiler when needed:
executor.execute(Runnable { println("This runs in a thread pool") })
Note that SAM conversions only work for interfaces, not for abstract classes, even if those also have just a single
abstract method.
Also note that this feature works only for Java interop; since Kotlin has proper function types, automatic conversion
of functions into implementations of Kotlin interfaces is unnecessary and therefore unsupported.
Using JNI with Kotlin
To declare a function that is implemented in native (C or C++) code, you need to mark it with the external
modifier:
external fun foo(x: Int): Double
The rest of the procedure works in exactly the same way as in Java.
Calling Kotlin from Java
Kotlin code can be called from Java easily.
Properties
A Kotlin property is compiled to the following Java elements:
- A getter method, with the name calculated by prepending the
get
prefix; - A setter method, with the name calculated by prepending the
set
prefix (only forvar
properties); - A private field, with the same name as the property name (only for properties with backing fields).
For example, var firstName: String
gets compiled to the following Java declarations:
private String firstName;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
If the name of the property starts with is
, a different name mapping rule is used: the name of the getter will be
the same as the property name, and the name of the setter will be obtained by replacing is
with set
.
For example, for a property isOpen
, the getter will be called isOpen()
and the setter will be called setOpen()
.
This rule applies for properties of any type, not just Boolean
.
Package-Level Functions
All the functions and properties declared in a file example.kt
inside a package org.foo.bar
, including extension functions,
are compiled into static methods of a Java class named org.foo.bar.ExampleKt
.
// example.kt
package demo
class Foo
fun bar() { ... }
// Java
new demo.Foo();
demo.ExampleKt.bar();
The name of the generated Java class can be changed using the @JvmName
annotation:
@file:JvmName("DemoUtils")
package demo
class Foo
fun bar() { ... }
// Java
new demo.Foo();
demo.DemoUtils.bar();
Having multiple files which have the same generated Java class name (the same package and the same name or the same
@JvmName annotation) is normally an error. However, the compiler has the ability to generate a single Java facade
class which has the specified name and contains all the declarations from all the files which have that name.
To enable the generation of such a facade, use the @JvmMultifileClass annotation in all of the files.
// oldutils.kt
@file:JvmName("Utils")
@file:JvmMultifileClass
package demo
fun foo() { ... }
// newutils.kt
@file:JvmName("Utils")
@file:JvmMultifileClass
package demo
fun bar() { ... }
// Java
demo.Utils.foo();
demo.Utils.bar();
Instance Fields
If you need to expose a Kotlin property as a field in Java, you need to annotate it with the @JvmField
annotation.
The field will have the same visibility as the underlying property. You can annotate a property with @JvmField
if it has a backing field, is not private, does not have open
, override
or const
modifiers, and is not a delegated property.
class C(id: String) {
@JvmField val ID = id
}
// Java
class JavaClient {
public String getID(C c) {
return c.ID;
}
}
Late-Initialized properties are also exposed as fields.
The visibility of the field will be the same as the visibility of lateinit
property setter.
Static Fields
Kotlin properties declared in a named object or a companion object will have static backing fields
either in that named object or in the class containing the companion object.
Usually these fields are private but they can be exposed in one of the following ways:
-
@JvmField
annotation; -
lateinit
modifier; -
const
modifier.
Annotating such a property with @JvmField
makes it a static field with the same visibility as the property itself.
class Key(val value: Int) {
companion object {
@JvmField
val COMPARATOR: Comparator<Key> = compareBy<Key> { it.value }
}
}
// Java
Key.COMPARATOR.compare(key1, key2);
// public static final field in Key class
A late-initialized property in an object or a companion object
has a static backing field with the same visibility as the property setter.
object Singleton {
lateinit var provider: Provider
}
// Java
Singleton.provider = new Provider();
// public static non-final field in Singleton class
Properties annotated with const
(in classes as well as at the top level) are turned into static fields in Java:
// file example.kt
object Obj {
const val CONST = 1
}
class C {
companion object {
const val VERSION = 9
}
}
const val MAX = 239
In Java:
int c = Obj.CONST;
int d = ExampleKt.MAX;
int v = C.VERSION;
Static Methods
As mentioned above, Kotlin represents package-level functions as static methods.
Kotlin can also generate static methods for functions defined in named objects or companion objects if you annotate those functions as @JvmStatic
.
If you use this annotation, the compiler will generate both a static method in the enclosing class of the object and an instance method in the object itself.
For example:
class C {
companion object {
@JvmStatic fun foo() {}
fun bar() {}
}
}
Now, foo()
is static in Java, while bar()
is not:
C.foo(); // works fine
C.bar(); // error: not a static method
C.Companion.foo(); // instance method remains
C.Companion.bar(); // the only way it works
Same for named objects:
object Obj {
@JvmStatic fun foo() {}
fun bar() {}
}
In Java:
Obj.foo(); // works fine
Obj.bar(); // error
Obj.INSTANCE.bar(); // works, a call through the singleton instance
Obj.INSTANCE.foo(); // works too
@JvmStatic
annotation can also be applied on a property of an object or a companion object
making its getter and setter methods be static members in that object or the class containing the companion object.
Visibility
The Kotlin visibilities are mapped to Java in the following way:
-
private
members are compiled toprivate
members; -
private
top-level declarations are compiled to package-local declarations; -
protected
remainsprotected
(note that Java allows accessing protected members from other classes in the same package
and Kotlin doesn't, so Java classes will have broader access to the code); -
internal
declarations becomepublic
in Java. Members ofinternal
classes go through name mangling, to make
it harder to accidentally use them from Java and to allow overloading for members with the same signature that don't see
each other according to Kotlin rules; -
public
remainspublic
.
KClass
Sometimes you need to call a Kotlin method with a parameter of type KClass
.
There is no automatic conversion from Class
to KClass
, so you have to do it manually by invoking the equivalent of
the Class<T>.kotlin
extension property:
kotlin.jvm.JvmClassMappingKt.getKotlinClass(MainView.class)
Handling signature clashes with @JvmName
Sometimes we have a named function in Kotlin, for which we need a different JVM name the byte code.
The most prominent example happens due to type erasure:
fun List<String>.filterValid(): List<String>
fun List<Int>.filterValid(): List<Int>
These two functions can not be defined side-by-side, because their JVM signatures are the same: filterValid(Ljava/util/List;)Ljava/util/List;
.
If we really want them to have the same name in Kotlin, we can annotate one (or both) of them with @JvmName
and specify a different name as an argument:
fun List<String>.filterValid(): List<String>
@JvmName("filterValidInt")
fun List<Int>.filterValid(): List<Int>
From Kotlin they will be accessible by the same name filterValid
, but from Java it will be filterValid
and filterValidInt
.
The same trick applies when we need to have a property x
alongside with a function getX()
:
<div class="sample" markdown="1" theme="idea" data-highlight-only auto-indent="false">
val x: Int
@JvmName("getX_prop")
get() = 15
fun getX() = 10
Overloads Generation
Normally, if you write a Kotlin function with default parameter values, it will be visible in Java only as a full
signature, with all parameters present. If you wish to expose multiple overloads to Java callers, you can use the
@JvmOverloads
annotation.
The annotation also works for constructors, static methods etc. It can't be used on abstract methods, including methods
defined in interfaces.
class Foo @JvmOverloads constructor(x: Int, y: Double = 0.0) {
@JvmOverloads fun f(a: String, b: Int = 0, c: String = "abc") { ... }
}
For every parameter with a default value, this will generate one additional overload, which has this parameter and
all parameters to the right of it in the parameter list removed. In this example, the following will be
generated:
// Constructors:
Foo(int x, double y)
Foo(int x)
// Methods
void f(String a, int b, String c) { }
void f(String a, int b) { }
void f(String a) { }
Note that, as described in Secondary Constructors, if a class has default
values for all constructor parameters, a public no-argument constructor will be generated for it. This works even
if the @JvmOverloads
annotation is not specified.
Checked Exceptions
As we mentioned above, Kotlin does not have checked exceptions.
So, normally, the Java signatures of Kotlin functions do not declare exceptions thrown.
Thus if we have a function in Kotlin like this:
// example.kt
package demo
fun foo() {
throw IOException()
}
And we want to call it from Java and catch the exception:
// Java
try {
demo.Example.foo();
}
catch (IOException e) { // error: foo() does not declare IOException in the throws list
// ...
}
we get an error message from the Java compiler, because foo()
does not declare IOException
.
To work around this problem, use the @Throws
annotation in Kotlin:
@Throws(IOException::class)
fun foo() {
throw IOException()
}
Null-safety
When calling Kotlin functions from Java, nobody prevents us from passing null{: .keyword } as a non-null parameter.
That's why Kotlin generates runtime checks for all public functions that expect non-nulls.
This way we get a NullPointerException
in the Java code immediately.
Variant generics
When Kotlin classes make use of declaration-site variance, there are two
options of how their usages are seen from the Java code. Let's say we have the following class and two functions that use it:
class Box<out T>(val value: T)
interface Base
class Derived : Base
fun boxDerived(value: Derived): Box<Derived> = Box(value)
fun unboxBase(box: Box<Base>): Base = box.value
A naive way of translating these functions into Java would be this:
Box<Derived> boxDerived(Derived value) { ... }
Base unboxBase(Box<Base> box) { ... }
The problem is that in Kotlin we can say unboxBase(boxDerived("s"))
, but in Java that would be impossible, because in Java
the class Box
is invariant in its parameter T
, and thus Box<Derived>
is not a subtype of Box<Base>
.
To make it work in Java we'd have to define unboxBase
as follows:
Base unboxBase(Box<? extends Base> box) { ... }
Here we make use of Java's wildcards types (? extends Base
) to emulate declaration-site variance through use-site
variance, because it is all Java has.
To make Kotlin APIs work in Java we generate Box<Super>
as Box<? extends Super>
for covariantly defined Box
(or Foo<? super Bar>
for contravariantly defined Foo
) when it appears as a parameter. When it's a return value,
we don't generate wildcards, because otherwise Java clients will have to deal with them (and it's against the common
Java coding style). Therefore, the functions from our example are actually translated as follows:
// return type - no wildcards
Box<Derived> boxDerived(Derived value) { ... }
// parameter - wildcards
Base unboxBase(Box<? extends Base> box) { ... }
NOTE: when the argument type is final, there's usually no point in generating the wildcard, so Box<String>
is always
Box<String>
, no matter what position it takes.
If we need wildcards where they are not generated by default, we can use the @JvmWildcard
annotation:
fun boxDerived(value: Derived): Box<@JvmWildcard Derived> = Box(value)
// is translated to
// Box<? extends Derived> boxDerived(Derived value) { ... }
On the other hand, if we don't need wildcards where they are generated, we can use @JvmSuppressWildcards
:
fun unboxBase(box: Box<@JvmSuppressWildcards Base>): Base = box.value
// is translated to
// Base unboxBase(Box<Base> box) { ... }
NOTE: @JvmSuppressWildcards
can be used not only on individual type arguments, but on entire declarations, such as
functions or classes, causing all wildcards inside them to be suppressed.
Translation of type Nothing
The type Nothing
is special, because it has no natural counterpart in Java. Indeed, every Java reference type, including
java.lang.Void
, accepts null
as a value, and Nothing
doesn't accept even that. So, this type cannot be accurately
represented in the Java world. This is why Kotlin generates a raw type where an argument of type Nothing
is used:
fun emptyList(): List<Nothing> = listOf()
// is translated to
// List emptyList() { ... }