内容简介:Java developers new to Kotlin are often confused by the lack of static members. Which options Kotlin provides in order to replace theContinue reading for the details.When Java developers start to code in Kotlin, one of the first question is: “How can I def
TL; DR
Java developers new to Kotlin are often confused by the lack of static members. Which options Kotlin
provides in order to replace the static
keyword? Let's summarize for easy reference:
Java static… | Replace with… |
---|---|
void main() | Top-level function |
Constants | Top-level constants |
Methods in utility class | Top-level functions |
Factory methods | Companion object functions |
Private methods | Private companion object functions or private top-level functions in same file |
Singleton accessors | Object instance |
Continue reading for the details.
Where is my precious static?
When Java developers start to code in Kotlin, one of the first question is: “How can I define a static function or property?". But there is no such keyword in Kotlin. Why?
Several modern languages dropped statics for the same reason they dropped explicit support for primitive types: simplify the language by removing special cases. As far as I know, Scala is the first JVM language that did that and Kotlin adopted a similar approach.
Java static methods and fields are attached to the class where they are declared, not its instances. Static member lives in a separate world than instance members, governed by different rules:
Instance methods | Static methods | |
---|---|---|
Scope | Instance | Class |
Can override open methods in superclass | Yes | No (but can shadow them) |
Can implement methods in interface | Yes | No |
Dispatch | Dynamic, by actual runtime type | Static, by type known at compile-time |
Having them inside the same language construct, the class
, is mixing together two different concerns: on one side the
dynamic behavior of individual instances and, on the other side, global static code.
It's no surprise that beginners have a hard time understanding static members: first we explain them the
object-oriented mantra that classes are like blueprints to define how individual objects can behave, but then we go on
and tell them that the blueprints themselves can have non-object-oriented behaviors on their own…
In Kotlin, a class can only define the behavior of instances: it's really like a blueprint. Static or global functions are defined separately, outside of the class definitions.
Let's now look in detail at the options Kotlin provides.
Top-level functions & properties
In Java everything must be defined inside a class. Fortunately Kotlin allows to define top-level functions , outside any class definition, directly in a .kt file.
Function main()
Every Java beginner starts by writing this “Hello world” application.
public class HelloWorld { public static void main(String[] args) { System.out.println("Hello World"); } }
There is a lot of boilerplate for such a simple program. With Kotlin top-level functions we can write the main()
function with a minimal amount of ceremony.
// Look Ma! No class! fun main() { // 'args: Array<String>' parameter is optional println("Hello World") }
Get rid of utility classes
I cannot count how many *Utils
or *Helper
classes I've created in Java during my career, with only static methods
inside…
//Java public class StringUtils { private StringUtils() { /* Forbid instantiation of utility class */ } public static int lowerCaseCount(String value) { return value.chars().reduce(0, (count, current) -> count + (Character.isLowerCase(current) ? 1 : 0)); } }
Let's get rid of the utility class by rewriting that with a top-level function:
package toplevel fun lowerCaseCount(value: String): Int = value.count { it.isLowerCase() }
That was easy!
Top-level functions & properties are associated with their package. This also means we cannot declare 2 top-level functions or properties with the same name in different files of the same package.
We can access a top-level function from another package by importing it from the package where it was defined:
package anotherpackage import toplevel.lowerCaseCount fun main() { println(lowerCaseCount("Hello World")) }
It's often a good idea to consider converting such top-level functions to extension functions , if they can be thought as extensions of one of the arguments.
fun String.lowerCaseCount(): Int = this.count { it.isLowerCase() } fun main() { println("Hello World".lowerCaseCount()) }
Constants
A property can also be declared top-level, but like in Java, we should use them only for constants or immutable values.
// This is OK const val CONSTANT_STRING = "CONSTANT" val READONLY_LIST = listOf("value1", "value2") // NOT OK: avoid public mutable top-level properties var mutableValue = "currentValue" val mutableList = mutableListOf("value1", "value2")
Private top-level functions
In Java, when refactoring long methods, I like to extract small referentially transparent private methods. Referentially transparent functions are easier to reason about and refactor because they forbid side effects, i.e their output purely depend on their input arguments. To ensure they stay referentially transparent, I make them static any access to instance fields.
// Java public class MyClass { private String myString; public void doSomething() { int count = lowerCaseCount(myString); ... } private static int lowerCaseCount(String value) { ... } // private static pure function }
In Kotlin I extract them into private top-level functions instead (or in some cases into companion functions, see below). Private top-level functions and properties are visible only in the file declaring them.
class MyClass(private val myString: String) { fun doSomething() { val count = myString.lowerCaseCount() ... } } private fun String.lowerCaseCount(): Int = ...
From Java
From Java, top-level members can be called like static members on a class named after the Kotlin file where the
top-level function is declared, with the Kt
suffix. Assuming we declared lowerCaseCount()
in a file called TopLevel.kt
, then in Java we'll call it like this:
System.out.println(TopLevelKt.lowerCaseCount("Hello Java"));
If you dislike the Kt
suffix, you can rename this class generated by Kotlin using the annotation @file:JvmName
:
@file:JvmName("TopLevel") package toplevel fun lowerCaseCount(value: String): Int = ...
Object singletons
Kotlin natively supports the Singleton pattern with Object declarations . Because it's an actual value, the Object can extend classes or interfaces and its functions are dynamically dispatched. Nonetheless they can be called very much like static members in Java.
interface Counter { fun count(value: String): Int } object LowerCaseCounter : Counter { // can implement an interface override fun count(value: String) = value.count { it.isLowerCase() } } object UpperCaseCounter : Counter { // another implementation of the same interface override fun count(value: String) = value.count { it.isUpperCase() } } fun main() { // functions on singleton objects can be called like Java static methods println(LowerCaseCounter.count("Hello World")) // prints 8 println(UpperCaseCounter.count("Hello World")) // prints 2 // But singletons are values and can be assigned to variables and passed as arguments val counter = if (someCondition) LowerCaseCounter else UpperCaseCounter println(counter.count("Hello Kotlin Everywhere")) // dynamic dispatch }
From Java
From Java, by default, you can access Object members through the static INSTANCE
field:
System.out.println(LowerCaseCounter.INSTANCE.count("Hello World"));
When an Object function is not overriding a function from a superclass or
interface, which necessarily implies dynamic dispatch, then it can be made static by using the @JvmStatic
annotation:
object StringOps { @JvmStatic fun lowerCaseCount(value: String): Int = value.count { it.isLowerCase() } }
The compiler now generates an actual static member which can be called directly from Java:
System.out.println(StringOps.lowerCaseCount("Hello World"));
Companion objects
Singleton objects are very nice but what if your Java class have static functions that access private non-static members? How to port that to Kotlin? Companion objects are your friends.
Static factory methods
Companion objects are singletons values that are declared inside a class and have a close relationship with it. Members of the companion object can be called using the name of the accompanied class. Moreover the companion object's members can access any private member of the class instances. Companion objects are therefore especially useful for factory methods.
class Rocket private constructor() { private fun attachPropellers() { ... } companion object { fun build(): Rocket { val rocket = Rocket() // can call private constructor rocket.attachPropellers() // can call private function return rocket } } } fun main() { val rocket = Rocket.build() // Companion function called using the accompanied class name }
From Java
By default the companion object members are accessible from Java through the static Companion
field:
Rocket rocket = Rocket.Companion.build();
Again, we can use the @JvmStatic
annotation to allow direct static access to the companion members from Java:
class Rocket private constructor() { companion object { @JvmStatic fun build(): Rocket = ... } }
// Then in Java Rocket rocket = Rocket.build();
Conclusion
By removing the static
keyword from its vocabulary, Kotlin avoid mixing up concerns and provide several other
constructs that cover the same functionality while being more powerful (singleton and companion objects) or needing
less ceremony (top-level functions and properties). However, developers new to Kotlin need to learn how to map the use
cases that were covered by static
to the appropriate Kotlin features. Hopefully this article can help.
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。