After one time too many stumbling over a forgotten/unknown function in Kotlin’s “collection” API, I decided to take stock of what’s available.
TLDR - That API is pretty large.
Some definitions are in order. I put “collection” in double quotes because from my practical perspective a collection is
pretty much any generic container I can forEach
over (CharSequence
is excluded being character specific). Towards
the end of this post there is a more solid justification for using forEach
as an umbrella name for “collections”.
In Kotlin the interfaces that fit the description are:
Collection
- resides inkotlin.collections
package.Array
- Array’s are notCollection
s. They reside in thekotlin
packageIterable
- resides in thekotlin.collections
package, Supertype ofCollection
.Sequence
- resides in thekotlin.sequences
package.Map
- As in Java - Map stands alone, it resides in thekotlin.collections
package but is not aCollection
.
To size the API, I decided to look at the extension functions defined for the above interfaces, to enumerate the extension functions I took the (somewhat hacky) approach of scraping the online API documentation.
The code:
import org.jsoup.Jsoup
val docRoot = "https://kotlinlang.org/api/latest/jvm/stdlib"
val docUrls = mapOf(
"array" to "$docRoot/kotlin/-array/",
"sequence" to "$docRoot/kotlin.sequences/-sequence/index.html",
"iterable" to "$docRoot/kotlin.collections/-iterable/index.html",
"collection" to "$docRoot/kotlin.collections/-collection/index.html",
"map" to "$docRoot/kotlin.collections/-map/index.html"
)
fun extensionFunctions(url: String): Set<String> {
return Jsoup.connect(url).get()
.selectFirst("h3#extension-functions ~ div.api-declarations-list")
.select("h4 > a")
.map { e -> e.ownText() }
.toSet()
}
val funMap: Map<String, Set<String>> = docUrls.mapValues { extensionFunctions(it.value) }
Results
Interface | Extension Function # |
---|---|
Collection | 115 |
Array | 142 |
Iterable | 107 |
Sequence | 105 |
Map | 47 |
total unique | 173 |
Here is a visualization and a breakdown courtesy of University of Gent:
The table below indicates which methods are in each intersection or are unique to a certain interface. For example out
of 105 Sequence
extension functions, only two are unique one of them being ifEmpty
.
Names | Total | Elements |
---|---|---|
array collection iterable map sequence | 25 | mapNotNullTo flatMapTo count mapTo all asSequence contains toMap asIterable toList filterTo maxBy map filterNotTo forEach flatMap none filterNot filter any minWith plus maxWith mapNotNull minBy |
array collection iterable sequence | 62 | sumByDouble mapIndexedTo groupBy flatten toMutableList reduce groupingBy last indexOfLast elementAt sortedByDescending requireNoNulls distinctBy toHashSet fold zip dropWhile filterIsInstanceTo firstOrNull lastIndexOf joinTo toSortedSet foldIndexed elementAtOrNull joinToString elementAtOrElse unzip take indexOfFirst distinct toSet mapIndexedNotNull mapIndexed withIndex partition drop find mapIndexedNotNullTo groupByTo forEachIndexed filterIndexed singleOrNull filterNotNull filterIsInstance associateBy associateByTo lastOrNull single associate indexOf toMutableSet reduceIndexed toCollection takeWhile findLast sortedWith sortedBy filterIndexedTo first associateTo sumBy filterNotNullTo |
collection iterable map sequence | 2 | onEach minus |
array collection iterable | 4 | union subtract reversed intersect |
array iterable sequence | 6 | average sortedDescending sorted sum max min |
array collection map | 2 | isNotEmpty isNullOrEmpty |
collection iterable sequence | 7 | associateWithTo plusElement windowed chunked zipWithNext associateWith minusElement |
collection map sequence | 1 | orEmpty |
array collection | 9 | toByteArray toShortArray toCharArray toLongArray toFloatArray random toDoubleArray toIntArray toBooleanArray |
array map | 1 | getOrElse |
collection iterable | 1 | shuffled |
array | 33 | foldRightIndexed binarySearch component3 subarrayContentToString reversedArray toCValues sort getOrNull slice sortByDescending foldRight sortWith component4 fill isArrayOf takeLast sortBy reduceRightIndexed component5 sortedArrayWith sliceArray sortedArray component2 takeLastWhile sortDescending sortedArrayDescending dropLast component1 reduceRight dropLastWhile isEmpty reverse toCStringArray |
collection | 2 | containsAll waitForMultipleFutures |
map | 16 | mapKeys filterValues mapValuesTo getOrDefault iterator toMutableMap withDefault filterKeys mapKeysTo mapValues get toProperties containsValue getValue containsKey toSortedMap |
sequence | 2 | ifEmpty constrainOnce |
The first row lists the extension functions that belong to all the interfaces and the reason I take forEach
to be a
“defining” collection function.
Conclusions
- At 173 functions, it is a pretty large API. True, I may never need some of the functions (looking at you
toCValues
) but they are there in the documentation and auto-completion competing for my attention. - The package structure - there probably is a way to rationalize it, but to me it looks confusing.
- The bulk comparison approach is a different way to look at APIs and points at further investigations. For example
Sequence
andIterable
are almost identical butSequence
hasorEmpty
andifEmpty
functions which are not present inIterable
- why not? - The
Array
andCollection
APIs have a naming convention regarding sorting: functions namedsortXXX
(e.g.sortBy
) do the sorting in-place while functions namedsortedXXX
(e.g.sortedBy
) produce a new collection.