Introduction
In previous topics, we discussed generic classes. We have mentioned that generics can accept any type of parameter and make it possible to reuse some code. But sometimes, the names of generics may get excessively long, which makes it harder to read the code.
In such situations, type aliases may help us improve code readability. Let's take a look at what they are and how they work.
Usage of type aliases
Type aliases provide alternative names for existing types – both standard types and custom ones. If the type name is too long, it's a good idea to introduce a different shorter name and use the new one instead.
class ClassWithVeryLongName{}
typealias SomeClass = ClassWithVeryLongName
Also, type aliases can be used if you want to emphasize the type, for example:
typealias Password = String
val myPassword: Password = "hyperskill"
Here we point out that "hyperskill" is a specific kind of string (a password), not just a common String.
Remember that type aliases should be top level! You cannot place them inside classes.
class Pet {
typealias Kitten = Pet.Kitten
class Kitten {
// compile error: nested and local type aliases are not supported
}
}
An alternative to type aliases are import aliases. If you're working with a long class name (especially, if you have package names within the class name), you can use import aliases to shorten it:
import Pet.Kitten as KittenThe principle of usage
You should remember that type aliases don't introduce new types. They are equivalent to the corresponding original types.
With type aliases, you can still use the fields and methods of the original type. For example:
typealias Kitten = Pet.Kitten
class Pet {
class Kitten(name: String) {
var kittenName = name
fun getName(): String {
return kittenName
}
}
}
fun main() {
var kitten: Kitten = Kitten("Fluffy")
println(kitten.getName()) // Fluffy
println(kitten.getName().length) // 6
}
You don't need to be worried that using type aliases can lead to some errors during runtime: compile time checks take care of that.
var kitten: Kitten = Kitten(6)/* compile-time error: The integer literal does not conform to the
expected type String*/
Thus, you will be warned that the type of the passed argument (Integer) doesn't match the expected type (String).
Also, you should always make sure that you really need type aliases and that they will really make your code more understandable.
Usage with generics
You can provide type aliases for generic classes as well as non-generic ones. And of course, you can use the original type fields and methods without any restrictions.
In generic programming, type aliases can be useful in shortening long generic types. For example:
typealias DessertBox<T> = BoxForSomeDessert<T>
class BoxForSomeDessert<T>(var dessert: T) {
fun getDessertFromBox(): T {
return dessert
}
}
class Tart(var name: String) {}
fun main() {
var tartBox = DessertBox(Tart("tastytart"))
println(tartBox.getDessertFromBox().name) // tastytart
}
When you declare a type alias for a generic type, don't forget to add <T> after the type alias name!
Naming type aliases
Try to define readable and meaningful type aliases so that other programmers can understand your code. Let's take a look at two examples: a bad one and a good one:
/* bad example */
typealias SomeClass = HashMap<Int, List<String>>
/* good example */
typealias FileTable = MutableMap<Int, MutableList<File>>
In the first example, we can't really understand the use of SomeClass and the type name is too vague. The second case is much better because we see that FileTable is a synonym of a class that defines a list of some files.
Conclusion
Type aliases allow you to shorten long generic names, but they don't introduce new types. They can improve the readability of your code if you use them wisely. Always ask yourself whether using a type alias will make your code more understandable.