Kotlin is a modern programming language that compiles to Java bytecode. It's free and open source, and makes coding for Android even more fun.
In the previous article, you learned more about object-oriented programming by digging into abstract classes, interfaces, inheritance, and type aliases in Kotlin.
In this post, you'll continue to learn about programming in Kotlin by learning about exceptions and how to handle them.
1. Exception Handling
Exceptions are used to indicate a problem in our code during a program's execution. Exception handling is the capability to address (or handle) the exception that might occur. If we don't handle any exception that occurs, our program will stop execution abruptly—crashing our app immediately.
Exception handling allows our program to continue execution even if there was an exception (though it is highly recommended to log your exceptions and report them using a crash reporting tool like Crashlytics).
In Java, we have two kinds of exceptions: checked and unchecked. I'll briefly explain both of them, but we'll start with unchecked exceptions.
Unchecked Exceptions
These are exceptions that are thrown because of flaws in your code. They are a direct or indirect subclass of the RuntimeException
superclass.
Examples of unchecked exceptions include:
ArithmeticException
: thrown when you divide by zero.ArrayIndexOutOfBoundExceptions
: thrown when an array has been accessed with an illegal index.SecurityException
: thrown by the security manager to indicate a security violation.NullPointerException
: thrown when invoking a method or property on a null object.
A method that might throw an unchecked exception doesn't contain any information about the exception thrown on its method declaration.
public Integer divideByZero(Integer numerator, Integer denominator) { return numerator / denominator; } divideByZero(7, 0) // throws ArithmeticException
These types of exception can be prevented by coding right. In the code above, we should have checked if the denominator was zero before performing the operation. For these exceptions, the developer doesn't need to catch the exception using the try...catch
block. In other words, we aren't forced by the compiler to wrap the code that might trigger the exception in a try...catch
block. Instead, we should just make sure the exceptions never occur in the first place.
Also, remember, Kotlin is a null safe language. In other words, it can help us avoid getting NullPointerExceptions
in our code. You can read the Nullability, Loops, and Conditions post to get a refresher on null safety in Kotlin.
Checked Exceptions in Java
A method that might throw a checked exception needs to declare so in its method signature using the throws
keyword. If you call a method that throws a checked exception, you either need to throw it again from your function or to catch it and handle it using a try...catch
block.
Checked exceptions are exceptions that are checked at compile time. These kinds of exceptions inherit from the Exception
class. An example of this kind of exception is IOException
. This can occur when you try to access a file that can't be opened because it doesn't exist. (FileNotFoundException
is a subclass of IOException
.)
// perform in a background thread public void editFile(File file, String text) { try { file.getParentFile().mkdirs(); FileOutputStream fileOutputStream = new FileOutputStream(file); Writer writer = new BufferedWriter(new OutputStreamWriter(fileOutputStream)); try { writer.write(text); writer.flush(); fileOutputStream.getFD().sync(); } finally { writer.close(); } } catch (IOException e) { // Log the exception e.printStackTrace(); } }
In the preceding code, we used a try...catch
block to handle the IOException
inside the editFile()
method. We can now call the editFile()
method as normal, and the compiler won't complain.
editFile(new File(""), "my text");
Looking at the code below, we've refactored the method to instead use the throws
keyword in the method signature. This indicates to callers that they need to handle the exception IOException
that might be thrown when calling the method editFile()
.
// this should be in a background thread public void editFile(File file, String text) throws IOException { file.getParentFile().mkdirs(); FileOutputStream fileOutputStream = new FileOutputStream(file); Writer writer = new BufferedWriter(new OutputStreamWriter(fileOutputStream)); try { writer.write(text); writer.flush(); fileOutputStream.getFD().sync(); } finally { writer.close(); } }
To call the method above, we need to surround it in a try...catch
block to handle the exception.
try { editFile(new File(""), "my text"); } catch (IOException e) { e.printStackTrace(); }
This has been a brief look at exceptions in Java. Let's now see how Kotlin handles exceptions.
2. Exceptions in Kotlin
The main difference between Kotlin and Java exception mechanisms is that all exceptions are unchecked in Kotlin. In other words, they are not explicitly declared in the function signatures, as they are in Java.
fun editFile(file: File, text: String) { file.parentFile.mkdirs() val fileOutputStream = FileOutputStream(file) val writer = BufferedWriter(OutputStreamWriter(fileOutputStream)) try { writer.write(text) writer.flush() fileOutputStream.fd.sync() } finally { writer.close() } }
Here, we have converted the editFile()
method to a Kotlin function. You can see that the function doesn't have the throws IOException
statement in its function signature. throws
isn't even a keyword in Kotlin.
Also, we can call this function without surrounding it with the try...catch
block—and the compiler won't complain. In other words, there's no such thing as checked exceptions in Kotlin. All exceptions are unchecked. (Note that if there is an exception thrown, program execution will stop as normal.)
If we think this exception might arise, we should still handle it by surrounding the method with a try...catch
block—but this is not enforced by the Kotlin compiler.
try { editFile(File(""), "text 123") } catch (e: IOException) { e.printStackTrace() }
If the exception thrown inside the editFile()
function is an instance of the IOException
class, our catch
block will be executed, and we simply print the stack trace for debugging purposes.
The try...catch
Block
The try
construct with catch
and finally
clauses in Kotlin is similar to that of Java.
fun foo() { try { throw Exception("Exception message") } catch (e: Exception) { println("Exception handled") } finally { println("inside finally block") } }
Here, we are throwing an Exception
object inside the try
block. Notice we didn't include the new
keyword as we do in Java to create a new instance. Also notice that we didn't specify the exception that will be thrown in the function signature as we would have to in Java.
We handle all subclasses and classes of type Exception
in the catch block. The optional finally
block is always executed—this is where we typically close any resources or connections that were previously opened so as to prevent resource leaks. For example, if you open a file or create a database or network connection in a try
block, you should close or free it in a finally
block.
Note that in Kotlin the throw
construct is an expression and can combine with other expressions.
val letter = 'c' val result = if (letter in 'a'..'z') letter else throw IllegalArgumentException("A letter must be between a to z")
Also, the try
construct can be used as an expression.
fun foo(number: Int) { val result = try { if (number != 1) { throw IllegalArgumentException() } true } catch (e: IllegalArgumentException) { false } println(result) } foo(2) // false
Here, we assigned the value returned from the try...catch
block to the result
variable. If the number is not 1
, it throws an IllegalArgumentException
and the catch
block is executed. The false
expression in the catch
block value will be assigned to the result
variable. If the number is 1
instead, then the true
expression value will be assigned to the result
variable.
Java Interop
Exceptions in Kotlin behave as normal in Java—but I want to make you aware of a useful annotation called @Throws
in Kotlin that might come in handy. Because all exceptions in Kotlin are unchecked, developers who consume your Kotlin code from Java might not be aware that your functions throw exceptions. However, you can still add the possible exceptions that might be thrown to a method signature with the @Throw
annotation. This will alert Java callers that they need to handle the exception.
Let's see a practical example of this annotation.
/* Functions.kt file */ fun addNumberToTwo(a: Any): Int { if (a !is Int) { throw IllegalArgumentException("Number must be an integer") } return 2 + a }
Here, we defined a Kotlin function that can throw an exception IllegalArgumentException
only if the type passed to the function is not of type Int
.
We call this top-level function addNumberToTwo()
directly from Java in the following way:
public void myJavaMethod() { Integer result = FunctionsKt.addNumberToTwo(5); System.out.println(result); // 7 }
This works fine; the compiler isn't complaining. However, if we want to communicate with Java callers that the addNumberToTwo()
top-level function throws an exception, we simply add the @Throws
annotation to the function signature.
@Throws(IllegalArgumentException::class) fun addNumberToTwo(a: Any): Int { if(a !is Int) { throw IllegalArgumentException("Number must be an integer") } return 2 + a }
This @Throws
annotation can accept a comma-separated list of arguments of exception classes. In the code above, we just included one exception class—IllegalArgumentException
.
Now we have to update our Java code to handle the exception.
public void myJavaMethod() throws IllegalArgumentException { Integer result = FunctionsKt.addNumberToTwo(5); System.out.println(result); }
If we decompile the Kotlin addNumberToTwo()
function, using the Show Kotlin Bytecode feature (if you're in IntelliJ IDEA or Android Studio, use Tools > Kotlin > Show Kotlin Bytecode), we'll see the following Java code:
// ... public static final int addNumberToTwo(@NotNull Object a) throws IllegalArgumentException { Intrinsics.checkParameterIsNotNull(a, "a"); if (!(a instanceof Integer)) { throw (Throwable)(new IllegalArgumentException("Number must be an integer")); } else { return 2 + ((Number)a).intValue(); } } // ...
In the generated Java code above (some elements of the generated code were removed for brevity's sake), you can see that the compiler added the throws
keyword to the method signature—because we included the @Throws
annotation.
Conclusion
In this tutorial, you learned more about programming in Kotlin by looking into exceptions. We saw that Kotlin doesn't have checked exceptions but that instead, all exceptions are unchecked. We also looked at how to handle exceptions using the try...catch
block and saw the usefulness of the @Throws
annotation in Kotlin for Java callers.
To learn more about the Kotlin language, I recommend visiting the Kotlin documentation. Or check out some of our other Android app development posts here on Envato Tuts!
-
Android SDKJava vs. Kotlin: Should You Be Using Kotlin for Android Development?
-
Android SDKIntroduction to Android Architecture Components
-
Android SDKGet Started With RxJava 2 for Android
-
Android SDKConcurrency in RxJava 2
No comments:
Post a Comment