plugins {
id 'org.jetbrains.kotlin.jvm' version '1.3.41'
id("io.kotest") version "0.2.6"
}
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
testImplementation ("io.kotest:kotest-runner-junit5:${kotestVersion}")
testImplementation("io.kotest:kotest-assertions-core-jvm:$kotestVersion")
testImplementation("io.kotest:kotest-framework-engine-jvm:$kotestVersion")
test {
useJUnitPlatform()
}
- Kotlin is a language that targets the JVM and JavaScript
- Semi Colons are optional in Kotlin
- Multiple classes per file is allowed
- Kotlin is a language that allows you to write concise and expressive code
- var
- This allows you to create mutable variables in kotlin
- This is a read write property in kotlin
var age: Int
var name = "Dilip"
- val
- This allows you to create immutable variables in kotlin
val city = "Minneapolis"
// city = "Detroit" // this is not allowed
- Kotlin is not an immutable language by default
- But the recommendation is to use immutable varibles whenever possible
- As we all know mutation is the root of all evil
- In kotlin everything is a type
- Int
- Double
- Long
- This is different from Java. Because java has primitive types
val doorNo = 1500 // This always maps to integer by default
- You have to be explicit about the type you would like to convert
val doorNoLong: Long = doorNo.toLong()
- Single quotes in Kotlin corresponds to Character
val char = 'A'
- Double quotes in Kotlin corresponds to String
val str = "ABC"
- They can be done using backslash
val escChar = "ABC \n DEF"
- MultiLines in Kotlin can be done using three quotes
val multiLine = "ABC\n" +
"DEF\n" +
"GHI\n"
val multiLine1 =
"""
ABC
DEF
GHI
"""
- This can be applied using the $ symbol
val message = "My name is $name"
- Function invocation in String Interpolation
val message1 = "My name is $name and the name length is ${name.length}"
- Kotlin can automatically determine the types based on the values assigned to the variable
- It helps to cut down the boiler plate code where you don't need to explicitly call out the type the variable is going to hold
var name = "Dilip"
- Forward range can be created using the ..
val range = 1..100
for (i in range) {
println(i)
}
- Reverse range can be created using the downTo operator
val reverseRange = 100 downTo 1
for (i in reverseRange) {
println("Reverse range is : $i")
}
- You can skip values using step keyword
for (i in reverseRange step 10) {
println("Reverse range is : $i")
}
- Using a forloop we can iterate over a collection
val names = listOf("adam", "ben", "chloe")
for (name in names) {
println("Name is : $name")
}
private fun whileLoop() {
var x = 1;
while (x < 5) {
println("Inside while : $x")
x++
}
}
private fun doWhileLoop() {
var x = 0
do {
println("Inside do while : $x")
x++
} while (x < 5)
}
-
You can label a piece of code using text followed by @
- loop@
-
We can use this label to break or continue a loop
-
break loop
fun customLabelBreak() {
loop@ for (i in 1..100) {
for (j in 1..100) {
println("j is $j")
if (j == 10) break@loop
}
}
}
- break innerloop
fun customLabelContinue() {
loop@ for (i in 1..100) {
println()
println("i is $i")
innerLoop@ for (j in 1..100) {
print("j is $j")
//if (j==10) continue@loop
if (j == 10) break@innerLoop
}
}
}
- continue @loop
fun customLabelContinue() {
loop@ for (i in 1..100) {
println()
println("i is $i")
innerLoop@ for (j in 1..100) {
print("j is $j")
if (j == 10) continue@loop
}
}
}
- if is an expression in kotlin
- This means that the last line in the if statement is assigned as a result to the variable
- Anytime using if as an expression, it is mandatory to have an else block
- A quick thing to note is that different branches can execute different result
private fun ifExpression(name: String) {
val resultName = if (name.length == 4) {
println("Length is 4 and the name is $name")
name.toUpperCase()
} else {
println("Length is not 4 and the name is $name")
name.toUpperCase()
}
println("resultName : $resultName")
}
- when block is a nice replacement for multiple if else statements
- This syntax is compact
- This is kind of an equivalent to switch-case statement in Java
- You are allowed to have multiple conditions in the same check
fun whenBlock(name: String) {
/* when (name) {
"Alex" -> println("Name is Alex")
"Ben" -> println("Name is Ben")
}*/
when (name.length) {
4, 6 -> println("Length of the name is 4")
5 -> println("Length of the name is 5")
}
}
- when can also be an expression
- else block is mandatory when using when as an expression
fun whenBlockExpression(name: String) {
val whenResult = when (name.length) {
4 -> {
println("Length of the name is 4")
name
}
5 -> {
println("Length of the name is 5")
name
}
else -> {
println("Length of the name is not 4 or 5")
""
}
}
println(whenResult)
}
- This concept is pretty much similar to how java works
- This concept can be used if you have conflicts with different classes
import com.explorekotlin._1basics.commons.strToLength as strLength
println("Length of the String is ${strLength(name)}")
- Any is equivalent to an Object in Java
- Unit is equivalent to void in Java
- These functions belong to a source file but not part of a class
- In java world there is no such thing like this available.
- You may notice in projects which might have a class with a bunch of static methods in it
- These are commonly called as Utility classes
- But in Kotlin, you dont need to do that. You dont need a redundant class to hold static functions
- This is equivalent creating properties that does not belong to any class
- When you would like to have a variable that needs to be available throughout the project then you can define and use them
- A function is something in kotlin which can do a unit of work.
- A function that does not return anything is represented as a Unit and this is equivalent to a void in java
fun printSomething(): Unit {
println("printSomething")
}
- A function with a Nothing return type never returns.
fun nothing(): Nothing {
throw RuntimeException("Exception Occurred")
}
fun multiply(x: Int, y: Int): Int {
return x + y
}
- Like variables, the kotlin compiler can infer the return types too.
- You basically inline the method body to the by assigning the method body after the equal operator
- This only works for single line method body.
- Single expression function does not need a method block
fun multiply1(x: Int, y: Int) = x + y
- In the below example, we have z with the default value
- If the value is not passed in then the default value will be used.
fun multiply2(x: Int, y: Int, z: Int = 1): Int {
println("x : $x and y : $y")
return x * y
}
- The caller of the function can invoke the function by calling out the names of the parameters
- This makes the code more expressive
fun multiply2(x: Int, y: Int, z: Int = 1, w: Int): Int {
println("x : $x and y : $y")
return x * y * z
}
- Function invocation for multiply2
multiply2(2, 3, w = 3)
- use vararg as a type for functions that requires indefinite number of parameters
fun printMultipleThings(vararg strings: String) {
printVarargs(*strings)
}
private fun printVarargs(vararg strings: String) {
for (string in strings) {
println("String : $string")
}
}
- Passing vararg as a argument to another function requires you to use a spread operator
- The spread operator is *
- Kotlin is an object oriented language with first class support for functional construct's
- Classes in kotlin are created with the class keyword
- Classes have first class support in Kotlin
- Classes can have properties
- Here we are declaring it as var so that the properties can be set later
- You need to have a default value if we are just declaring this as a property with var type
- There will be compilation issue if we don't initialize this value
- Here we are declaring it as var so that the properties can be set later
- Classes can have properties
class Customer {
var id: Int = 0
var name: String = ""
}
fun main() {
val customer = Customer()
}
- The traditional way of doing it is by pass through the constructor and intiailize the classs properties
class Customer(initId: Int, initName: String) {
var id: Int = initId
var name: String = initName
}
-
But the above looks cumbersome, and a has a lot of boilerplate code
-
Kotlin has a really nice way if writing concise code
-
The below syntax removes a lot of boiler plate code
- The type var or val is necessary to treat this as a property for this class
class Customer(var id: Int, var name: String)
- Using default values give you the behavior of the class with multiple constructors
class Customer(var id: Int, var name: String = "")
val customer = Customer(1, "Dilip")
println("customer name : ${customer.name}")
val customer1 = Customer(1) // Instance without the name
- We can use the init block to achieve the same
class Customer(var id: Int, var name: String = "") {
init {
name = name.toUpperCase()
}
}
- You are allowed to create constructors with construct keyword and these are called secondary constructors
constructor(email : String) : this(0, email = email) {
}
- This constructor needs to invoke the base constructor
- You normally need this when you need to apply some custom logic on the properties of the class
- Below is the custom getter for a property age
val age: Int
get() {
return LocalDate.now().year - yearOfBirth
}
- If you would like to apply some property validation then you can use custom setters
- The one key thing when using custom setters is you can set the value to the propery using the field
- field is a special one which is also known as a backing field value holds the value of that property
- The one key thing when using custom setters is you can set the value to the propery using the field
var phoneNumber: String = ""
set(value) {
if (value.length < 10) {
throw IllegalArgumentException("Phone Number should be 10 characters")
}
field =
value // field is a special one which is also known as a backing field value holds the value of that property
}
- The default visibility modifier in kotlin is public
- public
- Default and anywhere accessible
- private
- Visible only within the file containing declaration
- internal
- Visible anywhere within the module
- Module here refers to the maven module or client module
- Visible anywhere within the module
- Classes
- private - Only available to class members
- protected - Same as private and subclasses
- internal - Any client inside the module
-
There are some Classes that are created in enterprise applications won't have any behavior
- It just holds the data for the other classes to use and apply some business logic on top of those classes
-
By adding the data modifier before the class
- This automatically takes of generating the equals, toString and hashCode functions for you
- Advantages:
- This saves us from the boilerplate code
- Object equality checks is available out of the box
- This also has the copy functions similar to clone which helps us to copy properties from one to another
- Overall the classes are not bulky and concise
data class CustomerData(
val id: Int,
var name: String = "",
val email: String = "",
val yearOfBirth: Int = 0
)
- This is also called enumerated classes
- Enum classes have the enum keyword attached to the class
enum class Priority {
MINOR,
NORMAL,
MAJOR,
CRITICAL
}
val priority = Priority.CRITICAL
println(priority.name)
println(priority.ordinal)
- name
- This provides the value as like its declared in the enum
- ordinal
- This gives you the index of the enum
- Index usually starts with 0
- values()
- This gives you access all the enums thats part of the Enum class
for (p in PriorityEnum.values()) {
println(p.name)
}
- valueOf
- This functions retrieves the enum for the passed in String
val priorityEnum = PriorityEnum.valueOf("CRITICAL")
- If the passed in String does not have a valid Enum then it it will throw a java.lang.IllegalArgumentException
- You can add Custom values to enums using this approach
- You can access the values using the constructor argument value
println(priority.value)
enum class PriorityEnum(val value: Int) {
MINOR(-1),
NORMAL(0),
MAJOR(1),
CRITICAL(2)
}
val priority = PriorityEnum.CRITICAL
println(priority.name)
println(priority.ordinal)
- You can override the toString() method if you would like to return a different value for the Enum
MINOR(-1) {
override fun toString(): String {
return "MINOR PRIORITY"
}
}
- This is the only place you need to add a semicolon in kotlin
enum class PriorityEnum(val value: Int) {
MINOR(-1) {
override fun toString(): String {
return "MINOR PRIORITY"
}
},
NORMAL(0),
MAJOR(1),
CRITICAL(2);
fun abc() {
}
}
- object helps us to create singleton's in Kotlin
- You can access the properties inside the class without the instance of the object
object CommonUtil {
const val appName = "EXPLORE_KOTLIN"
}
- In kotlin, the base class for any class is Any
- In Kotlin, all classes are final by default which means we cannot extend a class
- You need to use the open modifier to allow a class to be extended
- The open modifier is needed in the functions if the subclass is going to override a behavior
open class Person{
open fun validate(){
println("Inside Person validate")
}
}
class Customer : Person() {
override fun validate(){
super.validate()
println("Inside Customer validate")
}
}
- To call the parent class function we can use the super keyword.
class Customer : Person() {
override fun validate(){
super.validate()
println("Inside Customer validate")
}
- super keyword and constructor
- Basically you need to take out the braces in the class creation and use the super keyword along with your constructor() keyword
class Customer : Person {
constructor() : super() {
}
override fun validate(){
super.validate()
println("Inside Customer validate")
}
}
- We need to make sure the Customer class has open modifier in it
class SpecialCustomer : Customer(){
fun validate1() {
super.validate()
}
}
- Let's say that we dont want this class to override to validate() function
- We can add the final keyword to that class
final override fun validate(){
super.validate()
println("Inside Customer validate")
}
- You cannot add a open modifier to data classes
- Inheritance is not supported in data classes
- But you can still extend a regular class from data class
open class Animal(open val name: String)
data class WildAnimal(
override val name: String,
val type: String,
val country: String,
val age: Int
) : Animal(name)
data class DomesticAnimal(
override val name: String,
val type: String,
val country: String,
val age: Int
) : Animal(name)
- They are normally created with the abstract keyword
- You cannot create an instance of this class
- This class can have state
- This class can have member functions
- You can make certain behaviors abstract and mandate the classes that extends it implement it.
abstract class AbstractPerson{
var isAlive : Boolean = true
abstract fun action()
fun alive(): String {
return isAlive.toString()
}
}
class Employee : AbstractPerson(){
override fun action() {
TODO("Not yet implemented")
}
}
- You can only field's of type val
interface CourseRepository {
val isCoursePublished : Boolean
get() = false
fun saveCourse(course: Course): Int {
println("course : $course")
return course.id
}
fun getById(id: Int): Course
}
- You are just allowed to have properties declared in the interface of type val
- There is no way we can alter the state of the property in interfaces
- You are allowed to manage the state of the variable in the subclass that's implementing it.
class SqlCourseRepository : CourseRepository {
override fun getById(id: Int): Course {
TODO("Not yet implemented")
}
override var isCoursePublished: Boolean = false
get() {
return true
}
set(value) {
field = value
}
}
-
We can override member functions in the implementation class
- One key thing to note here is that , we dont need the open modifier in the parent class
-
But you still need the override modifier in the subclass.
override fun saveCourse(course: Course): Int {
println("course in SqlCourseRepository : $course")
return course.id
}
- Lets say you have a class that implements two interfaces
- Both the interface have the same function
- You have to call the default implementation of that class then you need to provide the type and then call the appropriate function
super<A>.doSomething()
interface A {
fun doSomething(){
println("Do Something in A")
}
}
interface B {
fun doSomething(){
println("Do Something in B")
}
}
class AB : A, B {
override fun doSomething() {
super<A>.doSomething()
println("Do something in AB")
}
}
- Abstract classes are allowed to have state but not the Interface
- You can have subclasses that implements multiple Interface but not the abstract class
- If you would like to create generic classes then you would use generic Types
- In this case, we are declaring an interface with a generic Type T
interface Repository<T>{
fun getById(id: Int) : T
fun getAll() : List<T>
}
- The implementation of the Repository is below, which actually takes care of implementing the actual functionality.
class GenericRepositoryImpl<T> : Repository<T> {
override fun getById(id: Int): T {
TODO("Not yet implemented")
}
override fun getAll(): List<T> {
TODO("Not yet implemented")
}
}
- This approach will only makes sense if we have different types returned by different functions
interface Repo {
fun <T> getById() : T
fun <R> getAll() : List<R>
}
class RepoImpl : Repo{
override fun <T> getById(): T {
TODO("Not yet implemented")
}
override fun <R> getAll(): List<R> {
TODO("Not yet implemented")
}
}
- One of the important concept thats used in many programming languages is the support null references
- Null references are troublesome and can introduce the most popular exception which is the Null-Pointer Exception in runtime.
- Kotlin by default is a null safety programming language.
- The reason why nulls are needed in the app:
- Sometimes you might have to declare the value as null
- If nulls are not supported in Java then it might cause issues with interoperability in Java
- A nullable type in kotlin is declared using the ?
String?
- Trying to execute a function on a null reference without the elvis or the null-assertion operator will cause a compilation error in kotlin
- This is much better than the if null check that we perform traditionally and it cuts down a lot of boiler plate code
var name1 : String? = null
//println(name1?.length)
println(name1!!.length)
fun upperCase(name : String?) : String?{
return name?.uppercase()
}
- Kotlin does have the support for typecasting as like Java
- We can check the type of cast using the is operator
- One of the important thing about this is Kotlin can apply smart cast once the object resolves to a particulat type
fun checkTypeOfPerson(person: Person) {
if(person is Employee){
person.vacationDays(10)
}
}
- Type casting can be done usint the as operator
val castValue = count as String
- We can use the elvis operator to apply the casting safely.
- Without the ?, if the casting fails then it will throw an exception
val castValue1 = count as? String
- Pair and Triples are handy when you have a function that's going to return multiple values as a return value.
val namePair = nameAndLength("Dilip")
println("${namePair.first} and ${namePair.second}")
val nameTriple = nameAndLengthAndAge("Dilip", 33)
println("${nameTriple.first} and ${nameTriple.second} and the age is ${nameTriple.third}")
- This feature in kotlin allows us to write more expressive code.
- Here we can provide proper names to the values that are returned from the function
- This is much better than calling first and second in the Pair type
val (name, length) = namePair
println("$name and $length")
- We can also deconstruct regular objects
val customer = CustomerData(1, "Dilip", yearOfBirth = 1987)
val (id, name2,email,yob ) = customer
println("$id and $name2 and $yob" )
- Object destructuring can be applied on a collection too.
- We can apply object de-structuring on a collection using the below approach
val pairsList = listOf(nameAndLength("Dilip"), nameAndLength("Scooby"))
for ((name, length) in pairsList){
println("$name and $length")
}
- Throwable is the base class for exceptions and errors in Kotlin
- Exceptions can be handled using the try/catch block in kotlin
- You can also use the finally block to take some actions before the call returns
try {
checkIsNumber("A")
} catch (ex: NotANumberException) {
println("NotANumberException observed in ${ex.message}")
} catch (ex: Exception) {
println("Exception is $ex")
}finally {
println("Inside Finally Block")
}
- The last statement in each branch is the value that gets assigned to the result
- In the event of an exception here the values of the result will be Unit because we are just printing the exception and moving on
- The code in the finally block will not be assigned as a result of this expression
val result = try {
checkIsNumber("A")
} catch (ex: NotANumberException) {
println("NotANumberException observed in ${ex.message}")
} catch (ex: Exception) {
println("Exception is $ex")
}finally {
println("Inside Finally Block")
}
- Constants can be declaring in multiple ways
- Define the constant values there
object Constants {
const val courseName = "Kotlin Programming"
const val courseName1 = "Kotlin Programming"
}
val courseName1 = "Kotlin Programming"
const val courseName = "Kotlin Programming"
- Kotlin using Reflection
- Scope Functions are fundamentally there to make your code more concise and readable
- The object on which you are applying these scope functions are called Context Objects
- There are fundamentally five of them
- let
- returns the lambda result
- apply
- returns the context object
- also
- returns the context object
- with
- returns the lambda result
- run
- returns the lambda result
- let
- This function is more or less a side effect, and it does not change the original object
- A Higher Order Function is a type of Function which has two meanings:
- Your own function that takes lambda as arguments
- Return the function as a return type
- The Function Type Syntax is:
( x ,y ) -> Unit
[parameters] [return type]
val sum = { x: Int, y: Int -> x + y }
val action = { println(42) }
- Below is an Higher Order Function which takes an argument and returns a result
fun operation(x : Int, y :Int , op : (Int, Int)-> Int): Int {
return op(x, y)
}
fun sum(x: Int,y: Int) = x +y
### Invoking the Higher Order Function
- Invoking the Higher Order Function using Method Reference
println(operation(1, 2, ::sum))
- Use the function argument and followed by that add the paranthesis
- Add the necessary arguments to it
fun operation(x : Int, y :Int , op : (Int, Int)-> Int): Int {
return op(x, y)
}
- Lambda syntax allow you to create a behavior without having to create a function
{ x, y -> x + y })
(parameters) -> body
- Invoking the Higher Order Function using Lambda
- In this example, we dont need to explicitly call out the types as types are already defined in the actual funciton
println(operation(1, 2) { x, y -> x + y })
- In this case, we need to explicitly call out the types
val sumLamda = {x: Int,y: Int -> x-y}
- Kotlin allows the convention of not having to pass it as a single argument
fun unaryOperation(x: Int, op: (Int) -> Int) {
val result = op(x)
println("result : $result")
}
unaryOperation(1) { x -> x * x }
transaction("Dilip"){
val result = it.uppercase()
println(result)
println("MultiLine Lamda")
}
- You can create a function without the name
fun anonymousFunc(){
unaryOperation(3, fun(x : Int) : Int {
return x * x
})
}
- The concept of accessing a local variable inside lambda is a closure
fun outsideFun(){
val number = 10
unaryOperation(10) {
println(number+1)
val number1 = number+1
it * number1
}
}
- Kotlin is a language that works really well with Java classes
- Let's say you have a java project and you want to introduce kotlin then it's totally possible
- The advantage here is that you don't have to completely put the effort to change your existing code
- All you need in this case is the kotlin compiler added to your class path
- The advantage here is that you don't have to completely put the effort to change your existing code
- All the classes that's tied to Java should reside in src/main/Java directory
- Kotlin can interact with Java class just like interacting with any other class
- But one thing to call out is that, we will need to use the kotlin conventions to interact with Java class
- In here all the variables are accessed using the kotlin conventions
- Even though there is a setter/getter method, we still access the field using kotlin convention
val customer = CustomerJava()
customer.email = "[email protected]"
customer.name = "Dilip"
customer.printDetails()
- You can implement an interface without any issues as like you implement the interface in kotlin
- The Return type of the functions can be null
class CustomerRepositoryImpl : CustomerRepository? {
override fun findById(id: Int?): CustomerJava {
TODO("Not yet implemented")
}
override fun findCustomers(): MutableList<CustomerJava>? {
TODO("Not yet implemented")
}
}
- In Java , null is a valid return type for any function
- Java does not have a concept of a nullable or non-nullable type
- So, the kotlin code that interacts with Java code needs to handle it in their end.
-
A Java class that returns a String will have the return type as String!
- The exclamation in the return types infers to a platform type, in this case its the JVM
- There is no equivalent to this in Kotlin
-
Example of a platform type return type
customer.nonNull()
- We can change the behavior that can add specific things to the language
- This makes sure that the return type matches to what kotlin expects
public @NotNull String nonNull(){
return "I am not null";
}
- When interacting with kotlin classes using Java you need to use the Java Conventions
- Fields that are non-nullable types in kotlin expect the java code to pass a valid value
- Passing null will through Runtime Exceptions
Customer returningCustomer = new Customer(1, "dilip",
"[email protected]", 1987);
returningCustomer.setName("dilp1");
- You can access and modify the properties using the setter method in kotlin
- Even though kotlin classes does not have one
- The below declaration allows Java class to access that property as a field
@JvmField
var property = "Value"
- Java does not have the default value concept
- If you would like to have the java code compile for a kotlin function then we need to use @JvmOverloads annotation
- This takes care of generating different overloaded versions of these functions
@JvmOverloads
fun printString(str: String = "default"){
println("str : $str ")
}
- Changing the function name
@JvmName("changedName")
fun changeName(){
println("changeName")
}
customer.changedName();
- Kotlin does not have a concept of checked exceptions
@Throws(IOException::class)
fun readFile(fileName: String){
if (fileName == ""){
throw IOException("File Name is empty")
}
}
- The regular syntax is to use the FileName and invoke the class
KotlinTopLevelFunctionsKt.prefix("abc", "def");
- If the function name gets changes then this will introduce breaking change in the code.
- You can annotate the class like below.
- Make sure the JvmName is given before the package name declaration
@file:JvmName(name="CommonUtilities")
package com.explorekotlin._10interoperability
- Interacting with a regular field
- Regular variables can be accessed using the getter function, because thats the convention that java uses to access the fields
val name = "KOTLIN_FILE"
System.out.println(CommonUtilities.getName());
- Interacting with a constant field
const val NAME_CONSTANT = "KOTLIN_FILE"
System.out.println(CommonUtilities.NAME_CONSTANT);
- You can only access an extension function by their namne of the class and then pass the instance to it.
ReturningCustomerKt.extenstion(returningCustomer);
- Kotlin does not have collections of their own.
- But they provide interfaces or helper classes to deal with Collections in Kotlin
- Mutable and Immutable Collections
- Immutable List can be created like below
val names = listOf("adam", "ben", "chloe")
println(names)
val emptyList = emptyList<String>()
println(emptyList)
- Mutable List can be created using below
val namesMutable = mutableListOf("adam", "ben", "chloe")
namesMutable.add("anna")
println(namesMutable)
- hashMapOf
- This is used to create immutable map
val hashMap = hashMapOf(Pair("dilip", 33),Pair("scooby", 4),Pair("yaash", 2) )
println(hashMap)
val hashMap1 = hashMapOf("dilip" to 33 ,"scooby" to 4,"yaash" to 2)
println(hashMap1)
- mutableMapOf
- This is used to create a mutable map
val mutableMap = mutableMapOf("dilip" to 33 ,"scooby" to 4,"yaash" to 2)
mutableMap.put("abc", 100)
println(mutableMap)
val set = setOf("adam", "ben", "chloe")
println(set)
val mutableSet = mutableSetOf("adam", "ben", "chloe")
println(mutableSet)
- Cover map, filter, flatMap
- All the collection operations that we have seen so far are eager evaluation
- This means all the elements gets evaluated
- This is probably ok if the collection size is 10, 100, 1000
- But this is not ok for collections that are of size 10000
- The below code is an example of eager evaluation
- In the below example, the output gets printed only after the complete evaluation of the collection
- This will take a lot of time to complete the computation
private fun eagerEvaluation() {
val intRange = 1..100000000000
val output = intRange.filter { it < 40 }
println(output)
}
- In this example, we use a sequence, but the elements in the collection gets processed one by one.
- We need to call a terminal operator like for each to view the result.
private fun lazyEvaluation() {
val intRange = 1..100000000000
val output = intRange.asSequence()
.filter { it < 40 }
.map { it }
.forEach {
println(it)
}
println(output)
}
- This allows us to introspect the code and change the behavior of the code at runtime
- Behavior of the code can be modified on properties, functions and more
- You can achieve this using Reflection
- You can achieve this by using two method
- Using the Java Reflection API
- Using the Kotlin Reflection API
- You can achieve this by using two method
- The .javaclass in this example allows you start leveraging the java reflection api
fun introspected(obj: Any){
println("className using Reflection ${obj.javaClass.simpleName}")
obj.javaClass.fields.forEach {
println("Field name : ${it.name} and type is ${it.type}")
}
println("Functions:\n")
obj.javaClass.methods.forEach {
println("Method name is : ${it.name}")
}
}
- The KClass is the reflection class for Kotlin
classInfo.memberProperties
.forEach {
println("Name ${it.name} of type ${it.returnType}")
}
val constructor = ::Transaction
val transaction = constructor.call(1, 2000.0, "20000")
- Creating an instance using the callBy function of the constructor reference
val transaction1 = constructor.callBy(mapOf(constructor.parameters[0] to 2,constructor.parameters[1] to 2000.0, constructor.parameters[2] to "20000"))
- Annotations in Kotlin
@Table(name = "Contact_Table")
data class Contact(val id: Int, @Field(name="column_name")val name: String, val email: String)
@Target(AnnotationTarget.FIELD)
@MustBeDocumented
@Repeatable
@Retention(AnnotationRetention.RUNTIME)
annotation class Field(val name: String)
-
@MustBeDocumented
- This states that this information must be present in the generated source code
-
@Target(AnnotationTarget.FIELD)
- This mandates that the annotation should be used against the field
-
@Retention(AnnotationRetention.RUNTIME)
- This annotation information is retained in runtime
-
Using the annotation in the actual class
@Table(name = "Contact_Table")
data class Contact(val id: Int, @Field(name="column_name")val name: String, val email: String)
val annotation = Contact::class.annotations.find { it.annotationClass.simpleName == "Table" }
println(annotation)
- inline is a keyword which takes care of inlining the function in to the calling code itself
fun operation(op: () -> Unit) {
println("Before Operation")
op()
println("after Operation")
}
inline fun inlineOperation(op: () -> Unit) {
println("Before inlineOperation")
op()
println("after inlineOperation")
}
fun main() {
operation { println("I am the operation") }
inlineOperation { println("I am the inoperation") }
}
- In the above inlineOperation call actually the lamda thats passed in actually embeds the code in to the main
function
- You can check the bytecode -> Tools -> Kotlin -> Show Kotlin ByteCode
- It does display a line that does not even exist because inline function inject the code in to the calling function
actually
- But when the exception is printed in the console it does give you the option to navigate in to the lamda body