Java 9 preparations
Java 9 is coming and brings Project Jigsaw to the table — the Java platform module system. One of the constraints it imposes is that no two modules can declare public API in the same package. The situation, when there are two modules that contribute to the same package, is called “split” package.
We face this issue with split packages in our runtime artifacts: first, kotlin-runtime and kotlin-stdlib modules share a lot of kotlin.* packages, second, kotlin-runtime and kotlin-reflect share kotlin.reflect package. What we’re going to do to make our artifacts more friendly to the module system:
- We merge kotlin-runtime and kotlin-stdlib into the single artifact kotlin-stdlib. Also we’re going to rename kotlin-runtime.jar, shipped in the compiler distribution, to kotlin-stdlib.jar, to reduce the amount of confusion caused by having differently named standard library in different build systems.
That rename will happen in two stages: in 1.1 there will be both kotlin-runtime.jar and kotlin-stdlib.jar with the same content in the compiler distribution, and in 1.2 the former will be removed. - In kotlin-reflect module we move all API from kotlin.reflect to kotlin.reflect.full package. Kotlin 1.1 will have the former API deprecated with the replacements suggested, and it will be removed eventually in 1.2.
Note that this change will affect only extension functions and properties provided by kotlin-reflect for reflection interfaces and a couple of exception classes. Reflection interfaces themselves are located in the standard library and won’t be moved.
If you use maven or gradle and depend on kotlin-stdlib, you won’t need to change anything. If you depend on kotlin-runtime, you should replace that dependency with kotlin-stdlib.
New Standard Library API in 1.1
Here we introduce new functions and classes that are going to be published in Kotlin 1.1.
If you have already tried these new bits in Kotlin 1.1-Beta or in earlier releases and have some feedback, do not hesitate to share it in our forum, slack or issue tracker.
takeIf() and also()
These are two general-purpose extension functions applicable to any receiver.
also
is like apply
: it takes the receiver, does some action on it, and returns that receiver.
The difference is that in the block inside apply
the receiver is available as this
,
while in the block inside also
it’s available as it
(and you can give it another name if you want).
This comes handy when you do not want to shadow this
from the outer scope:
1 2 |
fun Block.copy() = Block().also { it.content = this.content } |
takeIf
is like filter
for a single value. It checks whether the receiver meets the predicate, and
returns the receiver, if it does or null
if it doesn’t.
Combined with an elvis-operator and early returns it allows to write constructs like:
1 2 3 4 5 6 |
val outDirFile = File(outputDir.path).takeIf { it.exists() } ?: return false // do something with existing outDirFile val index = input.indexOf(keyword).takeIf { it >= 0 } ?: error("keyword not found") // do something with index of keyword in input string, given that it's found |
groupingBy()
This API is to group a collection by key and fold each group in the same time. It consists of two parts: groupingBy
function, and terminal operations — fold
, reduce
, eachCount
.
First you invoke collection.groupingBy { key }
function, which just returns a Grouping
instance binding the provided key selector to the collection of elements. Then you call one of folding operations available for Grouping
, which iterates the collection and populates the resulting map with the result of folding elements in each group.
For example, it can be used to count the frequencies of characters in a text:
1 2 |
val charFrequencies: Map<Char, Int> = text.groupingBy { it }.eachCount() |
minOf() and maxOf()
These functions can be used to find the lowest and greatest of two or three given values, where values are primitive numbers or Comparable
objects. There is also an overload of each function that take an additional Comparator
instance, if you want to compare objects that are not comparable themselves.
1 2 3 4 5 |
val list1 = listOf("a", "b") val list2 = listOf("x", "y", "z") val minSize = minOf(list1.size, list2.size) val longestList = maxOf(list1, list2, compareBy { it.size }) |
Map.getValue()
This extension on Map
returns an existing value corresponding to the given key or throws an exception, mentioning which key was not found.
If the map was produced with withDefault
, this function will return the default value instead of throwing an exception.
1 2 3 4 5 6 7 8 9 10 |
val map = mapOf("key" to 42) // returns non-nullable Int value 42 val value: Int = map.getValue("key") // throws NoSuchElementException map.getValue("key2") val mapWithDefault = map.withDefault { k -> k.length } // returns 4 val value2 = mapWithDefault.getValue("key2") |
Map.minus() operator
In Kotlin 1.0 you could easily get a copy of a read-only Map with a new key-value pair inserted with the extension operator Map.plus()
. However to remove a key from the map you have to resort to less straightforward ways to like Map.filter()
or Map.filterKeys()
.
Now Map.minus()
operator fills this gap. There are 4 overloads available: for removing a single key, a collection of keys, a sequence of keys and an array of keys.
1 2 3 |
val map = mapOf("key" to 42) val emptyMap = map - "key" |
Array-like List instantiation functions
Similar to the Array
constructor, there are now functions that create List
and MutableList
instances and initialize each element by calling a lambda:
1 2 3 |
List(size) { index -> element } MutableList(size) { index -> element } |
String to number conversions
There is a bunch of new extensions on the String class to convert it to a number without throwing an exception on invalid number:
String.toIntOrNull(): Int?
, String.toDoubleOrNull(): Double?
etc.
1 2 |
val port = System.getenv("PORT")?.toIntOrNull() ?: 80 |
Note that these functions will box resulting numbers before returning them, since the return type is nullable, and nullable numbers are represented as boxed values.
Also integer conversion functions, like Int.toString()
, String.toInt()
, String.toIntOrNull()
,
each got an overload with radix
parameter, which allows to specify the base of conversion (2 to 36).
onEach()
onEach
is a small, but useful extension function for collections and sequences, which allows to perform some action, possibly with side-effects, on each element of the collection/sequence in a chain of operations.
On iterables it behaves like forEach
but also returns the iterable instance further. And on sequences it returns a wrapping sequence, which applies the given action lazily as the elements are being iterated.
1 2 3 4 5 |
inputDir.walk() .filter { it.isFile && it.name.endsWith(".txt") } .onEach { println("Moving $it to $outputDir") } .forEach { moveFile(it, File(outputDir, it.toRelativeString(inputDir))) } |
Map.toMap() and Map.toMutableMap()
These functions can be used for easy copying of maps:
1 2 3 4 |
class ImmutablePropertyBag(map: Map<String, Any>) { private val mapCopy = map.toMap() } |
Abstract collections
These abstract classes can be used as base classes when implementing Kotlin collection classes.
For implementing read-only collections there are AbstractCollection
, AbstractList
, AbstractSet
and AbstractMap
, and for mutable collections there are AbstractMutableCollection
, AbstractMutableList
, AbstractMutableSet
and AbstractMutableMap
.
On JVM these abstract mutable collections inherit most of their functionality from JDK’s abstract collections.
Currently, the main reason that there is a separate OSGi compatible jar is the split package issue mentioned above. Is there a plan to eliminate the separate OSGi jar and just make the main stdlib jar OSGi compatible in lieu of Java 9 changes?
I believe, when that version of kotlin-stdlib targeted for modular Java 9 is ready, it could also be used in OSGI.
By the way, do you have any clues, which JVM version is used in containers nowadays?
OSGi should work on every version of the JVM that Kotlin does. I don’t know a lot of specifics about stand alone containers since I just embed OSGi and manage it from with an outer application.
Thank you guys for creating this language! I couldn’t be happier with where it’s at and where it’s headed. Couple of quick questions:
Is there a particular reason why the name onEach() was chosen over simply each()?
Would you consider adding an equivalent of apply() to the stdlib where the name of the reciever in the block can be specified instead of passed in as ‘this’? Something along the lines of:
/**
* Apply a function to an object and return it.
*/
inline fun T.then(action: (T) -> Unit): T {
action(this)
return this
}
This seems like it would be useful to have around, especially with the upcoming async functionality.
That’s what
also
extension function is about.Pingback: Kotlin 1.1: What’s coming in the standard library | ExtendTree
Pingback: JVM-Sprache Kotlin: Kotlin 1.0.6 und Vorschau auf Kotlin 1.1 [Update] - JAXenter
What is the reasoning behind creating onEach()? What does this give a developer that you can’t get from forEach()?
I don’t think it is clear to a new developer to the language that calling onEach() returns the iterable instance for the next statement in the chain. In the example above I would just call forEach() in the following way:
.forEach { f ->
println(“Moving $f to $outputDir”)
moveFile(f, File(outputDir, f.toRelativeString(inputDir)))
}
It returns an instance on which you can continue to chain other operations. Consider several
map
andfilter
operations afteronEach
.It’s the equivalent of Java 8’s Stream#peek method (and somewhat similar to Ruby’s Object#tap method), so it makes sense, especially for debugging and logging.
Is there something like that in std lib:
fun List?.orEmpty() = this ?: emptyList()
I found this extension very useful when dealing with legacy java code which could give you unexpected null and I would love to have it in std lib (for Set and Map as well).
Then you’d be pleased to know we already have
orEmpty
extensions in the standard library even in Kotlin 1.0.