Companion object kotlin java

Using Java Reflection with Kotlin Companion Objects

Kotlin companion objects allow you to add static data and methods associated with a class. This is similar to how Java has static fields and methods. The problem is that Java doesn’t really know what a companion object it, so trying to access one using standard Java reflection might make you go crazy. 🤪

package com.handstandsam /** This is contrived example of a companion object */ class SpecialFeature < companion object < var enabled: Boolean = false >>

The decompiled Java class (created by the Kotlin Compiler) results in this:

package com.handstandsam; import kotlin.Metadata; import kotlin.jvm.internal.DefaultConstructorMarker; import org.jetbrains.annotations.NotNull; public final class SpecialFeature < private static boolean enabled; @NotNull public static final Companion Companion = new Companion((DefaultConstructorMarker)null); public static final class Companion < public final boolean getEnabled() < return SpecialFeature.enabled; >public final void setEnabled(boolean var1) < SpecialFeature.enabled = var1; >private Companion() < >public Companion(DefaultConstructorMarker $constructor_marker) < this(); >> >

The Kotlin Standard Lib for Java has a really cool method called companionObjectInstance that allows you to grab an instance of the declared companion object from the KClass object.

Why is companionObjectInstance helpful?

When Kotlin is compiled to Java Class files, the companion object has a fully qualified class name of com.handstandsam.SpecialFeature$Companion .

Читайте также:  Create def in python

Mapping Kotlin -> Java Byte Code can make your head hurt, so by using this companionObjectInstance helper method, we don’t have to figure out how to get an instance of the companion object, or figure out the fully qualified class name.

val companionObjectJavaClass = com.handstandsam.SpecialFeature::class.java
val companionObjectInstance = companionObjectJavaClass.kotlin .companionObjectInstance!!

Now that we have an instance of the companion object class, and know the Java class, we can use reflection to set the value of the enabled property on the companion object.

companionObjectInstance::class.java .methods .first < it.name == "setEnabled" > .invoke(companionObjectInstance, true)

Note: setEnabled is the name, and it is a method here. You might expect this to just be a property which is what I assumed, but when compiled to java byte code, it is marked private and has a getter and a setter.

Bonus: Accessing private properties using Java Reflection

You could alternatively use Java reflection to change the backing private static boolean enabled field directly if you choose.

If you wanted to set the private static field value itself, rather than calling the setter, you can grab the declared field, and set it to accessible which allows us to bypass the private visibility. This sort of thing is why the JVM can’t be considered secure as it can be modified at runtime.

val privateEnabledField = SpecialFeature::class.java.getDeclaredField("enabled") privateEnabledField.isAccessible=true privateEnabledField.set(companionObjectInstance, true)

Conclusion

Reflection is powerful, but confusing. I could have probably done this cleaner JUST using Kotlin Reflect and not Java Reflection, but in my case I wanted to use Java Reflection, but needed to interact with a Kotlin companion object . There is a lot of documentation on how to mix Kotlin + Reflection, so feel free to read up more there. Cheers!

Источник

Object expressions and declarations

Sometimes you need to create an object that is a slight modification of some class, without explicitly declaring a new subclass for it. Kotlin can handle this with object expressions and object declarations.

Object expressions

Object expressions create objects of anonymous classes, that is, classes that aren’t explicitly declared with the class declaration. Such classes are useful for one-time use. You can define them from scratch, inherit from existing classes, or implement interfaces. Instances of anonymous classes are also called anonymous objects because they are defined by an expression, not a name.

Creating anonymous objects from scratch

Object expressions start with the object keyword.

If you just need an object that doesn’t have any nontrivial supertypes, write its members in curly braces after object :

Inheriting anonymous objects from supertypes

To create an object of an anonymous class that inherits from some type (or types), specify this type after object and a colon ( : ). Then implement or override the members of this class as if you were inheriting from it:

If a supertype has a constructor, pass appropriate constructor parameters to it. Multiple supertypes can be specified as a comma-delimited list after the colon:

Using anonymous objects as return and value types

When an anonymous object is used as a type of a local or private but not inline declaration (function or property), all its members are accessible via this function or property:

If this function or property is public or private inline, its actual type is:

  • Any if the anonymous object doesn’t have a declared supertype
  • The declared supertype of the anonymous object, if there is exactly one such type
  • The explicitly declared type if there is more than one declared supertype

