Thursday, 15 February 2018

Kotlin: Generics

Just like in Java, kotlin supports type parameters.

Let me explain with an example,

class Employee<T>(var id: T, var name: String) {

}

Above snippet creates a generic Employee class, where id is generic type.

Create Employee object, where id is of type Int
var emp1: Employee<Int> = Employee(1, "Krishna")

Create Employee object, where id is of type String
var emp2: Employee<String> = Employee("I345123", "Chamu")

Find the below working application.

Test.kt
com.sample.test

class Employee<T>(var id: T, var name: String) {

}

private fun <T> printEmployee(emp: Employee<T>) {
 println("id : ${emp.id}")
 println("name : ${emp.name}")
}

fun main(args: Array<String>) {
 var emp1: Employee<Int> = Employee(1, "Krishna")
 var emp2: Employee<String> = Employee("I345123", "Chamu")

 printEmployee(emp1)
 printEmployee(emp2)

}

Output
id : 1
name : Krishna
id : I345123
name : Chamu

Can I declare multiple type parameters to a class?
Yes, you can declare multiple type parameters to a class.

Ex
class MapEntry<T, U>(var key: T, var value : U) {

}

Is Kotlin infer type arguments?
Most of the times (not always), kotlin can infer the type arguments based on the definition of the object.

For ex
var emp1 = Employee(1, "Krishna")
From the above statement, kotlin can infer that the Employee is of type Employee<Int>

var emp2 = Employee("I345123", "Chamu")
From the above statement, kotlin can infer that the Employee is of type Employee<String>

But there are some situations, where kotlin can’t infer the type arguments.

For example, kotlin provides listOf function to define list of values.

var primes = listOf(2, 3, 5, 7, 11)

From the above statement kotlin can infer that we are trying to create a list of Integers.

Let’s try to create empty list
var primes = listOf()


Test.kt
package com.sample.test

fun main(args: Array<String>) {
 var primes = listOf()
}

When you try to compile above program, you will end up in below error.

ERROR: Type inference failed: Not enough information to infer parameter T in inline fun <T> listOf(): List<T>
Please specify it explicitly.
 (4, 15)

It is because, you are not passing any arguments to listOf function, so kotlin can’t infer anything from empty list declaration.

To resolve above problem, you should define the list ‘primes’ like below.

var primes = listOf<String>()

Benefits of Generics
1. Stronger type checks at compile time
Types are checked at compile time, so most of the type related bugs detected at compile time.


Test.kt
package com.sample.test

class Employee<T>(var id: T, var name: String) {

}

fun main(args: Array<String>) {
 var emp1: Employee<String> = Employee(1, "Krishna")

}

Notify above snippet, I defined emp1 as of type Employee<String>, but I passed employee id as 1 (1 is integer, not string). When you try to compile above program, kotlin compiler throw below error.

ERROR: Type inference failed. Expected type mismatch: inferred type is Employee<Int> but Employee<String> was expected (9, 31)

2. Enable Programmers to implement Generic Data Structures.
I will explain about this in collections sections.


Is Kotlin had wildcard characters like java?
No, but kotlin has two other things declaration-site variance and type projections.

Kotlin: Generics: Declaration-site variance : out
Kotlin do not have wild card concept like Java, but it supports the same using Declaration-site variance and type projections

Why java support wild cards?
? is called the wild card in Java. It represents unknown type. There are some situations, where programmer certainly don't know, the kind of data that he is going to operate, in those situation wild cards are helpful.

First let us see, why generic types are invariant in Java.

Why generic types are invariant in Java?
Generic types in Java are invariant. That means, if T2 is a subtype of T1, List<T2> is not a sub type of List<T1>.


Ex
Integer is a subtype of Number.
But Stack<Integer> is not a sub type of Stack<Number>.

Why java makes generics as invariant?
Let me explain with an example.

                 /* Define a character list */
                 List<Character> chars = new ArrayList<Character>();

                 /* Java do not allow this */
                 List<Object> objs = chars;

                 /* Adding double to character list */
                 objs.add(1.23);

                 /* ClassCastException: Cannot cast double to Character */
                 Character s = chars.get(0);

As you notify above snippet. I defined a character list ‘chars’ and cast it to list of objects ‘objs’. I added a double to ‘objs’. Last statement cast the double to character, which is not possible. To prohibit these kind of things and ensure run time safety of the application, java generics are invariant.

But generic type invariant has its own implications. For example, you would like to calculate sum of numbers (number can be integer, byte, double, float etc.,). Method signatures looks like below.


         private static Double sumOfintegers(List<Integer> listOfIntegers) {

         }

         private static Double sumOfFloats(List<Float> listOfDoubles) {

         }

        
         private static Double sumOfDoubles(List<Double> listOfDoubles) {

         }

If there are ‘n’ different Number types, you will end up in writing ‘n’ different functions to calculate sum of the numbers. To resolve this kind of problem, wild card character is introduced.

         private static Double sumOfNumbers(List<? extends Number> listOfNumbers) {

                 Double sum = 0.0;

                 for (Number num : listOfNumbers) {
                          sum = sum + num.doubleValue();
                 }

                 return sum;

         }

