generics-kotlin

Generics

Generics is a technique whereby functions and classes can be written in terms of types that are not specified when there are written, then later used for many different types.

For example, You can write a single sort method that could sort the elements in an Integer array, String array , or any type of array that supports sorting.

Generics provide a way for you to re-use the same code with different inputs, parametric polymorphism and templates are names used in other languages.

Advantages of generic:

  • Enables programmer to implement generic algorithms, work on collection of different types,
  • Compile time checking allows programmers to catch invalid types at compile rather than on run time, run time error are more costly
  • Type-safety : We can hold only a single type of objects in generics. It doesn’t allow to store other objects.
  • Type casting is not required: There is no need to typecast the object. Generics eliminates the risk of ClassCastException which was common with collections

Spread Operator (*)

Kotlin Generics with Classes

You can declare a function or class with generics, classes in kotlin may have type parameters, sometimes referred to as container because of close association with collections .

To declare a parameterized type, we use the angle bracket syntax on the right-hand side of the type name and put the type parameters in the angle brackets.

You can use the type parameters in the body of the class, just like any other types.


								class Bucket<T>
								

We can declare more than one type parameter.


class Dictionary<K, V>

The most commonly used parameterized types are collections.

We can declare a typed parameter with the primary constructor.


class Bucket<T>(t:T){
	var SomeName = t
}

To create an instance of such class, we need to provide th type arguments, we must "fill in"? that type when we instantiate it by replacing the parameters with concrete or proper types.


class Bucket<T>{}
var buck:Bucket = Box(10)

The parameters may be inferred and thus type arguments can be omitted, 10 has type Int, so the compiler knows that, and we can remove the type.


class Bucket<T>{}
var buck = Box(10)

Kotlin Standard Functions

Generic Functions

Functions can also have type parameters; type parameters are placed before the name of the function.

We can write the function using type parameters


fun  sayGreeting(one:T, two:T, three:T):T

In above example, we have defined a single type parameter-T-which we then use for all three parameters and the return type.

We are informing the compiler that whatever type we fix T to be, we will return that same type. This allows the compiler to correctly infer the return type.

To invoke this function, we don't need to do anything other than pass in instances, respecting the relationship between them:


val greet: String = sayGreeting("hello", "namasthe", "bonjour")

Functions can have more than one type of parameter.


fun  put(key:K, value:V):Unit

You can apply the generics on top-level function and extension functions as well.


fun  T.basic():String(){}

Operator Overloading in kotlin

Generics at RunTime | Type erasure

Generics on the JVM are normally implemented through type erasure, the type of arguments of an instance of a generic class are not preserved at run time.

Type arguments for generic types only exist at compile time, you cannot use types with type augments together with the is operator

Type arguments are erased at runtime, as a workaround, type parameters of inline functions can be marked as reified.


inline fun  isA(value:Any) = value is T
println(isA("abc"))
println(isA(123))

Higher Order Functions

Generic Constraints

Generic constraints restrict the set of all possible types that can be substituted for a given type parameter.

The most common type of constraint is an upper bound that corresponds to Java's extends keyword. This will restrict the types to those that are subclasses of the bound. To use an upper bound, simply declare it alongside the type parameter.


