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: 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
No comments:
Post a Comment