Java Issues Fixed in Kotlin
Nowadays, Kotlin has been considered as a modern statically typed programming language used by over 60% of professional Android developers, which helps you boost efficiency, satisfaction, and code safety. Notwithstanding the fact that Java has some best-practices to encounter controversial issues, particularly in Android development, some issues have been remained to address. This essay will provide you with some Java issues that have been fixed and improved in Kotlin for Android developers.
Introduction and Overview
As a matter of fact, Kotlin is an expressive and concise programming language that decreases typical code errors, and integrates into existing Android applications easily. Additionally, Kotlin has been recommended firmly by Google to build an Android app. There are many reasons can be mentioned for using Kotlin in Android development. For instance, you can do more tasks in practice with less code. Google has mentioned that 67% of professional developers who use Kotlin mention Kotlin has increased their efficiency. Moreover, Kotlin supports a number of advanced features to help you avoid common programming mistakes, like null pointer exceptions. It has been showed that Android apps that include Kotlin code are 20% less likely to crash in reality. Another important point is that you can call Java-based code from Kotlin, or call Kotlin from Java-based code. This means Kotlin is 100% interoperable with the Java language. Lastly, Kotlin coroutines can simplify background task management considerably, including calling network calls and accessing local data. This article aims to discuss some Java issues that have been fixed and addressed effectively in Kotlin for Android developers.
Null Safety in Kotlin
As an Android developer, one of the main problems that you must address is NullPointerException(NPE) in developing your codes with Java. Even though Java has some best-practices to deal with, NPE has been remained as one of the most problematic issues for all developers. In fact, NullPointerException usually occurs in some common situations in Android programming with Java as follows:
- Accessing or modifying the field of null object
- Invoking methods on an object, which is not initialized
- Parameters passed in a method are null
- Throwing null with a Throw-able value
- Calling toString() method on object, which is null
- Comparing object properties block without checking null equality
- Taking the length of null as if it were an array
- Using synchronized on an object, which is null
- Chained statements like multiple method calls in a single statement
- Accessing or modifying the slots of null as if it were an array
As you know, to avoid this exception, Java has some best-practices, such as avoiding chained method calls, using Ternary Operator, and etc. However, Kotlin null safety has been proposed to eliminate NPE form the code. NPE can just only possible on following cases:
- Throw NullPointerException() as an explicit call
- Using external java code as Kotlin, which means Java interoperability.
- An uninitialized of this operator that is available in a constructor passed and used another place.
- Use of the !! operator
- A superclass constructor calls an open member.
Some best-practices to avoid NPE in Kotlin
Initially, Kotlin differentiates between references that can hold null (nullable reference) ones, which cannot hold null (non-null reference). In other words, types of String are not nullable in general. Therefore, to create a string that can hold null value, we must explicitly define it by putting ? after String. For instance:
var sampleTest: String = “Test” // Regular initialization means non-null by default
sampleTest = null // compilation error!
We can be able to assign null to a nullable type variable easily, but we should use the safe operator to get the length of the string. Remember once you indicates a variable using a question mark in Kotlin, it will become a special type of variable, which only can be accessed by compile-time check.
var sampleString : String? = "Test"
sampleString = null // There is no problem in executing because we declared this variable as a nullable.
print(sampleString) // The output of this code is printing the null.fun main(args: Array<String>){
var sampleTest: String = "Sample"
sampleTest = null //Compile Time Error!
println(sampleTest.length) // compiler error! because string can be null
To access the property of nullable type in Kotlin, there have been offered some ways as follows:
1. Using expressions for checking null
In general, you can explicitly check for null, and manage the two options separately:
val sampleVariable = if (sampleTest != null) sampleTest.length else -1
For facing more complex situations, one of the most typical approaches for checking null reference is using if-else expression as follows:
val sampleTest: String? = "Test"
if (sampleTest != null && sampleTest.length > 0) {
print("String of length ${sampleTest.length}")
} else {
print("Empty string!")
}
The important point is that sampleTest must be immutable (once created, an object/variable can’t be changed).
2. Using safe Call operator (?.)
Basically, using expressions for null check is a simple approach, however, a number of nested if-else condition could be considered as a problematic issue. So, Kotlin has a second way, which is called Safe Call Operator (?.) that diminishes the complexity, and execute an operation just only when the particular reference holds a non-null value. In short, it allows you to combine a null-check and a method call in a single expression appropriately. So, the following expression has the same behavior to the second example:
sampleTest?.toUpperCase()if(sampleTest != null)
sampleTest.toUpperCase()
else
null
Besides, Safe Calls are helpful in chains. Obviously, a chain returns null if any of the properties in it is null. For example:
person?.section?.head = managersPool.getManager()
Also, to accomplish a specific operation just only for non-null values, you can utilize the Safe Call operator together with let() method as follows:
val SampleList: List<String?> = listOf("Java", "Kotlin", null, "Android")var newlist = listOf<String?>()for (item in sampleList) {
item?.let { println(it) } // Print all non-nullable values and ignores null
}
3. Elvis operator
As a matter of fact, Kotlin uses an advance operator, which is called Elvis operator(?:) that return the non-null value or default value even the conditional expression is null. Besides, it checks the null safety of values.
val sampleTest = firstTest ?: "Expected"
So, the above expression is the same to the following one:
val sampleTest = if(firstTest!= null)
firstTest
else
"Expected"
Also, as an alternative way of writing the if expression as follows, you can have an opportunity to write it with the Elvis operator ?:
as follows:
val firstVariable: Int = if (sampleVariable != null) sampleVariable.length else -1
All in all, the right-hand side expression is assessed only if the left side of Elvis operator found to be null.
val firstVariable= sampleVariable?.length ?: -1 //Using Elvis
4. Using Smart Casts
As you know, in Java, it is necessary for having explicit type casting on the variable before accessing the properties of that variable. Nevertheless, Kotlin provides Smart Cast as an advanced feature. In other words, the Kotlin compiler converts the variable to a particular class reference automatically once it is passed through any conditional operator. For instance, the following example shows type casting in Java:
Object sampleObject = "Test";if(sampleObject instanceof String) { String sampleString = (String) sampleObject; // Explicit type casting in Java System.out.println("length of String " + sampleString.length());}
In Kotlin, there is no need for explicit type casting. You can use is operator to check the type of variable. Thus, the compiler casts the variable to the target type automatically as follows:
fun main(args: Array<String>) { val sampleString: String? = "Test"if(sampleString is String) {
println("length of String ${sampleString.length}")
}
else {
println("String is null")
}
}
5. Using the !! operator
Essentially, the not-null assertion operator (!!) converts any value to a non-null type, and throws an exception if the value is null as well. If you want NPE, you can ask it explicitly by using this operator. For example:
fun main(args: Array<String>) { var sampleString : String? = "Test" println(sampleString!!.length) //print 4 str = null str!!.length // You have NPE here}
6. Having a collections of a nullable type
Basically, if you have a collection of elements of a nullable type and you want to filter non-null elements, you can perform it easily by using filterNotNull() method as follows:
val nullableList: List<Int?> = listOf(3, 6, null, 8, 10) val intList: List<Int> = nullableList.filterNotNull()
7. Kotlin Safe Cast Operator (as?)
In some cases, it is not possible to cast types; hence, it throws an exception, which is called unsafe cast. For instance, A nullable string (String?) cannot be cast to non-nullabe string (String) because it throws an exception. To address this problem safely, Kotlin provides a safe cast operator as? to cast safely to another type. This means if casting is not possible, it returns a null rather than throwing an ClassCastException. The following example shows if you want to cast Any type of string value that is initially known by the programmer into a nullable string then it works properly. In contrast, if you initialize the Any with Integer value, and try to cast into a nullable string, this typecasting is not possible and returns null.
fun main(args: Array<String>){ var sampleString: Any = "Test" val secondString: String? = sampleString as? String // it works sampleString = 20 val thirdString: String? = sampleString as? String // Type casting is not possible. It returns null to sampleString. val lastString: Int? = sampleString as? Int // it works }
Invariant Array in Kotlin
Unlike Java programming language, arrays in Kotlin are invariant. This means that Kotlin will not support you to assign an Array<String>
to an Array<Any>
because it prevents a possible runtime failure and implicit type safety issues. Furthermore, passing an array of a subclass as an array of superclass to a Kotlin method cannot be done, but for Java methods this is allowed through platform types. Kotlin also provides specialized classes to create arrays of primitive data types without boxing overhead, such as ByteArray, ShortArray, and IntArray. These classes have no inheritance relation to the Array class, but they have the same set of methods and properties.
Arrays in Kotlin are not built on native types, but are instead based on a Java array. Although these are similar, they do behave slightly differently. In Java, we can assign an array of a type to an array of its parent type. Arrays in Kotlin are invariant, which means that an array of a specific type cannot be assigned to an array of its parent type. It is not possible to assign Array<Integer> to Array<Any>. This provides implicit type safety and prevents possible runtime errors in the application.
Checked Exceptions
Fundamentally, the Exception Handling is one of the powerful approaches to manage the runtime errors. As a result, the normal flow of the application could be maintained. In general, there are two types of exceptions:
- Checked Exception: Exceptions that occur at the compile-time, like IOException.
- Unchecked Exception: Exceptions that occur at the runtime, like OutofBound exception.
Essentially, Kotlin does not have checked exceptions at all. All exception classes in Kotlin inherit the Throwable class. Every exception has a message, a stack trace, and an optional cause. There are some reasons for having no support for checked exception in Kotlin. For example:
Examination of small programs leads to the conclusion that requiring exception specifications could both enhance developer productivity and enhance code quality, but experience with large software projects suggests a different result — decreased productivity and little or no increase in code quality.
High-order functions and lambdas
As a matter of fact, a function is a unit of code that accomplish a special task. In a word, function is used to break the code into smaller modules that makes the code more manageable and reusable. Kotlin uses a group of function types to represent functions as a statically typed programming language, and offers a set of specialized language constructs, like lambda expressions. Fundamentally, Lambdas expression and Anonymous function both are function literals. This means these functions are not declared; however, they can be passed immediately as an expression. For instance, the syntax of Lambda expression:
val lambda_name : Data_type = { argument_List -> code_body }
For instance, you can use the Lambda expression as follows that are equivalent :
val sum = { x: Int, y: Int -> x + y } //After eliminating the optional partval sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y } // Using the complere form
In Kotlin, you have to have an explicit declare for the type of your lambda expression. If lambda returns no value, you can use it as Unit. Some examples for Lambdas with return type could be defined as follows:
val firstSample: (Int) -> Int = (a -> a * a)
val secondSample: (String,String) -> String = { a , b -> a + b }
val thirdSample: (Int)-> Unit = {print(Int)}
Even though the return type can be inferred from the Lambda expression automatically, it would be a requisite matter in some special cases. Therefore, if you require to write it explicitly, you can use an alternative solution that is called anonymous function. The body of this function could be either an expression or a block like a typical function. For example:
fun(x: Int, y: Int): Int {
return x + y
}
Kotlin uses function types, like (Int) -> String
, for declarations that deal with functions: val onClick: () -> Unit = ...
. So, these types have a special notation, which corresponds to the signatures of the functions, including their parameters and return values.
In Kotlin programming, a function can accept a function as parameter or can return a function, which is called Higher-Order function. This means instead of Integer, String or Array as a parameter to function, we can be able to pass anonymous function or lambdas. In most cases, lambda expressions are passed as parameters in Kotlin functions.
Finally, in addition to some benefits of using different types of functions in Kotlin, like Lambda expressions, you can use it in SAM conversions. In Kotlin, these function types also provide more flexibility and efficiency in comparison with Java’s SAM-conversions. For functional interfaces in Kotlin, you can use SAM conversions that help make your code more concise and readable by using lambda expressions.
fun interface IntPredicate {
fun accept(i: Int): Boolean
}
For instance, if we want to create an instance of class using SAM conversion in Kotlin, you can write code as follows:
val isEven = IntPredicate { it % 2 == 0 }
In conclusion
Initially, Kotlin is an expressive and concise programming language that diminishes typical code errors, and integrates into existing Android applications easily. Although Java has some best-practices to deal with controversial issues, particularly in Android development, some issues have been remained to address. This essay considered some Java issues that have been fixed and enhanced in Kotlin for Android developers based on JetBrains documents and resources.