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
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
toIndex, which was JVM-only. Now it is common, and so are two new related functions, which are subrange versions of
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.
reduceIndexedOrNull()have been added as counterparts for onEach() and reduceOrNull(), respectively. As you may already know,
Indexedin the name of a collection-processing function means that the operation applied has the element index as a parameter.
runningReduce()are introduced as synonyms for
scanReduce(). Such names are more consistent with the related functions
reduce(). In the future,
scan()will be available along with
runningFold()since it’s a commonly known name for this operation. The experimental
scanReduce(), 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
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
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
Floats are now “real” constants:
They are now defined as
const variables, so you can use them as annotation arguments.
SIZE_BYTES are new constants in
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
Number.MAX/MIN_VALUE, or equivalently,
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
minOf() functions from the standard library find the greater and the smaller of two values. Starting with 1.4-M1,
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.
ReadOnlyProperty interfaces are handy for defining property delegates as your custom class or anonymous object can implement them:
Starting with 1.4,
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 (
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.
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.