Ques 1. What do you understand about function extension in the context of Kotlin? Explain.
In Kotlin, we can add or delete method functionality using extensions, even without inheriting or altering them. Extensions are statistically resolved. It provides a callable function that may be invoked with a dot operation, rather than altering the existing class.
Function Extension - Kotlin allows users to specify a method outside of the main class via function extension. We\'ll see how the extension is implemented at the functional level in the following example:
// KOTLIN
class Sample {
var str : String = "null"
fun printStr() {
print(str)
}
}
fun main(args: Array) {
var a = Sample()
a.str = "Without"
var b = Sample()
b.str = "Book"
var c = Sample()
c.str = a.add(b)
c.printStr()
}
// function extension
fun Sample.add(a : Sample):String{
var temp = Sample()
temp.str = this.str + \" \" +a.str return temp.str
}
Output:-
Without Book
Ques 2. What do you understand about Companion Object in the context of Kotlin?
In some languages, such as Java, the static keyword is used to declare class members and utilise them without creating an object, i.e. by simply calling them by their class name. In Kotlin, there is nothing called the “static” keyword. So, if we want to achieve the functionality of static member functions, we use the companion objects. This is also referred to as Object Extension.
We must use the companion keyword in front of the object definition to construct a companion object.
// Syntax in KOTLIN
class CompanionClass {
companion object CompanionObjectName {
// code
}
}
val obj = CompanionClass.CompanionObjectName
We can also remove the CompanionObject name and replace it with the term companion, resulting in the companion object\'s default name being Companion, as shown below:
// KOTLIN
class CompanionClass {
companion object {
// code
}
}
val obj = CompanionClass.Companion
All the required static member functions and member variables can be kept inside the companion object created. For example,
class Sample {
companion object Test {
var a: Int = 1
fun testFunction() = println("Companion Object’s Member function called.")
}
}
fun main(args: Array) {
println(Sample.a)
Sample.testFunction()
}
Output:-
1Companion Object’s Member function called.
Ques 3. What are the advantages of Kotlin over Java?
Following are the advantages of Kotlin over Java:
- Data class: In Java, you must create getters and setters for each object, as well as properly write hashCode (or allow the IDE to build it for you, which you must do every time you update the class), toString, and equals. Alternatively, you could utilize lombok, but that has its own set of issues. In Kotlin, data classes take care of everything.
- Patterns of getter and setter: In Java, for each variable, you use it for, rewrite the getter and setter methods. You don't have to write getter and setter in kotlin, and if you must, custom getter and setter take a lot less typing. There are additional delegates for identical getters and setters.
- Extension Functions: In Java, there is no support for extension functions. Kotlin on the other hand provides support for extension functions which makes the code more clear and cleaner.
- Support for one common codebase: You may extract one common codebase that will target all of them at the same time using the Kotlin Multi-Platform framework.
- Support for Null Safety: Kotlin has built-in null safety support, which is a lifesaver, especially on Android, which is full of old Java-style APIs.
- Less prone to errors: There is less space for error because it is more concise and expressive than Java.
Ques 4. Which one is better to use - val mutableList or var immutableList in the context of Kotlin?
The program's design clarity is improved by using mutable and immutable lists. This is done to have the developer think about and clarify the collection's purpose.
We use a mutable list if the collection will alter as part of the design. On the other hand, we use an immutable list if the model is only meant to be viewed.
Val and var serve a distinct purpose than immutable and mutable lists. The val and var keywords specify how a variable's value/reference should be handled. We use var when the value or reference of a variable can be altered at any moment. On the other hand, we use val when a variable's value/reference can only be assigned once and cannot be modified later in the execution.
Immutable lists are frequently preferred for a variety of reasons:
- They promote functional programming, in which state is passed on to the next function, which constructs a new state based on it, rather than being altered. This is evident in Kotlin collection methods like map, filter, reduce, and so forth.
- It's often easier to understand and debug software that doesn't have any side effects (you can be sure that the value of an object will always be the one at its definition).
- Because no write access is required in multi-threaded systems, immutable resources cannot induce race conditions.
However, there are some disadvantages of using immutable lists as well. They are as follows :
- Copying large collections simply to add/remove a single piece is very expensive.
- When you need to alter single fields frequently, immutability can make the code more difficult. Data classes in Kotlin provide a built-in copy() method that allows you to clone an instance while changing only part of the fields' values.
Ques 5. What do you understand about lateinit in Kotlin? When would you consider using it?
lateinit is an abbreviation for late initiation. If you don\'t want to initialize a variable in the constructor and instead want to do it later, and you can guarantee the initialization before using it, use the lateinit keyword to declare that variable. It won\'t start allocating memory until it\'s been initialized. Lateinit cannot be used for primitive type attributes like Int, Long, and so on. Because the lateinit variable will be initialized later, you cannot use val. When a lateinit property is accessed before it has been initialized, a special exception is thrown that explicitly identifies the property and the fact that it hasn\'t been initialized.
For example,
// KOTLIN
lateinit var test: Stringfun testFunction() {
test = "Interview"
println("The length of string is "+test.length)
test = "Book"
}
When the testFunction is called, we get the following output:-
9
There are a few scenarios in which this is particularly useful, for example:
- Variables that are initialized in lifecycle methods in Android;
- Using Dagger for DI: injected class variables are initialized outside of the constructor and independently;
- Setup for unit tests: in a @Before - annotated function, test environment variables are initialized;
- Annotations in Spring Boot (for example, @Autowired).
Ques 6. Explain lazy initialization in the context of Kotlin.
There are some classes whose object initialization is so time-consuming that it causes the entire class creation process to be delayed. Lazy initialisation helps in such problems. When we declare an object using lazy initialisation, the object is initialised only once when the object is used. If the object is not used throughout, the object is not initialised. This makes the code more efficient and faster.
For example, let us imagine you have a SlowClass class and you require an object of that SlowClass in a different class called FastClass:
// KOTLIN
class FastClass {
private val slowObject: SlowClass = SlowClass()
}
We are generating a large object here, which will cause the development of the FastClass to be slow or delayed. There may be times where the SlowClass object isn\\\'t required. As a result, the lazy keyword can assist you in this situation:
class FastClass {
private val slowObject: SlowClass by lazy {
SlowClass()
}
}
For example,
// KOTLIN
class FastClass {
private val slowObject: SlowClass by lazy {
println("Slow Object initialised")
SlowClass()
}
fun access() {
println(slowObject)
}
}
fun main(args: Array) {
val fastClass = FastClass()
println("FastClass initialised")
fastClass.access()
fastClass.access()
}
Output:-
FastClass initialised
Slow Object initialised
SlowClass@2b12fkk7
SlowClass@2b12fkk7
Explanation:- In the above code, we have instantiated an object of the SlowClass inside the class structure of the FastClass using lazy initialisation. The object of the SlowClass is generated only when it is accessed in the above code, that is, when we call the access() method of the FastClass object and the same object is present throughout the main() method.
Ques 7. Differentiate between lateinit and lazy initialisation. Explain the cases when you should use lateinit and when you should use lazy initialisation.
lateinit | lazy initialisation |
---|
The main purpose is to delay the initialisation to a later point in time. | The main purpose is to initialise an object only when it is used at a later point in time. Also, a single copy of the object is maintained throughout the program. |
It\'s possible to initialise the object from anywhere in the program. | Only the initializer lambda can be used to initialise it. |
Multiple initializations are possible in this case. | Only a single initialisation is possible in this case. |
It\'s not thread-safe. In a multi-threaded system, it is up to the user to correctly initialise. | Thread-safety is enabled by default, ensuring that the initializer is only called once. |
It works only with var. | It works only with val. |
The isInitialized method is added to verify if the value has previously been initialised. | It is impossible to uninitialize a property. |
Properties of primitive types are not allowed | Allowable on primitive type properties. |
Ques 8. What do you understand about coroutines in the context of Kotlin?
Unlike many other languages with equivalent capabilities, async and await are neither keywords nor part of Kotlin's standard library. JetBrains' kotlinx.coroutines library is a comprehensive library for coroutines. It includes a number of high-level coroutine-enabled primitives, such as launch and async. Kotlin Coroutines provide an API for writing asynchronous code in a sequential manner.
Coroutines are similar to thin threads. Coroutines are lightweight since they don't allocate new threads when they're created. Instead, they employ pre-defined thread pools as well as intelligent scheduling. The process of deciding which piece of work you will do next is known as scheduling. Coroutines can also be paused and resumed in the middle of their execution. This means you can have a long-term project that you can work on incrementally. You can pause it as many times as you want and continue it whenever you're ready.
Ques 9. Explain scope functions in the context of Kotlin. What are the different types of Scope functions available in Kotlin?
The Kotlin standard library includes numerous functions that aid in the execution of a block of code within the context of an object. When you use a lambda expression to call these functions on an object, temporary scope is created. These functions are referred to as Scope functions. The object of these functions can be accessed without knowing its name. Scope functions make code more clear, legible, and succinct, which are key qualities of the Kotlin programming language.
Following are the different types of Scope functions available in Kotlin:-
- let:-
Context object: it
Return value: lambda result
The let function is frequently used for null safety calls. For null safety, use the safe call operator(?.) with ‘let'. It only runs the block with a non-null value. - apply:-
Context object: this
Return value: context object
“Apply these to the object,” as the name suggests. It can be used to operate on receiver object members, primarily to initialise them. - with:-
Context object: this
Return value: lambda result
When calling functions on context objects without supplying the lambda result, ‘with' is recommended. - run:-
Context object: this
Return value: lambda result
The ‘run' function is a combination of the ‘let' and ‘with' functions. When the object lambda involves both initialization and computation of the return value, this is the method to use. We can use run to make null safety calls as well as other calculations. - also:-
Context object: it
Return value: context object
It's used when we need to do additional operations after the object members have been initialised.
Ques 10. Explain suspend function in the context of Kotlin.
A function that may be started, halted, then resumed is known as a suspend function. One of the most important things to remember about the suspend functions is that they can only be invoked from another suspend function or from a coroutine. Suspending functions are merely standard Kotlin functions with the suspend modifier added, indicating that they can suspend coroutine execution without blocking the current thread. This means that the code you're looking at may pause execution when it calls a suspending function and restart execution at a later time. However, it makes no mention of what will happen to the present thread in the meantime.
Suspending functions can call any other ordinary functions, but another suspending function is required to suspend the execution. Because a suspending function cannot be called from a regular function, numerous coroutine builders are supplied, allowing you to call a suspending function from a non-suspending scope like launch, async, or runBlocking.
delay() function is an example of suspend function.
Ques 11. What do you understand about sealed classes in Kotlin?
Kotlin introduces a crucial new form of class that isn't seen in Java. These are referred to as "sealed classes." Sealed classes, as the name implies, adhere to constrained or bounded class hierarchies. A sealed class is one that has a set of subclasses. When it is known ahead of time that a type will conform to one of the subclass types, it is employed. Type safety (that is, the compiler will validate types during compilation and throw an exception if a wrong type has been assigned to a variable) is ensured through sealed classes, which limit the types that can be matched at compile time rather than runtime.
Ques 12. What do you understand about the backing field in Kotlin?
A backing field is an auto-generated field for any property that may only be used inside accessors (getter or setter) and will only be present if it utilizes the default implementation of at least one of the accessors, or if a custom accessor refers to it through the field identifier. This backing field is used to avoid an accessor's recursive call, which would result in a StackOverflowError.
Fields are not allowed in Kotlin classes. When employing custom accessors, however, it is occasionally required to have a backing field. Kotlin includes an automatic backing field for these purposes, which may be accessed by the field identifier.
For example,
var marks: Int = someValue
get() = field
set(value) {
field = value
}
Explanation:- Here the field identifier acts as a reference to the property “marks” value in the get() and set() method. So, whenever we call the get(), we get the field’s value returned. Similarly, whenever we call the set(), we set the “marks” property value to “value”.
Ques 13. Differentiate between launch / join and async / await in Kotlin.
launch / join:-
The launch command is used to start and stop a coroutine. It's as though a new thread has been started. If the code inside the launch throws an exception, it's considered as an uncaught exception in a thread, which is typically written to stderr in backend JVM programs and crashes Android applications. Join is used to wait for the launched coroutine to complete before propagating its exception. A crashed child coroutine, on the other hand, cancels its parent with the matching exception.
async / await:-
The async keyword is used to initiate a coroutine that computes a result. You must use await on the result, which is represented by an instance of Deferred. Uncaught exceptions in async code are held in the resultant Deferred and are not transmitted anywhere else. They are not executed until processed.
Ques 14. What are some of the disadvantages of Kotlin?
Following are some of the disadvantages of Kotlin:
- In Kotlin, there are a few keywords that have non-obvious meanings: internal, crossinline, expect, reified, sealed, inner, open. Java has none of these.
- Checked exceptions are likewise absent in Kotlin. Although checked exceptions have become less prominent, many programmers believe them to be an effective technique to ensure that their code is stable.
- A lot of what happens in Kotlin is hidden. You can almost always trace the logic of a program in Java. When it comes to bug hunting, this can be really useful. If you define a data class in Kotlin, getters, setters, equality testing, tostring, and hashcode are automatically added for you.
- Learning resources are limited. The number of developers who are moving to Kotlin is growing, yet there is a small developer community accessible to help them understand the language or address problems during development.
- Kotlin has variable compilation speed. In some situations, Kotlin outperforms Java, particularly when executing incremental builds. However, we must remember that when it comes to clean builds, Java is the clear winner.