Chapter 9: Operator overloading and other conventions
Introduces Kotlin conventions as language operations enabled by specially named functions and explains why extensions make those conventions useful for existing classes.
After this chapter you will be able to
- Function-based Kotlin conventions. Explain that Kotlin can implement language constructs by calling functions with specific names, rather than requiring a class to implement a special interface. Give the section’s example: a class with a suitable plus function can support the + operator.
- Extensions for existing classes. Justify Kotlin’s use of named-function conventions for existing Java classes: extension functions can supply convention methods without changing a third-party class to implement another interface.
- Arithmetic operators beyond primitives. Explain why Kotlin arithmetic operators can make code for other types more convenient, using BigInteger addition or adding an element to a collection as an example.
- Binary operator function convention. Explain how defining an operator plus function lets two Point objects be added with +, and describe why the operator modifier is required. State that the function can be a member or an extension.
- Operand and result type flexibility. Describe how an operator function may use different operand types and return a different type. Explain why supporting both orders of two operand types requires separate definitions, and how overloads can share a name.
- Compound assignment behavior. Explain how += can use plus to compute and reassign a value, or call an operator plusAssign function to modify the existing object without reassigning its reference.
- Collection assignment behavior. Distinguish how + and - behave for collections from how += and -= behave for mutable and read-only collections, including the variable requirement for a read-only collection.
- Unary operator function convention. Explain how a type overloads a unary operator and how unary operator functions differ from binary ones.
- Increment and decrement semantics. Describe how overloaded `inc` and `dec` behave with prefix and postfix operators.
- Comparison operators for objects. Explain that Kotlin lets you use operators such as ==, !=, >, and < with objects, not just primitive types, instead of calling equals or compareTo as in Java.
- Nullable equality comparisons. Explain how == and != use equals and why they work with nullable operands.
- Implementing the equals convention. Explain why a manual equals implementation overrides Any.equals rather than defining an extension or adding operator.
- Ordering operator convention. Explain how Kotlin evaluates ordering operators for comparable objects and how this differs from Java’s explicit compareTo calls.
- Person ordering with Comparable. Describe how the section defines an ordering for Person objects using Comparable and compareValuesBy.
- Collection and range operator syntax. Describe how Kotlin uses a[b] to get or set an element by index and in to check membership in a collection or range or to iterate over a collection. State that collection-like classes can support these operations too.
- Indexed reads with get. Explain how to make a class support square-bracket reads, and describe the index types and number of indices that get can accept.
- Indexed assignments with set. Explain how a class supports assignment through square brackets and how the bracket indices and assigned value become set arguments.
- The in and contains convention. Explain how an in expression checks membership by calling contains, identifying the receiver and argument.
- Range creation with rangeTo. Explain that .. calls rangeTo, which returns a range, and that the standard library lets comparable objects form ranges without defining their own operator.
- For-loop iterator convention. Explain that in a Kotlin for loop, in means iteration: the loop calls iterator() on the object and repeatedly calls hasNext() and next() on the result.
- Custom date range iteration. Describe how an iterator function can make a custom or third-party type iterable, using the section’s LocalDate range example.
- Destructuring component function calls. Explain how a destructuring declaration initializes several local variables from one value, including how componentN functions supply those values.
- Map entry destructuring in loops. Explain how a loop can iterate directly over a Kotlin map and destructure each entry into its key and value.
- Skipping Unneeded Destructured Values. Explain how to omit unused trailing components and use _ to skip an unused component before one you still need.
- Positional Destructuring Risks. Explain why changing a data class’s property order can silently change the values assigned by destructuring, and describe where the section recommends using destructuring.
- Delegated property accessor logic. Explain how a delegated property uses a helper object for its accessor logic and why that is useful for properties that do more than store a value in a backing field.
- Delegated property access flow. Trace how a delegated property obtains its delegate and how property access reaches that object.
- Delegate operator function requirements. Distinguish the functions required for delegated property access from the optional function used when creating a delegate.
- Lazy delegated property. Explain how `by lazy()` replaces the manual backing property and what the section says about its initialization and thread safety.
- Custom delegate convention. Describe the getValue and setValue convention needed for ObservableProperty to serve as a delegate, and trace what by does when the delegated property is accessed or changed.
- Delegated property compiler translation. Explain that the compiler stores a MyDelegate instance in a hidden property, represents the delegated property with a KProperty object, and generates accessor calls to getValue and setValue.
- Map-backed dynamic attributes. Explain why a map is useful for an object with varying attributes and how delegating a property to that map provides named access to a value.
- Column delegates for attributes. Trace how a column object delegates reads and writes of a User attribute, including the effect of an expression such as user.age += 1.
What it builds on
- Adding methods to other people’s classes: Extension functions and properties (chapter 3) · Introduces extension functions as a way to add callable functions to other people’s classes.
- Working with pairs: Infix calls and destructuring declarations (chapter 3) · Infix calls put a function name between its operands.
- Universal object methods (chapter 4) · Earlier discussion of the universal equals method.
- No overriding for extension functions (chapter 3) · Extension functions do not override members.
- Data classes: Autogenerated implementations of universal methods (chapter 4) · Data classes can receive a compiler-generated equals implementation.
- Using in to check collection and range membership (chapter 2) · Earlier use of in for collection and range membership.
- Read-only and mutable collections (chapter 8) · The distinction between mutable and read-only collections.
- Iterating over numbers: Ranges and progressions (chapter 2) · Basic ranges and their endpoints.
Section by section
Introduction
Introduces Kotlin conventions as language operations enabled by specially named functions and explains why extensions make those conventions useful for existing classes.
- Function-based Kotlin conventions: Explain that Kotlin can implement language constructs by calling functions with specific names, rather than requiring a class to implement a special interface. Give the section’s example: a class with a suitable plus function can support the + operator.
Why it matters: It explains how a Kotlin class can support familiar syntax such as +. - Extensions for existing classes: Justify Kotlin’s use of named-function conventions for existing Java classes: extension functions can supply convention methods without changing a third-party class to implement another interface.
Why it matters: It shows why conventions can work with classes you do not control.
Key terms: conventions, plus, operator, extension functions, third-party classes
9.1 Overloading arithmetic operators makes operations for arbitrary classes more convenient
Introduces why Kotlin arithmetic operators are useful for types beyond Java’s usual operands.
- Arithmetic operators beyond primitives: Explain why Kotlin arithmetic operators can make code for other types more convenient, using BigInteger addition or adding an element to a collection as an example.
Why it matters: This explains the appeal of operator syntax before the chapter shows how to provide it.
Key terms: arithmetic operators, BigInteger, +, +=
9.1.1 Plus, times, divide, and more: Overloading binary arithmetic operations
This section explains how Kotlin maps binary arithmetic syntax to operator functions, including their type flexibility, precedence, and Java interoperability.
- Binary operator function convention: Explain how defining an operator plus function lets two Point objects be added with +, and describe why the operator modifier is required. State that the function can be a member or an extension.
Why it matters: It connects concise arithmetic syntax to the function your code actually defines. - Operand and result type flexibility: Describe how an operator function may use different operand types and return a different type. Explain why supporting both orders of two operand types requires separate definitions, and how overloads can share a name.
Why it matters: It helps you define exactly the arithmetic expressions your types should support. - Fixed operators and precedence: Explain that Kotlin permits overloading a limited set of existing operators rather than inventing new symbols, and that overloaded arithmetic keeps standard precedence. Distinguish bitwise operations, which use regular functions with infix call syntax instead of special operators.
Why it matters: It sets expectations for which expressions you can create and how they will be evaluated. - Java operator interoperability: Describe how Java calls Kotlin overloaded operators and when Kotlin can use operator syntax for Java methods. Explain the role of a correctly named extension when an existing Java method has a different name.
Why it matters: It shows how operator-style Kotlin code can work alongside existing Java APIs.
Key terms: operator keyword, plus function, extension function, operand types, return type, commutativity, operator functions, binary operators, precedence, infix call syntax
9.1.2 Applying an operation and immediately assigning its value: Overloading compound assignment operators
This section explains how compound assignment can either reassign a computed value or modify an object, and how those choices apply to custom classes and collections.
- Compound assignment behavior: Explain how += can use plus to compute and reassign a value, or call an operator plusAssign function to modify the existing object without reassigning its reference.
Why it matters: The same += syntax can mean reassignment or in-place modification. - Consistent operator design: Explain why defining both plus and plusAssign for the same applicable use can cause a compiler error, and describe the section’s recommended choice for immutable versus mutable classes.
Why it matters: A consistent operator design avoids ambiguous compound assignments. - Collection assignment behavior: Distinguish how + and - behave for collections from how += and -= behave for mutable and read-only collections, including the variable requirement for a read-only collection.
Why it matters: Collection mutability determines whether compound assignment changes a collection or replaces the referenced value.
Key terms: compound assignment operators, plus, plusAssign, operator, Unit, immutable, mutable, mutable collections, read-only collections, var
9.1.3 Operators with only one operand: Overloading unary operators
This section explains how to overload unary operators and how Kotlin preserves the usual prefix and postfix behavior of increment and decrement.
- Unary operator function convention: Explain how a type overloads a unary operator and how unary operator functions differ from binary ones.
Why it matters: This convention lets your own types support unary operator syntax. - Increment and decrement semantics: Describe how overloaded `inc` and `dec` behave with prefix and postfix operators.
Why it matters: You can use familiar increment and decrement syntax without implementing prefix and postfix behavior separately.
Key terms: unary operator, member, extension, operator, inc, dec, pre-increment, post-increment, BigDecimal
9.2 Overloading comparison operators makes it easy to check relationships between objects
This section introduces Kotlin’s use of comparison operators with objects as a concise alternative to calling comparison methods directly.
- Comparison operators for objects: Explain that Kotlin lets you use operators such as ==, !=, >, and < with objects, not just primitive types, instead of calling equals or compareTo as in Java.
Why it matters: You can read and write object comparisons using familiar operator syntax.
Key terms: comparison operators, equals, compareTo
9.2.1 Equality operators: equals (==)
This subsection explains how Kotlin’s equality operators use equals, handle nulls, and differ from identity comparison.
- Nullable equality comparisons: Explain how == and != use equals and why they work with nullable operands.
Why it matters: It explains both ordinary equality checks and why comparing nullable values is safe. - Identity versus equality: Distinguish the identity operator === from == and describe its use in an equals implementation.
Why it matters: It helps distinguish a same-object shortcut from the equality behavior supplied by equals. - Implementing the equals convention: Explain why a manual equals implementation overrides Any.equals rather than defining an extension or adding operator.
Why it matters: It identifies where custom equality behavior must be implemented for == to use it.
Key terms: ==, !=, equals, nullable operands, identity equals operator, ===, Any, override, operator, extension
9.2.2 Ordering operators: compareTo (<, >, ⇐, and >=)
Explains how Kotlin turns ordering operators into compareTo calls and how a Comparable implementation can define an ordering for a class.
- Ordering operator convention: Explain how Kotlin evaluates ordering operators for comparable objects and how this differs from Java’s explicit compareTo calls.
Why it matters: This lets you read concise comparisons without losing sight of the method they call. - Person ordering with Comparable: Describe how the section defines an ordering for Person objects using Comparable and compareValuesBy.
Why it matters: A defined ordering makes Person objects usable with Kotlin’s comparison syntax and Java sorting functions.
Key terms: Comparable, compareTo, comparison operators, Person, compareValuesBy, selector functions, property references
9.3 Conventions used for collections and ranges
Introduces the operator syntax for indexed access and membership or iteration, and notes that collection-like classes can support these operations.
- Collection and range operator syntax: Describe how Kotlin uses a[b] to get or set an element by index and in to check membership in a collection or range or to iterate over a collection. State that collection-like classes can support these operations too.
Why it matters: This connects familiar collection operations to the operator syntax Kotlin also allows for your own collection-like classes.
Key terms: indexed access operator, a[b], in operator, collections, ranges
9.3.1 Accessing elements by index: The get and set conventions
This section explains how Kotlin translates square-bracket reads and assignments into operator functions that classes can define.
- Indexed reads with get: Explain how to make a class support square-bracket reads, and describe the index types and number of indices that get can accept.
Why it matters: This lets your own types offer natural indexed access, including access by keys or multiple coordinates. - Indexed assignments with set: Explain how a class supports assignment through square brackets and how the bracket indices and assigned value become set arguments.
Why it matters: It explains what your mutable type must provide for bracket assignments to work.
Key terms: indexed access operator, get, operator, set, mutable point
9.3.2 Checking whether an object belongs to a collection: The in convention
This section explains how Kotlin’s in operator calls contains and why open ranges suit rectangle membership checks.
- The in and contains convention: Explain how an in expression checks membership by calling contains, identifying the receiver and argument.
Why it matters: This lets you understand what a membership check does when you define contains for your own type. - Open ranges for rectangle membership: Describe the difference between open and closed ranges and why the rectangle example uses an open range.
Why it matters: The range’s endpoint rule determines whether a point on the rectangle’s bottom or right boundary counts as inside.
Key terms: in operator, contains, open range, closed range, ..< operator
9.3.3 Creating ranges from objects: The rangeTo and rangeUntil conventions
Explains how Kotlin creates ranges with rangeTo and rangeUntil, including ranges of comparable objects and syntax for using range expressions.
- Range creation with rangeTo: Explain that .. calls rangeTo, which returns a range, and that the standard library lets comparable objects form ranges without defining their own operator.
Why it matters: It explains why range syntax works for objects beyond numbers. - Exclusive upper range bound: Distinguish the rangeUntil operator from rangeTo by describing what happens to the upper bound.
Why it matters: It lets you choose whether a range includes its endpoint. - Range expression grouping: Describe when parentheses clarify or enable the use of a range expression.
Why it matters: Parentheses make the intended range expression clear and allow method calls on it.
Key terms: .., rangeTo, Comparable, range, rangeUntil, ..<, open-end range, upper bound, rangeTo operator, parentheses
9.3.4 Making it possible to loop over your types: The iterator convention
Explains how Kotlin’s iterator convention makes for loops work with built-in and custom types, including date ranges.
- For-loop iterator convention: Explain that in a Kotlin for loop, in means iteration: the loop calls iterator() on the object and repeatedly calls hasNext() and next() on the result.
Why it matters: This explains what a type must provide to work in a for loop. - Custom date range iteration: Describe how an iterator function can make a custom or third-party type iterable, using the section’s LocalDate range example.
Why it matters: You can enable for-loop iteration even for types whose class definitions you cannot change.
Key terms: iterator convention, iterator, hasNext, next, extension function, ClosedRange<LocalDate>, Iterator<LocalDate>
9.4 Making destructuring declarations possible with component functions
This section explains how component functions enable destructuring declarations and how destructuring can unpack returned objects and collections.
- Destructuring component function calls: Explain how a destructuring declaration initializes several local variables from one value, including how componentN functions supply those values.
Why it matters: This explains what must be available on a value before you can destructure it. - Multiple return value choices: Describe how destructuring makes values returned together by a function easy to use, and explain the expressiveness tradeoff between a named data class and Pair or Triple.
Why it matters: It helps you choose a return type that is convenient without obscuring what its values mean. - Collection destructuring limits: Explain when arrays and collections can be destructured and how many elements the standard library supports through this syntax.
Why it matters: It shows when collection destructuring is convenient and where its built-in support stops.
Key terms: destructuring declaration, componentN, data class, primary constructor, Pair, Triple, arrays, collections, first five elements
9.4.1 Destructuring declarations and loops
This section shows how destructuring declarations work in loops and lambdas, using map entries as examples.
- Map entry destructuring in loops: Explain how a loop can iterate directly over a Kotlin map and destructure each entry into its key and value.
Why it matters: It explains why a compact map loop can access keys and values without explicitly unpacking each entry. - Destructuring composite lambda values: Describe how destructuring can be used when a lambda receives a composite value, such as a map entry passed to forEach.
Why it matters: It lets the same unpacking idea carry over from loops to collection operations.
Key terms: destructuring declarations, iterator, Map.Entry, component1, component2, lambda, composite value, forEach
9.4.2 Ignoring destructured values using the _ character
This section shows how to skip unneeded values in a destructuring declaration and explains why positional destructuring can be fragile.
- Skipping Unneeded Destructured Values: Explain how to omit unused trailing components and use _ to skip an unused component before one you still need.
Why it matters: You can avoid unused variables without accidentally shifting the values assigned to variables you need. - Positional Destructuring Risks: Explain why changing a data class’s property order can silently change the values assigned by destructuring, and describe where the section recommends using destructuring.
Why it matters: A property reorder can introduce incorrect assignments without making the destructuring code fail.
Key terms: destructuring declaration, component1, component2, _ character, positional, componentN functions, data class, destructuring declarations
9.5 Reusing property accessor logic: Delegated properties
Introduces delegated properties as a way to reuse property accessor logic by handing it to a helper object.
- Delegated property accessor logic: Explain how a delegated property uses a helper object for its accessor logic and why that is useful for properties that do more than store a value in a backing field.
Why it matters: Delegation lets properties share accessor behavior when a simple backing field is not enough.
Key terms: delegated properties, delegation, delegate, accessors, backing fields
9.5.1 Basic syntax and inner workings of delegated properties
This section explains how a property delegates its accessor behavior to an object and which functions that object must provide.
- Delegated property access flow: Trace how a delegated property obtains its delegate and how property access reaches that object.
Why it matters: It explains why a property looks ordinary to callers while another object handles its accessors. - Delegate operator function requirements: Distinguish the functions required for delegated property access from the optional function used when creating a delegate.
Why it matters: These conventions determine whether an object can serve as a property delegate.
Key terms: delegated property, by, delegate, hidden helper property, getValue, setValue, provideDelegate, operator functions, mutable properties
9.5.2 Using delegated properties: Lazy initialization and by lazy()
This section explains why lazy initialization is useful, shows the limitations of implementing it with a backing property, and introduces `by lazy()` as a simpler alternative.
- On-demand property initialization: Explain why a property might be initialized only when first accessed and why its value should then be reused.
Why it matters: It explains why the example delays loading a person’s emails. - Backing property trade-offs: Describe how the `_emails` backing property supports lazy loading and why this manual approach has drawbacks.
Why it matters: It shows both what the delegate replaces and why the manual version may be unsafe. - Lazy delegated property: Explain how `by lazy()` replaces the manual backing property and what the section says about its initialization and thread safety.
Why it matters: It is the section’s simpler solution to the manual implementation’s boilerplate and concurrency problem.
Key terms: lazy initialization, first access, backing property, _emails, emails, thread safe, delegated property, by, lazy, lambda
9.5.3 Implementing your own delegated properties
This section uses observable properties to show how a custom delegate removes repeated setter logic and how Kotlin’s standard library provides an alternative.
- Observable property notifications: Explain why the Person example notifies observers when age or salary changes, and how extracting the value storage and notification logic reduces repeated setter code.
Why it matters: The repeated setter code makes the benefit of delegated properties concrete. - Custom delegate convention: Describe the getValue and setValue convention needed for ObservableProperty to serve as a delegate, and trace what by does when the delegated property is accessed or changed.
Why it matters: This is the connection between a property declared with by and the code that handles its reads and writes. - Standard observable delegate option: Explain how the standard-library observable property support replaces the hand-written logic, and state what kinds of expressions can supply a delegate.
Why it matters: You can reuse standard-library behavior without assuming it knows how your application reports changes.
Key terms: observable, Observer, ObservableProperty, onChange, by, delegate, operator, getValue, setValue, thisRef
9.5.4 Delegated properties are translated to hidden properties with custom accessors
This section explains how the compiler implements delegated properties and why that mechanism makes property behavior customizable.
- Delegated property compiler translation: Explain that the compiler stores a MyDelegate instance in a hidden property, represents the delegated property with a KProperty object, and generates accessor calls to getValue and setValue.
Why it matters: It explains what happens when code reads or writes a delegated property. - Customizable property storage and access: Describe how delegation lets code customize both where a property's value is stored and what happens when it is accessed.
Why it matters: Delegation can add storage and access behavior without repeating it in each property's accessors.
Key terms: delegated property, hidden property, KProperty, getValue, setValue, delegated properties, validation, change notifications
9.5.5 Accessing dynamic attributes by delegating to maps
This section shows how a map can back delegated properties for objects with dynamically defined attributes.
- Map-backed dynamic attributes: Explain why a map is useful for an object with varying attributes and how delegating a property to that map provides named access to a value.
Why it matters: Map delegation gives dynamically stored data a convenient property-style interface.
Key terms: delegated property, Map, MutableMap, getValue, setValue
9.5.6 How a real-life framework might use delegated properties
This section uses a database framework example to show how delegated properties can make entity attributes behave like ordinary Kotlin properties while controlling how their values are retrieved and updated.
- Database entity property model: Describe how the example represents a database table and its users, and why accessing their properties is convenient.
Why it matters: This model explains what the delegated properties let application code access without working directly with the column-to-value mapping. - Column delegates for attributes: Trace how a column object delegates reads and writes of a User attribute, including the effect of an expression such as user.age += 1.
Why it matters: It shows how framework code can attach database-aware behavior to familiar property syntax.
Key terms: Users, User, Entity, columns, delegated property, Column, getValue, setValue, dirty
9.6 Summary
Where this is used later
- Chaining infix calls: The should function in test frameworks (chapter 13) · Chained infix calls use named functions rather than newly invented operator symbols.
- Restricting the type a generic class or function can use: Type parameter constraints (chapter 11) · Introduces the comparison convention that translates > to compareTo.
- The invoke convention: Objects callable as functions (chapter 13) · Uses the earlier get convention as an analogy for concise operator syntax.
- Member extension functions: Internal DSL for SQL (chapter 13) · Provides the database-framework example revisited when discussing an internal DSL for SQL.
Concepts explained in this chapter
- delegated propertyA delegated property is a Kotlin property whose read or write behavior is provided by a separate delegate object.
- operator modifierThe operator modifier is a Kotlin declaration modifier that permits a function to implement a predefined operator convention.
- operatorThe `..<` operator constructs a range that includes its lower endpoint and excludes its upper endpoint.
- getValuegetValue is a Kotlin property-delegation operator function that retrieves a delegated property's value from a receiver and property reference.
- setValuesetValue is a Kotlin property-delegation operator function that stores a delegated property's value using a receiver and property reference.
- operator overloadingOperator overloading is a language feature that assigns custom implementations to predefined operator symbols.
- 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.
- componentN functionA componentN function is a Kotlin convention-named function that returns the Nth component of a composite value for use in a destructuring declaration.
- delegationDelegation is a composition pattern in which one object forwards operations or property lookup to another object that provides the behavior.
- destructuring declarationA destructuring declaration is Kotlin syntax that initializes multiple variables from the component values of an expression.
- operator functionAn operator function is a Kotlin function marked with the operator modifier whose name and parameters implement a predefined operator convention.
- KPropertyKProperty is a Kotlin reflection interface that represents a property declaration and provides access to its reflected metadata and value.
- ..< operatorThe `..<` operator constructs a range that includes its lower endpoint and excludes its upper endpoint.
- equality by valueequals is an object method that determines whether another value is equal to the receiver according to the object's equality contract.
- membership operatorThe `in` operator tests whether a value belongs to a collection, range, or other container by invoking its containment operation.
- intervalAn interval is a connected set or span of values between specified lower and upper endpoints, with endpoint inclusion determined by its definition.
- MutableMapMutableMap is a Kotlin collection interface representing key-value associations that can be modified.
- infix functionAn infix call is Kotlin call syntax that places a one-parameter function name between its receiver and argument without parentheses or other separators.
- collectionA collection is a container or aggregate that holds multiple elements and supports operations such as membership testing.
- custom operatorA custom operator is a programmer-provided implementation of a predefined operator symbol.
- read-only collection interfaceA read-only collection interface exposes collection contents and traversal operations without exposing mutation operations through that interface.
- overrideMethod overriding is the definition of a subclass member function that replaces an inherited implementation with the same signature.
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.
