Chapter 8: Basic types, collections, and arrays
Introduces the reliability-oriented choices in Kotlin’s type system that shape its basic types and collections.
After this chapter you will be able to
- Reliability-Oriented Type System Choices. Explain that Kotlin’s type system aims to improve reliability beyond nullability, drawing lessons from Java and other type systems. Give read-only collections and its treatment of arrays as examples of those choices.
- Primitive and wrapper distinction. Explain that Kotlin, unlike Java, does not distinguish primitive types from wrapper types at the language level.
- Unified Kotlin basic types. Explain that Kotlin uses the same type, such as Int, whether a value needs primitive-like or object-like treatment, and that methods can be called on number values.
- Efficient JVM number representation. Explain how Kotlin normally compiles Int to a Java primitive but uses a Java wrapper when Int is a type argument of a generic class such as a collection.
- Unsigned integer value ranges. Explain how unsigned integer types use the same number of bits as their signed counterparts to represent a larger range of non-negative values, and give a situation where that is useful.
- Unsigned types on the JVM. Explain how Kotlin provides unsigned numbers despite the JVM lacking unsigned primitives, and why they can perform like signed primitive numbers.
- Nullable primitive representation. Explain why a nullable primitive type such as Int? uses a Java wrapper type, and how to choose between Int and Int? in Kotlin.
- Boxing of generic primitives. Explain why a primitive type used as a generic type argument is boxed even when its values are not nullable, and state the storage alternatives mentioned for large collections.
- Explicit numeric type conversions. Explain that Kotlin does not automatically convert a numeric value from one type to another, even when the destination type is larger, and describe how conversion functions are used instead.
- String parsing failure choices. Distinguish string-to-primitive conversion functions that throw on parsing failure from their OrNull counterparts, and describe the Boolean conversion cases.
- Any and Any? values. Distinguish Any from Any? as types for holding values in Kotlin, including primitive-type values and null.
- Unit as a real type. Explain why Unit can be used as a generic return type when a function has no interesting result to return.
- Meaning and uses of Nothing. Explain that Nothing is the return type for a function that never completes normally, such as one that throws an exception or loops forever. State why a variable of type Nothing would not be useful.
- Elvis branch and non-null inference. Trace why an Elvis operator whose right-hand side calls a Nothing-returning function lets the compiler treat the resulting value as non-null.
- Kotlin collection API foundation. Explain that Kotlin builds on the Java collections library and adds collection features through extension functions.
- Collection and element nullability. Distinguish a list that can contain null elements from a list that can itself be null, and explain what each question mark means in List<Int?>?.
- Handling nullable list elements. Explain why arithmetic on an element from a list of nullable integers requires a null check, and describe how filterNotNull changes the resulting collection’s type.
- Collection interface capabilities. Distinguish what code can do through Collection and MutableCollection: the former supports reading, while the latter extends it with modification methods.
- Choosing collection parameter types. Explain why code should generally use read-only collection interfaces and reserve mutable interfaces for code that modifies a collection; describe what a mutable parameter signals and when a defensive copy may be needed.
- Read-only views and mutation. Explain why a read-only collection reference does not guarantee an immutable or thread-safe collection, and state the precaution described for multithreaded access.
- Kotlin and Java collection interfaces. Explain that Kotlin collections can be passed to Java without conversion, while Kotlin represents each Java collection interface with separate read-only and mutable interfaces.
- Java modification of Kotlin collections. Explain why Java code can modify a Kotlin collection passed to it despite a read-only or non-null Kotlin type, and how that affects the Kotlin function’s parameter type.
- Java method collection contracts. Describe the three choices needed when implementing a Java method with a collection parameter, and use the section’s examples to show why the same Java type can become different Kotlin types.
- Object array creation and conversion. Describe how to create an object array with arrayOf, arrayOfNulls, or the Array constructor, and how to convert a collection with toTypedArray when an array is required.
- Primitive arrays and boxing. Distinguish Array<Int> from IntArray, including their Java representations and the ways to create or convert to a primitive array.
What it builds on
- Avoiding NullPointerExceptions and handling the absence of values: Nullability (chapter 7) · Introduces nullability as an existing reliability feature of Kotlin’s type system.
- Extra type safety without overhead: Inline classes (chapter 4) · the concept of inline classes used to explain unsigned-number storage
- Making possibly null variables explicit with nullable types (chapter 7) · Nullable types mark values that may be null.
- Creating collections in Kotlin (chapter 3) · Creating Kotlin collections.
- Platform types (chapter 7) · Java types whose nullability is unknown in Kotlin.
- Universal object methods (chapter 4) · The common methods available on Kotlin objects.
- Providing default values in null cases with the Elvis operator: ?: (chapter 7) · The Elvis operator’s right-hand side handles a null value.
- Nullability of type parameters (chapter 7) · Nullability of type arguments
Section by section
Introduction
Introduces the reliability-oriented choices in Kotlin’s type system that shape its basic types and collections.
- Reliability-Oriented Type System Choices: Explain that Kotlin’s type system aims to improve reliability beyond nullability, drawing lessons from Java and other type systems. Give read-only collections and its treatment of arrays as examples of those choices.
Why it matters: These design choices provide context for how Kotlin treats basic types, collections, and arrays.
Key terms: type system, nullability, read-only collections, arrays
8.1 Primitive and other basic types
Introduces Kotlin’s basic types and its distinction from Java’s treatment of primitive types and wrappers.
- Primitive and wrapper distinction: Explain that Kotlin, unlike Java, does not distinguish primitive types from wrapper types at the language level.
Why it matters: This distinction sets up the later explanation of Kotlin’s basic types.
Key terms: primitive types, wrappers
8.1.1 Representing integers, floating-point numbers, characters, and Booleans with primitive types
The section contrasts Java’s primitive and wrapper types with Kotlin’s unified basic types, then explains how Kotlin represents those types efficiently on the JVM.
- Primitive and wrapper distinction: Distinguish Java primitive values from reference values and explain why Java uses wrapper types when an object is required, such as in a collection.
Why it matters: This contrast makes Kotlin’s simpler type usage easier to appreciate. - Unified Kotlin basic types: Explain that Kotlin uses the same type, such as Int, whether a value needs primitive-like or object-like treatment, and that methods can be called on number values.
Why it matters: You can write Kotlin using one basic type without choosing a separate wrapper type. - Efficient JVM number representation: Explain how Kotlin normally compiles Int to a Java primitive but uses a Java wrapper when Int is a type argument of a generic class such as a collection.
Why it matters: It explains how Kotlin keeps a simple type system in code without representing all numbers as objects.
Key terms: primitive types, reference types, wrapper types, Int, coerceIn, Java primitive type, Java wrapper type, generic classes, java.lang.Integer
8.1.2 Using the full bit range to represent positive numbers: Unsigned number types
This section explains what Kotlin’s unsigned integer types are for, how their ranges differ from signed types, and how they work on the JVM.
- Unsigned integer value ranges: Explain how unsigned integer types use the same number of bits as their signed counterparts to represent a larger range of non-negative values, and give a situation where that is useful.
Why it matters: The shifted range explains when an unsigned type provides a benefit over a signed one. - Choosing regular or unsigned integers: Distinguish needing the full bit range from merely requiring a non-negative input, and describe the section’s recommendation for the latter case.
Why it matters: It helps you choose unsigned types for their intended range benefit rather than treating them as a general input-validation tool. - Unsigned types on the JVM: Explain how Kotlin provides unsigned numbers despite the JVM lacking unsigned primitives, and why they can perform like signed primitive numbers.
Why it matters: The representation explains why unsigned types do not normally require a separate JVM primitive or impose a performance penalty.
Key terms: unsigned number types, signed counterparts, full bit range, UInt, unsigned integers, regular integers, JVM, unsigned numbers, inline classes, signed counterpart
8.1.3 Nullable primitive types: Int?, Boolean?, and more
This section explains when Kotlin primitive values use boxed JVM representations and how nullability affects operations on them.
- Nullable primitive representation: Explain why a nullable primitive type such as Int? uses a Java wrapper type, and how to choose between Int and Int? in Kotlin.
Why it matters: It connects Kotlin’s nullability rules to the JVM representation without making Java wrapper names necessary in Kotlin code. - Nullable value comparisons: Describe why two Int? values cannot be compared directly in the Person age example and what permits the comparison.
Why it matters: The age comparison shows that ordinary Kotlin nullability rules still apply to nullable primitive types. - Boxing of generic primitives: Explain why a primitive type used as a generic type argument is boxed even when its values are not nullable, and state the storage alternatives mentioned for large collections.
Why it matters: Boxing can occur without nullability and can matter when storing many primitive values.
Key terms: nullable primitive types, wrapper type, Int?, nullability rules, generic classes, type argument, boxed representation, arrays
8.1.4 Kotlin makes number conversions explicit
Explains why Kotlin requires explicit conversions between numeric variables, how literals are treated, and how strings can be converted to primitive types.
- Explicit numeric type conversions: Explain that Kotlin does not automatically convert a numeric value from one type to another, even when the destination type is larger, and describe how conversion functions are used instead.
Why it matters: Explicit conversion makes it clear when your code changes a number’s type. - Boxed value comparison rationale: Justify Kotlin’s explicit numeric conversions using the section’s example of boxed numeric values and equality.
Why it matters: The rule helps avoid surprising results when numbers are compared as boxed values. - Number literals and arithmetic: Describe how Kotlin marks numeric literal types and why literals and mixed-type arithmetic do not always require explicit conversion calls.
Why it matters: Literal and arithmetic rules explain why some numeric expressions work without a conversion call even though converting variables requires one. - String parsing failure choices: Distinguish string-to-primitive conversion functions that throw on parsing failure from their OrNull counterparts, and describe the Boolean conversion cases.
Why it matters: Choosing a parsing function determines whether invalid input produces an exception, null, or—in the Boolean case—false.
Key terms: numeric conversions, toLong(), toInt(), boxed values, equals, number literals, L suffix, arithmetic operators, overflow, toInt
8.1.5 Any and Any?: The root of the Kotlin type hierarchy
This section explains how Any and Any? sit in Kotlin’s type hierarchy, how Any relates to Java’s Object, and which methods Any provides.
- Any and Any? values: Distinguish Any from Any? as types for holding values in Kotlin, including primitive-type values and null.
Why it matters: The distinction determines whether a variable meant to hold arbitrary values can also hold null. - Boxing and Java Object: Explain what happens when a primitive-type value is assigned to Any and how Any corresponds to Java’s Object.
Why it matters: It connects Kotlin’s type hierarchy to the representation and nullability you encounter when working with Java. - Methods available on Any: Describe which common methods Kotlin classes inherit from Any and how that differs from the methods available on Java’s Object.
Why it matters: Corresponding to Java Object does not mean Any exposes every Object method.
Key terms: Any, Any?, non-nullable, null, automatic boxing, java.lang.Object, platform type, toString, equals, hashCode
8.1.6 The Unit type: Kotlin’s void
Explains how Kotlin’s Unit serves as a no-result return type while remaining a real type with a value.
- Unit as a real type: Explain why Unit can be used as a generic return type when a function has no interesting result to return.
Why it matters: Unit lets a generic API represent a function that has no meaningful result without requiring a separate interface or an explicit return. - Unit and Java void: Describe how an ordinary Kotlin function returning Unit relates to a Java void function.
Why it matters: This explains why Unit usually feels familiar to Java developers despite being a distinct Kotlin type.
Key terms: Unit, type argument, return Unit, void, block body
8.1.7 The Nothing type: “This function never returns”
This section explains how Kotlin’s Nothing type marks functions that never return normally and how the compiler uses that fact when analyzing callers.
- Meaning and uses of Nothing: Explain that Nothing is the return type for a function that never completes normally, such as one that throws an exception or loops forever. State why a variable of type Nothing would not be useful.
Why it matters: Nothing tells readers and the compiler that execution cannot continue normally from a call. - Elvis branch and non-null inference: Trace why an Elvis operator whose right-hand side calls a Nothing-returning function lets the compiler treat the resulting value as non-null.
Why it matters: This explains why a throwing null check can establish a non-null type for code that follows.
Key terms: Nothing, return type, Elvis operator, non-null
8.2 Collections and arrays
This introduction connects Kotlin’s collection APIs to Java collections and introduces a closer look at their relationship.
- Kotlin collection API foundation: Explain that Kotlin builds on the Java collections library and adds collection features through extension functions.
Why it matters: This relationship helps explain why Kotlin collection code can use familiar Java collections while offering additional operations.
Key terms: Java collections library, extension functions
8.2.1 Collections of nullable values and nullable collections
This section distinguishes nullable collection elements from nullable collections and shows how to process and filter nullable values.
- Collection and element nullability: Distinguish a list that can contain null elements from a list that can itself be null, and explain what each question mark means in List<Int?>?.
Why it matters: The position of each question mark tells you whether to check the collection, its elements, or both. - Parsing lines into nullable values: Describe how parsing each input line can produce a list of nullable integers, including what happens when a line is not a valid number.
Why it matters: The parsing example makes nullable elements concrete: an unsuccessful conversion occupies a place in the result as null. - Handling nullable list elements: Explain why arithmetic on an element from a list of nullable integers requires a null check, and describe how filterNotNull changes the resulting collection’s type.
Why it matters: Filtering nulls can both simplify processing and give the result a non-null element type.
Key terms: List<Int?>, List<Int>?, List<Int?>?, type argument, map, toIntOrNull, Int?, filterNotNull, List<Int>
8.2.2 Read-only and mutable collections
This section explains how Kotlin separates read-only and mutable collection interfaces, how to choose between them, and why a read-only view does not guarantee immutability or thread safety.
- Collection interface capabilities: Distinguish what code can do through Collection and MutableCollection: the former supports reading, while the latter extends it with modification methods.
Why it matters: The interface type tells you which collection operations your code can call. - Choosing collection parameter types: Explain why code should generally use read-only collection interfaces and reserve mutable interfaces for code that modifies a collection; describe what a mutable parameter signals and when a defensive copy may be needed.
Why it matters: Choosing the interface makes a function’s intended effect on a collection clearer and helps protect internal state. - Read-only views and mutation: Explain why a read-only collection reference does not guarantee an immutable or thread-safe collection, and state the precaution described for multithreaded access.
Why it matters: A read-only view restricts what its holder can change, not what other holders can change.
Key terms: Collection, MutableCollection, read-only interface, mutable interface, read-only interfaces, mutable variants, defensive copy, read-only view, immutable, thread safe
8.2.3 Kotlin collections and Java collections are deeply related
Explains how Kotlin’s read-only and mutable collection interfaces correspond to Java collections, and why passing collections to Java requires care.
- Kotlin and Java collection interfaces: Explain that Kotlin collections can be passed to Java without conversion, while Kotlin represents each Java collection interface with separate read-only and mutable interfaces.
Why it matters: This explains both the ease of Java interoperation and the distinction Kotlin makes between collection interfaces. - Read-only interface versus immutability: Distinguish a read-only collection interface from a guarantee that the underlying collection cannot change, using setOf() and mapOf() as examples.
Why it matters: A read-only type tells you which operations its interface permits, not what implementation it will always use. - Java modification of Kotlin collections: Explain why Java code can modify a Kotlin collection passed to it despite a read-only or non-null Kotlin type, and how that affects the Kotlin function’s parameter type.
Why it matters: Kotlin’s type declarations cannot by themselves protect a collection from changes made by Java code.
Key terms: Collection, MutableCollection, Map, MutableMap, java.util, setOf(), mapOf(), Set, read-only interfaces, read-only Collection
8.2.4 Collections declared in Java are seen as platform types in Kotlin
Explains how Kotlin treats collections declared in Java and how to choose Kotlin collection types when implementing Java methods.
- Java collection platform types: Explain why Kotlin code can treat a collection declared in Java as read-only or mutable, and its type as nullable or non-null.
Why it matters: This explains why a Java collection type does not dictate a single Kotlin declaration. - Java method collection contracts: Describe the three choices needed when implementing a Java method with a collection parameter, and use the section’s examples to show why the same Java type can become different Kotlin types.
Why it matters: The Java method’s contract tells you which Kotlin type accurately expresses what your implementation must handle or change.
Key terms: platform types, read only, mutable, List<String>?, MutableList<String?>, contract
8.2.5 Creating arrays of objects and primitive types for interoperability and performance reasons
Explains how to create Kotlin arrays, choose object or primitive arrays for interoperability and performance, and use collection-style operations on them.
- Object array creation and conversion: Describe how to create an object array with arrayOf, arrayOfNulls, or the Array constructor, and how to convert a collection with toTypedArray when an array is required.
Why it matters: These choices let you build an array from the data you have and pass it to an API that requires one. - Primitive arrays and boxing: Distinguish Array<Int> from IntArray, including their Java representations and the ways to create or convert to a primitive array.
Why it matters: Choosing a primitive array avoids boxing and produces the Java primitive array type when an API needs it. - Collection operations on arrays: Explain how collection-style extension functions apply to arrays, what forEachIndexed passes to its lambda, and what filter or map returns.
Why it matters: You can process arrays with familiar collection functions without assuming the result remains an array.
Key terms: Array, arrayOf, arrayOfNulls, toTypedArray, vararg, Array<Int>, IntArray, boxing, intArrayOf, toIntArray
8.3 Summary
Where this is used later
- Function types specify the parameter types and return values of a lambda (chapter 10) · Explains Unit as the type for a function with no meaningful return value.
- Java lambdas are automatically converted to Kotlin function types (chapter 10) · Kotlin’s Unit type
- Accessing elements by index: The get and set conventions (chapter 9) · The distinction between mutable and read-only collections.
- Variance determines whether it is safe to pass an argument to a function (chapter 11) · The distinction between read-only and mutable collections.
Concepts explained in this chapter
- read-only collection interfaceA read-only collection interface exposes collection contents and traversal operations without exposing mutation operations through that interface.
- primitive data typeA Java primitive type is one of Java's built-in value types, such as int or boolean, that is represented directly rather than as an object reference.
- listMutableList is a Kotlin mutable collection type whose elements can be read and modified through its invariant type parameter.
- collectionA collection is a container or aggregate that holds multiple elements and supports operations such as membership testing.
- nullable typeA nullable type is a type whose values may include null and whose unsafe operations are restricted by the type system.
- arrayAn array is an indexed, fixed-size sequence of elements of a specified type.
- IntArrayIntArray is Kotlin's specialized array type whose elements are 32-bit integers stored without boxing.
- boxed primitiveA wrapper type is a Java reference type that represents a value of a corresponding primitive type, such as Integer for int.
- boxingBoxing is the representation of a primitive value as an object instance such as java.lang.Integer.
- primitive arrayA primitive array is an array specialized for values of a primitive type and represented as a Java primitive array without boxing.
- platform typeA platform type is a Kotlin type imported from Java when the Java declaration provides no nullability information, leaving Kotlin unable to determine whether its value is nullable.
- platform typeA platform type is a Kotlin type imported from Java when the Java declaration provides no nullability information, leaving Kotlin unable to determine whether its value is nullable.
- unit typeA unit type is a type with exactly one value, commonly written (), that represents the absence of meaningful information or a meaningful result.
- MutableMapMutableMap is a Kotlin collection interface representing key-value associations that can be modified.
- data transformationmap is a Kotlin standard-library operation that transforms each element and collects the resulting values into a new collection.
- immutable collectionImmutability is the property that an object's state cannot change after the object is created.
- anyany is a Kotlin standard-library operation that returns true when at least one element in a collection satisfies a predicate.
- interoperabilityThe ability of software components, languages, or systems to work together and exchange data or invoke one another across their boundaries.
- extensionAn extension function is a Kotlin function defined outside a class that can be called with member-like syntax on values of an existing receiver type.
- KotlinKotlin is a statically typed programming language designed to interoperate with Java and run across multiple execution environments.
- hashsetHashSet is a set implementation that organizes elements using hash codes for efficient membership testing.
These guides are generated from the text of Manning books by Manning's knowledge graph and link to the sections they draw on. The full books are available on liveBook and at manning.com.
