Kotlin
A concise multiplatform language developed by JetBrains
First Look at Kotlin 1.4-M2: Standard Library Improvements
We keep working on Kotlin 1.4 and the next preview 1.4-M2 is just around the corner. Right now we’re ready to unveil some improvements from this preview; in this post, we’ll get you acquainted with the standard library changes.
Here are some key improvements in the standard library in 1.4-M2:
- Changes to the existing API like updating signatures and introducing new constants
- More functions in the common library
- New functions for Arrays and Collections
- Property delegation improvements
Even though Kotlin 1.4-M2 hasn’t been released yet, we’ve deployed its early version to the Kotlin playground so that you can try everything you find in this post. The code samples in this post run on the new version as well.
If you can’t wait to try the new version, subscribe to the Kotlin blog newsletter and you won’t miss the release day.
Extending the common library
You can use the standard library in “common” code – the code shared between different platforms, be they Android & iOS or JVM & JS. We’re gradually extending the common library and adding or moving missing functionality to it.
The previous implementation of appendln
in Kotlin/JVM added a system-dependent line separator (\n
on UNIX systems and \r\n
on Windows). However, we feel it is important to guarantee the same behavior independently of the operating system, and independently of the underlying platform if we’re talking about common code. This is why we’re deprecating appendln
in favor of the new appendLine
function, which always terminates lines with a single \n
character:
You can still use System.lineSeparator()
in the JVM when you need a platform-specific separator.
The other functions that can now be used in common code allow you to use the string representation of the given stack trace. The Throwable.stackTraceToString()
extension returns the detailed description of this throwable with its stack trace, and Throwable.printStackTrace()
prints this description to the standard error output.
In common code, you can also use the Throwable.addSuppressed()
function, which allows you to specify the exceptions that were suppressed in order to deliver the exception, and the Throwable.suppressedExceptions
property, which returns a list of all the suppressed exceptions.
New array functions
To provide a consistent experience when working with different container types, we’ve added new extension functions for arrays:
shuffle()
– puts the array elements in a random order.onEach()
– performs the given action on each array element and returns the array itself.
These functions should be familiar to those who use lists; they work for arrays the same way.
We’ve also added new functions for sorting array subranges. Previously, there was sort()
with parameters fromIndex
and toIndex
, which was JVM-only. Now it is common, and so are two new related functions, which are subrange versions of reverse()
and sortDescending()
. Each of these takes two indices and reorders the elements between them (including fromIndex
but excluding toIndex
) in ways that are hopefully evident from their names:
reverse()
for array subranges reverses the order of the elements in the subrange.sortDescending()
for array subranges sorts the elements in the subrange in descending order.
New functions in collections API
In 1.4-M2, we continue expanding the collections API in the standard library to cover more real-life cases:
- A new set creation function
setOfNotNull()
makes a set consisting of all the non-null items among the provided arguments.
shuffled()
is now available for sequences.
onEachIndexed()
andreduceIndexedOrNull()
have been added as counterparts for onEach() and reduceOrNull(), respectively. As you may already know,Indexed
in the name of a collection-processing function means that the operation applied has the element index as a parameter.
runningFold()
andrunningReduce()
are introduced as synonyms forscan()
andscanReduce()
. Such names are more consistent with the related functionsfold()
andreduce()
. In the future,scan()
will be available along withrunningFold()
since it’s a commonly known name for this operation. The experimentalscanReduce()
, however, will be deprecated and removed soon.
Improving the existing API
As Kotlin 1.4 is a major “feature” release, we can add new features to the language, and new functions or interfaces to the standard library. We only add new experimental declarations in incremental releases (like 1.3.70). If you’re writing code using Kotlin 1.3.70 and you don’t use any experimental declarations, your code will compile just fine for your teammates who use Kotlin 1.3.40.
Feature releases don’t need to obey the same strict rules as minor ones, meaning that if you use a new feature or API, the Kotlin compiler of the 1.3 version may not compile code written in Kotlin 1.4. This allows us to introduce some changes to the API in order to improve it. We do try to carefully observe backward compatibility so that your code written for the older version continues to compile and work as before.
In Kotlin 1.4, we’ve relaxed several functions to accept null
s:
Kotlin 1.3 won’t compile this code because it requires the receiver of String.toBoolean()
to be non-nullable. Kotlin 1.4 changes the receiver to a nullable string: String?.toBoolean()
. Still, all the code you’ve written before continues to compile and work in Kotlin 1.4.
The same logic applies to the Array
receiver of the contentEquals
, contentHashCode
, and contentToString
functions: now it’s nullable. Also, String.format()
now allows null
as a locale
argument, in which case no localization is applied.
The following constants defined in Double
s and Float
s are now “real” constants:
They are now defined as const
variables, so you can use them as annotation arguments.
SIZE_BITS
and SIZE_BYTES
are new constants in Double
and Float
; they contain the number of bits and bytes accordingly used to represent an instance of the type in binary form.
Note that we’ve also changed the Float.MAX_VALUE
and Float.MIN_VALUE
values in Kotlin/JS. Before, they were equal to JavaScript Number.MAX/MIN_VALUE
, or equivalently, Double.MAX/MIN_VALUE
, because Float
is essentially equivalent to Double
in Kotlin/JS. Now, these Float
range constants are the same for all platforms.
maxOf() and minOf() with varargs
The maxOf()
and minOf()
functions from the standard library find the greater and the smaller of two values. Starting with 1.4-M1, maxOf()
and minOf()
can accept a variable number of arguments (vararg
), allowing you to use them on any sets of numbers or other comparable items.
Property delegation improvements
In Kotlin, delegated properties work via conventions, not interfaces: the type you want to use as a delegate must define an operator function rather than implement the required interface. This provides flexibility (as we aren’t bound by specific interfaces), but in many practical use cases, it still is helpful to use interfaces.
In Kotlin 1.4, we’ve made such complementary interfaces even better to work with: we’ve introduced a new PropertyDelegateProvider
interface, and ReadWriteProperty
now inherits ReadOnlyProperty
. Please read on for more details.
Delegate expression
The ReadWriteProperty
and ReadOnlyProperty
interfaces are handy for defining property delegates as your custom class or anonymous object can implement them:
Starting with 1.4, ReadWriteProperty
inherits ReadOnlyProperty
. This gives you more flexibility for working with delegate expressions. In our example, now you can pass the myDelegate()
call whenever a ReadOnlyProperty
is expected.
Here we’d like to emphasize that “read-only” isn’t the same as “immutable” in Kotlin, similarly to how a read-only list is not an immutable list. “Read-only” means “this interface provides only read-only access to the object in question”.
Providing a delegate
By using the mechanism of providing a delegate, you can extend the logic of creating a “delegate” object – the object to which the property implementation is delegated. You can find the details of how it works in the documentation. In 1.4, to make this mechanism a bit more convenient, we’ve added a new PropertyDelegateProvider
interface. You can use it when you don’t want to create an extra class and prefer using an anonymous object, similar to what we’ve seen in myDelegate()
example above.
Delegating to another property
Starting with 1.4, a property can delegate its getter and setter directly to another property. This may be useful, for instance, when you want to rename a property in a backward-compatible way: you introduce a new property, annotate an old one with the @Deprecated
annotation, and delegate its implementation.
The optimization for compiled delegated properties described before works for this case. Since the delegation operator implementation doesn’t use the information about the property being delegated (oldName
), there’s no need for the compiler to generate the KProperty
instance with that information. In the future, it will also be possible not to generate an additional KMutableProperty
instance for the delegate (newName
).
How to try
All the described changes will be part of the Kotlin 1.4-M2 preview, but you can already try them online at play.kotl.in; just make sure to select the version 1.4-M2 in the settings.
Pre-release notes
Note that backward compatibility guarantees do not cover pre-release versions. The features and API can change in subsequent releases based on your feedback.
Share your feedback
We’re thankful for all your bug reports in our issue tracker. We’ll do our best to fix all the important issues before the final release so you won’t have to wait until yet another Kotlin release for them to be addressed.
You are also welcome to join the #eap channel in our Kotlin Slack (get an invite here) to ask questions, participate in discussions, and get notifications of new preview builds.
Let’s Kotlin!