Translation Instructions:
When (and When not) to Use Type Parameter Constraints in Kotlin
Original address:Typealias.com/guides/when…
Original author:Dave Leeds
Previous Kotlin articles, please check them out:
Translation series:
- An easy way to remember Kotlin’s parameters and arguments
- Should Kotlin define functions or attributes?
- How to remove all of them from your Kotlin code! (Non-empty assertion)
- Master Kotlin’s standard library functions: run, with, let, also, and apply
- All you need to know about Kotlin type aliases
- Should Sequences or Lists be used in Kotlin?
- Kotlin’s turtle (List) rabbit (Sequence) race
- The Effective Kotlin series considers using static factory methods instead of constructors
- The Effective Kotlin series considers using a builder when encountering multiple constructor parameters
Original series:
- Everything you need to know about the Kotlin property broker
- Source code parsing for Kotlin Sequences
- Complete analysis of Sets and functional apis in Kotlin – Part 1
- Complete parsing of lambdas compiled into bytecode in Kotlin syntax
- On the complete resolution of Lambda expressions in Kotlin’s Grammar
- On extension functions in Kotlin’s Grammar
- A brief introduction to Kotlin’s Grammar article on top-level functions, infix calls, and destruct declarations
- How to Make functions call Better
- On variables and Constants in Kotlin’s Grammar
- Elementary Grammar in Kotlin’s Grammar Essay
Actual combat series:
- Use Kotlin to compress images with ImageSlimming.
- Use Kotlin to create a picture compression plugin.
- Use Kotlin to compress images.
- Simple application of custom View picture fillet in Kotlin practice article
Brief description (again):
Today this article is still very simple, as long as understand a thing can be. That is the constraint on type parameters in generics, a concept that is also present in Java. But we have a question: when do we use generic type parameters?
Let’s get down to business.
When you declare a generic, Kotlin allows you to add constraints to the type parameters of the generic. In other words, you limit the type parameters to only one type. So what does this do? Let’s take a look at this example:
Imagine this demand scenario: Suppose you have several pets at home and you want to choose a favorite:
fun <T> chooseFavorite(pets: List<T>): T {
val favorite = pets[random.nextInt(pets.size)]
// This next line won't compile - because `name` can't be resolved
println("My favorite pet is ${favorite.name}")
return favorite
}
Copy the code
The println() function declaration won’t compile because you can’t reference the name property by T. Because T can be anything the caller specifies. For example, T, which is an Int, obviously has no name attribute, as implemented in the following code:
chooseFavorite(listOf(1.2.3))
Copy the code
Solution one – abandon generics
You can try modifying this function to get rid of generics:
fun chooseFavorite(pets: List<Pet>): Pet {
val favorite = pets[random.nextInt(pets.size)]
println("My favorite pet is ${favorite.name}")
return favorite
}
Copy the code
This may seem fine, but we encounter an unwanted return value type. Here’s what happens when we call this function:
val pets: List<Dog> = listOf(Dog("Rover"), Dog("Sheba"))
val favorite: Pet = chooseFavorite(pets)
Copy the code
Although we declare List< Dog >, chooseFavorite returns a Pet type, unless we use a cast here.
Solution 2 – Use type parameter constraints
We can limit type parameters by specifying upper limits – in other words, specifying the supertypes you want to receive. In our case, we want this function to handle List
as List
fun <T : Pet> chooseFavorite(pets: List<T>): T {
val favorite = pets[random.nextInt(pets.size)]
println("My favorite pet is ${favorite.name}")
return favorite
}
Copy the code
The declaration of the type parameter is
val pets: List<Dog> = listOf(Dog("Rover"), Dog("Sheba"))
val favorite: Dog = chooseFavorite(pets)
Copy the code
Use advice
There are two types of constraints where you need to use type parameters:
- When you call specific functions or properties on a type (that is, functions and properties that are unique to a class of a type)
- When you want to preserve a particular type when a function returns
This is a quick cheat sheet to help you decide what to use in which situations.
Members need to be called (member functions or attributes of the class) | There is no need to call members (member functions or attributes of the class) | |
---|---|---|
Type to Preserve | Use generics with type parameter constraints | Use generics without type parameter constraints |
There is no need to preserve the type | Use non-generics and appropriate abstractions | Use native types in Java |
How to specify constraints
There are many more constraints – you can specify constraints for multiple parameters, as well as multiple constraints on the same parameter. For all the details, check out the concept article Type Parameter Constraint. You can also quickly view common constraints in Kotlin’s official reference documentation.
Readers have something to say
The core of this article is when generic constraints should be used. The authors have a good summary of that table. If you understand and master that table, you will be in a good position to use generic parameter constraints. The table may be a little hard to understand, but let me add an explanation here:
- First, let’s explain whether we need to call members, which means whether we use generic parameters inside the function declaration to constrain certain members of the corresponding class in the upper bound type, including functions or attributes. For example, the name attribute in chooseFavorite method is a specific member attribute of the Pet class. In a word: whether a particular type of class member or attribute needs to be called inside a function definition directly determines whether the type parameter constraint is required. If no member needs to be called, then the type parameter constraint is not required
- And then, explainWhether the type needs to be preservedIn this example, the first solution is to remove the generics and use the abstract parent class Pet instead, even if it is passed in
List<Dog>
However, chooseFavorite method returns Pet as its parent and receives type Dog externally, so casting is unavoidable. In a word:The need to preserve the type directly determines whether to use generics. If you do not preserve the type, you do not need to use generics. The external receiver of the function can use the abstract parent class to receive it. If the type is reserved, the external receiver of the function must be explicit about a type, so if you can’t avoid type conversions with abstract superclasses, then you should use generics - Finally, a simple conclusion is drawn:
1. The need to call a member determines whether to use generic type parameter constraints if generics are used. Otherwise you can just use abstraction
2. The retention of types determines whether generics are used
3. To preserve a type and call a member is to use generics with parameter constraints
4. To call a member without preserving a type is to not use generics and appropriate abstractions
5. To preserve a type without calling a member is to use a generic type without parameter constraints
6. If you do not retain a type and do not call a member, you are using Java native ecological types, such as the List type in Java
Welcome to the Kotlin Developer Association, where the latest Kotlin technical articles are published, and a weekly Kotlin foreign technical article is translated from time to time. If you like Kotlin, welcome to join us ~~~