Using generics.
How we would use data type specifications in Java and their benefits:
public class JavaGenerics {
public static void main(String[] args) {
List<String> list = new ArrayList<>(); // By giving an indication of data type we have improved error-checking, need less casting and code is clearer
list.add("Hello");
// list.add(new BigDecimal(10.5)) // Error-checking picks up on the incompatibility
list.get(0).toUpperCase(); // IDE suggests functions related to data type
List list1 = new ArrayList<>(); // However, we can still create a Collection without specifying type
}
}
The same in Kotlin, introducing Generics:
// Use generics to give the compiler a hint as to what type of objects you are dealing with, often related to Collections
// Allows compiler to do some error-checking at compile time
fun main() {
val list: MutableList<String> // Kotlin insists on knowing the data type
list = mutableListOf("Hello")
list.add("another string")
printCollection(list)
list[0].toUpperCase() // We can use the relevant functions
println("======")
val bdList = mutableListOf(BigDecimal(-33.45), BigDecimal(3503.99), BigDecimal(0.329))
// printCollection(bdList) // Won't allow printCollection because it expects String
printGenericCollection(bdList) // Works because Generics are used
println("======")
bdList.printCollectionExtension() // Same result using extension function - IDE suggests
// At runtime the application has none of the Generic information - type erasure. It is simply a syntactical feature e.g. it cannot run 'instanceOf' on a Generic since that information does not exist at runtime.
// Also true of Kotlin since it uses the JVM. How do we use the 'is' operator (equivalent of instanceOf)?
if (list is List<String>){} // This is allowed! How come, since Generics are lost at runtime? Suspect the function is handled at compile-time by Kotlin
val listAny: Any = listOf("str1", "str2") // To check this theory...
// if (listAny is List<String>) {} // This is invalid as the statement cannot be evaluated at compile-time
// if (listAny is List) // We can't even do this since Kotlin expects a data type
if (listAny is List<*>) {// So we do this instead - star projection syntax, sort of like wildcard
println("So let's assume this List contains strings") // If it isn't a List of non-Strings this
val strList = listAny as List<String> // Casting. This is how we use 'as' in conjunction with this. If it isn't a List of Strings it will throw a ClassCastException. Compiler will warn
println(strList[1].replace("str", "string"))
}
}
fun printCollection(collection: List<String>) {
for (item in collection) {
println(item)
}
}
fun <T> printGenericCollection(collection: List<T>) { // The <T> after fun is the type parameter declaration and is necessary
for (item in collection) {
println(item)
}
}
// Extension function example:
fun <T> List<T>.printCollectionExtension() {
for (item in this) {
println(item)
}
}