fun Comparable> sort(list:list){

The type specified after a colon is the upper bound, In the above example, only a subtype of Comparable may be substituted for T. Comparable is the standard library type that defines the compareTo method, used for comparison.

In the above example, we defined our type parameter with the upper bound of Comparable, any time this function is called, the value of T must extend from this type.

In below example, T must be subclass of Number class.


fun  divideByTwo(value:T):Double{
    return value.toDouble() /2.0
}
fun main(args: Array) {
    println(divideByTwo(8))
}

datatypes-kotlin

The default upper bound is Any?

Only one Upper bound can be specified inside he angle brackets.

where in kotlin generics

Sometimes, you might want to have more than one upper bound, we need a separate where-clause., we move the upper bound declaration out of the type parameter.


fun  cloneWhenGrater(list:List, threshold:T):List
	where T:Comparable{
		T:Cloneable{
			return list.filter{it >threshold}.map{it.clone()
		}
}				

In below, example we would be accepting a value which is sub class of CharSequence and Appendable, I hope you know the difference between String & String Builder.

suffixKotlin function adds '_KOTLin' to the given value at the end


fun  suffixKotlin(str:T) where T: CharSequence, T:Appendable{
    str.append("_KOTLIN")
}
fun main(args: Array) {
    var name = StringBuilder("cherchertech")
   suffixKotlin(name)
    println(name)
}

Classes also can define multiple upper bounds, the syntax is similar, with the where clause written after the type parameter.


class MultipleUpperBoundedClass	<T> where T:Comparable<T>, T: Serializable{

Exceptions in kotlin

Type Variance

Type variance describe how types with the same base type and different type arguments relate to each other.

List<String> and List<Any>

If we consider a class Benz, which is a subtype of Car, then is a Crate<Benz> a subtype of a Crate? The first instinct is to think 'of course', since an Apple can be used where a Fruit is required, but generally speaking the answer is no.

In fact, a Crate<Benz> can be a subtype of Crate<Car>, a supertype of it, or neither depending on which type of variance is used.

Don't hurry with Inheritance to derive conclusion when you are using the classes in Generics.

Understanding the variance is important when you write your own/custom generic classes or function in kotlin.

It helps you create APIs that don't restrict users in inconvenient ways and don't break their type-safety expectations.

Types of variance in Kotlin :

  • Invariance
  • Co-Variance
  • Contra-Variance

Invariance :

A generic class is called invariant, if any two different types A and B, someClass<A> is not subtype or a supertype of someClass<B>

So Speed<Benz> might not be a subtype of Speed<Car>.

In kotlin, type parameters are invariant by default, which mean there is no subtype relationship between the types.

Type M<T> is neither a subtype or a supertype of M<U>, regardless of the relationship between T and U.

So to compiler a Speed<Benz> and a Speed<Car> are as related as a Speed<Benz> and Speed<Duck>.

Co-Variance :

A generic class is called covariant, if any two types A and B, SomeClass<A> is a subtype of SomeClass<B> if A is subtype of B.

For example, Producer<AntMan> is subtype of Produces<Avenger> because AntMan is subtype of Avenger

To declare the class to be covariant on a certain type parameter you put the out keyword before the name of the type parameter.


interface Producer<out T>
{
	fun produce():T
}

Marking a type parameter of a class as co-variant makes it possible to pass values of that class as function parameters and return values when the type arguments do not exactly match the ones in the function definition.

You cannot make any class covariant, it would be unsafe. Constraints the possible user of this type parameter in the class.

To guarantee type safety, it can be used only in out positions, the class can give out values of type T but not make them in.

For example, a class that declares a type parameter T and contains a functions that uses T., if the T is used as return type of a function, it is in out position, the function produces values of Type T.

Contra-Variance :

Contra-VarianceContra-Variance is opposite of covariant, the relationship between the type parameters is reversed in the types themselves.

String is subtype of Any, Box- would be a supertype of a Box if Box had its type parameter marked as contra-variant.

To mark a type parameter as contra-variant, we mark the type parameter with the keyword in.


interface Generator<in T>
{
	fun produce():T
}

Above example, a class that declares a type parameter T and contains a function that uses T, if T is used as the type of a function parameter, it's in in position, The function consumes the values of the T.

Inner & Nested Classes in Kotlin

Summary of Variance

  1. variance is a way to specify whether the type parameter of a type can be substituted for its subclass or superclass
  2. You can declare a class as co-variant on a type parameter if the parameter used only in out positions
  3. The opposite is true for contra-variant cases: you can declare a class as contra-variant on type parameter if it's used only in in positions
  4. The read-only interface List in kotlin is declared as co-variant, which means List<String> is a subtype of List
  5. The function interface is declared as contra-variant on its first type parameter and co-variant on its second, which makes (Animal)->Int a subtype of (cat)->Number.
  6. Backing Field or field

Type Projection

When using type parameters, there is a distinction between use site and declaration site variance.

Use site variance is the term used when the variance of type parameters is set by the variable itself.

Declaration site variance is the term used when the type or function determines the variance.

It is very convenient to declare a type parameter T as out and avoid trouble with sub-typing on the use site.

Some classes cannot be restricted to only return T's


class Array(val size:Int){
	fun get(index:Int):T{/*....*/}
	fun set(index:Int, value:T){/*....*/}
}

The above class cannot be either covariant or contra-variant in T, imposes certain inflexibilities.


fun copy(from:Array, to:Array){
	assert(from.size == to.size)
	for(in fomr.indices){
		to[i] = from[i]
	}
}

The above function supposed to copy one array elements to other elements.


val ints:Array = arrayOf(1, 2, 3)
var any = Array(3){""}
copy(ints, any)

above code throws error : error : expects(Array, Array)
Array is invariant in T, thus neither of Array and Array is a subtype of the other (ClassCastException)

We want to make sure that copy() doesnot writing to from.


fun copy(from:Array, to:Array){
	//..
}

The above is called Type projection:

  • We said that from is not simply an array, but restricted(projected) one.
  • We can only call those methods that return the type Parameter T
  • Corresponds to java's Array<?extends object>, but in a slightly simpler way

Types of inheritance in kotlin

aaaaaaaaaaaaa
Comment / Suggestion Section
Point our Mistakes and Post Your Suggestions

Recent Addition

new tutorial Selenium Online Training : Our next online training course for Selenium with Java starts from 17th December 2018.

You can attend first 3 classes for free, the total course fee is INR 10,000

The course time would be 8.00 PM(IST) for the first three classes

If you are interested to learn, then you can join the course by sending email to chercher.tech@gmail.com

or Register below


 
Join My Facebook Group
Join Group