In all these cases, members added in the anonymous object are not accessible. Overridden members are accessible if they are declared in the actual type of the function or property:

interface A < fun funFromA() <>> interface B class C < // The return type is Any; x is not accessible fun getObject() = object < val x: String = "x" >// The return type is A; x is not accessible fun getObjectA() = object: A < override fun funFromA() <>val x: String = «x» > // The return type is B; funFromA() and x are not accessible fun getObjectB(): B = object: A, B < // explicit return type is required override fun funFromA() <>val x: String = «x» > >

Accessing variables from anonymous objects

The code in object expressions can access variables from the enclosing scope:

Object declarations

The Singleton pattern can be useful in several cases, and Kotlin makes it easy to declare singletons:

This is called an object declaration, and it always has a name following the object keyword. Just like a variable declaration, an object declaration is not an expression, and it cannot be used on the right-hand side of an assignment statement.

The initialization of an object declaration is thread-safe and done on first access.

To refer to the object, use its name directly:

Such objects can have supertypes:

Object declarations can’t be local (that is, they can’t be nested directly inside a function), but they can be nested into other object declarations or non-inner classes.

Data objects

When printing a plain object declaration in Kotlin, the string representation contains both its name and the hash of the object:

Just like data classes, you can mark an object declaration with the data modifier. This instructs the compiler to generate a number of functions for your object:

  • toString() returns the name of the data object
  • equals() / hashCode() pair You can’t provide a custom equals or hashCode implementation for a data object .

The toString() function of a data object returns the name of the object:

The equals() function for a data object ensures that all objects that have the type of your data object are considered equal. In most cases, you will only have a single instance of your data object at runtime (after all, a data object declares a singleton). However, in the edge case where another object of the same type is generated at runtime (for example, by using platform reflection with java.lang.reflect or a JVM serialization library that uses this API under the hood), this ensures that the objects are treated as being equal.

Make sure that you only compare data objects structurally (using the == operator) and never by reference (using the === operator). This helps you to avoid pitfalls when more than one instance of a data object exists at runtime.

The generated hashCode() function has behavior that is consistent with the equals() function, so that all runtime instances of a data object have the same hash code.

Differences between data objects and data classes

While data object and data class declarations are often used together and have some similarities, there are some functions that are not generated for a data object :

  • No copy() function. Because a data object declaration is intended to be used as singleton objects, no copy() function is generated. The singleton pattern restricts the instantiation of a class to a single instance, which would be violated by allowing copies of the instance to be created.
  • No componentN() function. Unlike a data class , a data object does not have any data properties. Since attempting to destructure such an object without data properties would not make sense, no componentN() functions are generated.

Using data objects with sealed hierarchies

data object declarations are a particularly useful for sealed hierarchies, like sealed classes or sealed interfaces, since they allow you to maintain symmetry with any data classes you may have defined alongside the object:

sealed interface ReadResult data class Number(val number: Int) : ReadResult data class Text(val text: String) : ReadResult data object EndOfFile : ReadResult fun printReadResult(r: ReadResult) < when(r) < is Number ->println(«Num($«) is Text -> println(«Txt($») is EndOfFile -> println(«EOF») > > fun main() < printReadResult(EndOfFile) // EOF >

Companion objects

An object declaration inside a class can be marked with the companion keyword:

Members of the companion object can be called simply by using the class name as the qualifier:

The name of the companion object can be omitted, in which case the name Companion will be used:

Class members can access the private members of the corresponding companion object.

The name of a class used by itself (not as a qualifier to another name) acts as a reference to the companion object of the class (whether named or not):

Note that even though the members of companion objects look like static members in other languages, at runtime those are still instance members of real objects, and can, for example, implement interfaces:

However, on the JVM you can have members of companion objects generated as real static methods and fields if you use the @JvmStatic annotation. See the Java interoperability section for more detail.

Semantic difference between object expressions and declarations

There is one important semantic difference between object expressions and object declarations:

  • Object expressions are executed (and initialized) immediately, where they are used.
  • Object declarations are initialized lazily, when accessed for the first time.
  • A companion object is initialized when the corresponding class is loaded (resolved) that matches the semantics of a Java static initializer.

Источник

Оцените статью