The wildcard type operator ‘? extends Number’ indicates that this method accepts a collection of objects of number or some subtype of Numbers, not just E itself.  We can use ‘sumOfNumbers’ function to calculate the sum of different number types (Integer, Float, Double etc.,)

Sum of integers can be calculated like below.
List<Integer> intList = Arrays.asList(2, 3, 5, 7);
Double sumOfIntegers = sumOfNumbers(intList);

Sum of Doubles can be calculated like below.
List<Double> doubleList = Arrays.asList(2.0, 3.0, 5.0, 7.0);
Double sumOfDoubles = sumOfNumbers(doubleList);

Sum of Floats can be calculated like below
List<Float> floatList = Arrays.asList(2.0f, 3.0f, 5.0f, 7.0f);
Double sumOfFloats = sumOfNumbers(floatList);

Find the below working application.

Test.java
import java.util.Arrays;
import java.util.List;

public class Test {

 private static Double sumOfNumbers(List<? extends Number> listOfNumbers) {

  Double sum = 0.0;

  for (Number num : listOfNumbers) {
   sum = sum + num.doubleValue();
  }

  return sum;

 }

 public static void main(String args[]) throws CloneNotSupportedException {
  List<Integer> intList = Arrays.asList(2, 3, 5, 7);
  List<Double> doubleList = Arrays.asList(2.0, 3.0, 5.0, 7.0);
  List<Float> floatList = Arrays.asList(2.0f, 3.0f, 5.0f, 7.0f);

  Double sumOfIntegers = sumOfNumbers(intList);
  Double sumOfDoubles = sumOfNumbers(doubleList);
  Double sumOfFloats = sumOfNumbers(floatList);

  System.out.println("sumOfIntegers : " + sumOfIntegers);
  System.out.println("sumOfDoubles : " + sumOfDoubles);
  System.out.println("sumOfFloats : " + sumOfFloats);

 }
}


Output
sumOfIntegers : 17.0
sumOfDoubles : 17.0
sumOfFloats : 17.0

Let’s see a scenario, where Java generics is not intelligent enough to take decision.

For example, I had an interface DemoObj. It has single method getObj() that returns the object itself.


DemoObj.java
package com.sample.demo;

public interface DemoObj<T> {
 T getObj();
}

‘DemoObjImpl’ implements DemoObj interface like below.


DemoObjImpl.java
package com.sample.demo;

public class DemoObjImpl<T> implements DemoObj<T> {

 private T obj;

 public DemoObjImpl(T obj) {
  this.obj = obj;
 }

 @Override
 public T getObj() {
  return obj;
 }

}


Since instance of DemoObjImpl is just returning the generic object, it would be perfectly safe to store a reference to an instance of DemoObj<String> in a variable of type DemoObj<Object>. But Java doesn’t know this and prohibit the application.

DemoObj<String> demoStrObj = new DemoObjImpl("Hello World");
DemoObj<Object> demoObj = demoStrObj; // Compiler throw error


Test.java
package com.sample.demo;

public class Test {

 public static void main(String args[]) throws CloneNotSupportedException {
  DemoObj<String> demoStrObj = new DemoObjImpl("Hello World");
  DemoObj<Object> demoObj = demoStrObj;
 }
}

When you try to run above application, Java compier throw below error.

Exception in thread "main" java.lang.Error: Unresolved compilation problem:
         Type mismatch: cannot convert from DemoObj<String> to DemoObj<Object>

         at com.sample.demo.Test.main(Test.java:7)
How to resolve above problem in Kotlin?
Kotlin solves this problem using ‘declaration-site variance'. when a type parameter T of a class C is annotated with ‘out’ keyword, C<Base> can safely be a supertype of C<Derived>.

public interface DemoObj<out T> {
         fun getObj(): T
}

fun getDemoObj(demoStrObj: DemoObj<String>): DemoObj<Any> {
         var demoObj: DemoObj<Any> = demoStrObj
         return demoObj;
}

Find the below working application.


Test.kt
public interface DemoObj<out T> {
 fun getObj(): T
}

public class DemoObjImpl<out T> : DemoObj<T> {

 private var obj: T

 public constructor(obj: T) {
  this.obj = obj;
 }

 override public fun getObj(): T {
  return obj;
 }

}

fun getDemoObj(demoStrObj: DemoObj<String>): DemoObj<Any> {
 var demoObj: DemoObj<Any> = demoStrObj
 return demoObj;
}

fun main(args: Array<String>) {
 var demoObjStr: DemoObj<String> = DemoObjImpl("Hello World")

 var demoObj: DemoObj<Any> = getDemoObj(demoObjStr)

 println("Value of demoObj : ${demoObj.getObj()}")

}

Output
Value of demoObj : Hello World


Previous                                                 Next                                                 Home

No comments:

Post a Comment