Jim H
Jim H
  • Home
  • Resume
  • Code Review-BestPractices
  • Java Best Practices
  • Kotlin Best Practices
  • Homophones in English
  • Personal
  • Blog

Types of Classes

Back to Contents


Aside from the ordinary types of classes shown in previous sections, Kotlin allows several other types of classes:

  • Final class: All (non-abstract) Kotlin classes are final unless they are declared open, as described in the section on immutability.
  • Abstract class: A class decorated with the keyword abstract has at least one member (field or function) that is also declared abstract, and which has no definition. An abstract class is automatically open. A non-abstract member must be declared open to be overridden in a derived class.
  • Open class: explicitly allows itself to be inherited; its definition is decorated with the open keyword.. Any member that is also declared open may be overridden. (Classes derived from a derived class do not need to be "re-declared" open for subsequent classes to override the same member. To close an open member in the children of a child class, add the final keyword.
  • Data class: intended to hold data (this should not be a surprise), a data class must have at least one property defined in its primary constructor. Data classes automatically generate several member functions: 
    1. copy() will create a copy of the class instance, optionally with some members changed.
    2. equals() / hashCode() will be generated using all properties declared in the constructor (but not properties not in the constructor, so such a property would not be part of the object's "state").
    3. componentN() is a getter (and setter, if the property is var) for the Nth property in the constructor.
    4. toString(), formatted as “Datum(prop1=foo,prop2=bar)” for the instance of Datum with properties named “prop1” and “prop2”.

  • Sealed class: a sealed class (or interface) must be declared with all of its direct descendants at the same time; and, those are the only direct descendants the sealed class can have. (Though it is legal for one or more of those descendants to be declared open, so a sealed class can have indirect descendants that it doesn't know about.) A sealed class is, by definition, abstract.
  • Nested and inner classes: Like Java, and other object oriented derivatives of C, Kotlin allows classes to be defined within other classes. In Java, a nested class automatically has access to the outer class's members, unless it is declared static. In Kotlin, a nested class only has this access if it is declared inner. A nested class that is not declared inner behaves just like a nested class in Java that is declared static.
  • Inline value classes: Use the soft keyword value to declare a class with a single property initialized in its primary constructor. The class may still have other members and functions, but that single value is "inlined" into its usages, highly optimized for performance.
  • Object declarations: single instances of objects, always initialized on first use, and thread-safely. Refer to the object with its name. Most often used to create a Singleton.
  • Companion object: a special (singleton) object that allows "static" access to a class, just referring to the class name. (In other words, instance variables go in the class body, and class variables go in the companion object body.) By convention, if a class has a companion object, it should be the last item declared in the class.
  • Enum classes: covered here and in Java BP.

Throwables

Back to Contents


Throwable is the superclass for exceptions and errors, just as in Java.  A Throwable will unwind the call stack until it is caught, or until the program (or thread) is completely unwound and terminates. Error and Exception are both open classes, and may be inherited.


Error

Exception

Throwing exceptions

Where to catch

Precondition functions

Releasing resources

Error

Back to Contents

Back to section top


An Error represents a condition that an application should not try to catch; usually an abnormal condition, such as the VM running out of memory, or a class missing from the class path.  In theory, it is impossible to recover from either of those conditions. The most common method of handling Error is to let it terminate the program, writing a useful log message.  Then you can fix whatever happened to your build.

Exception

Back to Contents

Back to section top


In Kotlin, all exceptions are unchecked. This is a clear deviation from Java, where only RuntimeException and its descendants are unchecked. Because Kotlin, in the JVM, can interface with Java classes, those classes may need to expect an exception. For this reason, the @Throws annotation may be used to declare "checked" exceptions. (This is also useful for interfacing with languages like Swift and Objective-C in Kotlin/Native.)

Unlike Error, there will be times when you want to catch an Exception and recover from the condition. The most common situation is handling "evil" input data: just print a message to the console and/or log, and ignore the bad input. 

Throwing Exceptions

Back to Contents

Back to section top


When your code encounters an exceptional condition, you may throw an exception yourself. The first step is to create the exception. The zeroth step is to choose the type of exception to throw:

  • The Java library has a large number of "standard" subclasses of Exception and Error. 
  • You will probably never throw an Error, as most are thrown by conditions detected by the JVM (OutOfMemoryError, NoSuchMethodError).
  • Most exceptional conditions you detect will likely be appropriate for some subclass of Exception. For the else case of a when, you might want to throw an IllegalStateException. A floating-point operation that results in a NaN or Infinity (see below) might warrant an ArithmeticException.
  • In some cases, you will want to define your own exception type. Make sure it inherits from Exception through zero or more intermediate classes.

The constructors for standard exception classes come in four general flavors:

  • A plain constructor with no parameters;
  • A constructor that takes String message, to describe the condition;
  • A constructor that takes Throwable cause, to wrap another condition; and
  • A constructor that takes both message and cause.

Some standard Exception types don't come with the flavors that take the cause parameter. These types are assumed never to be caused by another condition. When an exception is logged, it often includes a stack trace; if the throwable had a cause, that cause appears with its own stack trace. You should rarely (or never) call the no-parameter constructor. Create a useful message; your successors will thank you, and you might even thank yourself.

If you write your own exception class, you should probably include at least the constructor with a message parameter. Include the cause parameter if you might wrap a different Throwable.

If you ever catch and then re-throw an exception, throw a new exception with the caught exception as the cause.

Where Should You Catch?

Back to Contents

Back to section top


In general, if a method has no idea what to do with an exception, it should not catch it.  Rather, let the call stack unwind until it reaches a method that can handle the issue.

Different exceptions might be handled with different catch blocks; or sometimes with the same block.  Consider:

try {

  <something> 

} catch (EOFException ex) {

  <handle EOF> 

} catch (FileNotFoundException | RemoteException ex) {

  <handle those two> 

} catch (IOException ex) {

  <handle non-specific IOException>

}

You may have as many types separated by | as make sense for the catch block.

Note that the catch (IOException ex) block is last. If it came before EOFException's or FileNotFoundException's catch, you would never enter those catch blocks, because they are subclasses of IOException.  Always catch the more specific exception first. The base class Throwable should only be caught explicitly as a last resort, and then only in a debugging situation, to figure out what was actually thrown and why.

Notes: 

  • When you explicitly throw an exception, write a meaningful message 
  • Never leave a catch block empty. This would result in the exception just being ignored, and the program continuing as if nothing bad had happened. That might not be the right way to handle a serious issue.


One more thing:

In Kotlin, almost any block, including try/catch, is an expression that has a value, which can be assigned to a variable. Consider:

var answer: Int

try {

    answer = question()  // but question() can throw an exception...

} catch (ex: SomeException) {

    println("question threw something, defaulting to 1")

    answer = 1

}

Even though we wanted answer to be val, it needed to be var just so we could later change it. Later, we add some code, and (inadvertently) modify answer. That's a subtle bug...

Instead, just treat the try as an expression:

val answer = try {

    question()

} catch (ex: SomeException) {

    println("question threw something, defaulting to 1")

    1

}

If the program gets to the end of the try block, the expression's value is the value of the last expression in the try. If a SomeException is caught, the expression's value is the value of the last expression in that catch block. (If the exception is caught elsewhere, the value is moot, since the assignment was aborted and stack has unwound.) If there is a finally block, it has no effect on the value of the try expression.

Precondition Functions

Back to Contents

Back to section top


Kotlin offers some efficient ways to check for invalid conditions, and throw exceptions if those conditions are met.

  • require():  You can use require() to check for valid arguments. If the condition specified as the argument to require() is not met, an IllegalArgumentException is thrown.

fun bePositive(value: Int) {

    require(value > 0) { "I needed a positive integer, but I got $value" }

    // ... 

}


  • check(): Similar to require, but not specifically for function arguments. If the condition specified by check() is not met, an IllegalStateException will be thrown.

    val optionalFoo: Optional<Foo> = maybeGetFoo()

    check(optionalFoo.isPresent) { "Foo is missing! }


  • error(): The error() function will unconditionally throw an IllegalStateException. This is appropriate for catching an unexpected value in the else clause of a when.

fun firstOfTwentyQuestions(val animalVegetableOrMineral: String): Int {

    return when (animalVegetableOrMineral) {

        "animal" -> 1

        "vegetable" -> 2

        "mineral" -> 3

        else -> error("Unexpected type")

    }

Releasing Resources

Back to Contents

Back to section top


Veterans of C++ and its concept of Resource Acquisition Is Initialization (RAII) will miss the concept of a destructor that releases resources.  Since Java uses garbage collection instead of destruction, and GC happens only when necessary, something else is needed.


finally

Any try block (with or without catch blocks) may have a finally block as well. The statements in the finally block will be executed when either:

  • If no exception is thrown, when the try block exits; or
  • If any catch block catches the exception, after the catch block exits.
  • If no catch block catches the exception, when the exception is thrown.

Resource rez;

try {

  rez = acquireResource();

  <statements> 

} catch(Exception ex) {

  <more statements> 

} finally {

  <even more statements>  

  releaseResource(rez); 

}

The finally block can be used to release any resource acquired for the try. Make sure that any exceptions thrown from the finally block are handled, and that your resources are really released. Whether there are catch blocks before the finally block, or elsewhere in the stack chain, is an implementation detail.


use function

Since Java 8, the try with resources form has added a shorthand for releasing resources. A similar facility exists in Kotlin: the use function.

val myFile = File(path)

val textLine = myFile.bufferedReader().use { reader ->  reader.readLine() }

The bufferedReader method returns a BufferedReader (surprised?). Because BufferedReader implements the interface AutoCloseable, the reader will be closed when the lambda exits, just as if by a finally block.

Integer and Floating-point Arithmetic

Back to Contents


Integer and floating-point math in Kotlin is identical* to math in Java. Float and Double are, respectively, 32-bit and 64-bit IEEE 754 binary floating-point numbers, and if they are non-nullable, are represented by the float and double primitives; and BigDecimal is available.

Kotlin provides operator overloads for BigDecimal and BigInteger, so it isn't necessary to call methods like BigDecimal.add() and .multiply() explicitly.

On platforms other than the JVM, it is theoretically possible that integers use something other than Two's Complement arithmetic, but I have not heard of a modern processor that uses One's Complement or BCD, or anything even more extreme.


[* On the JVM, Kotlin arithmetic is definitely identical to Java arithmetic. On other platforms, it could, conceivably be different, but it's not likely. For integer math, the world has pretty well settled on Two's Complement, and floating-point arithmetic on any architecture developed after 1985 will conform to the IEEE 754 standard. The only differences one is likely to encounter would be related to word length.]

Date & Time

Back to Contents


Does anybody really know what time it is? (Does anybody really care? Is it 25 or 6 to 4?) 


If you are working with dates and times, please understand time zones and the class kotlinx.datetime.TimeZone.  Using a TimeZone, you can convert between Instant and LocalDateTime. Create a TimeZone by

    val zone = TimeZone.of("America/Los_Angeles")    // for example


As for writing your own date/time handling code, I have one word of advice: Don’t. It’s much, much harder than it looks. Here’s an entertaining and educational video describing the issues. 


The argument to TimeZone.of() corresponds to the zones defined in /usr/share/zoneinfo on Unix systems.


kotlinx-datetime

For simple operations with calendars, consider using the library kotlinx-datetime. This package contains just about everything needed to simplify date and time calculations, including operator overloads for computing differences in time, and conversions to java.time objects. To include the package in your project:

Maven

dependency>
   <groupId>org.jetbrains.kotlinx</groupId>
   <artifactId>kotlinx-datetime-jvm</artifactId>
   <version>0.6.1</version>
</dependency>
Gradle

dependencies {

   implementation 'org.jetbrains.kotlinx:kotlinx-datetime-jvm:0.6.1'

}

(Keep the version up to date, of course.)

A nice tutorial on kotlinx-datetime is here.

First PagePreviousNextHome

Copyright © 2019-2025 Jim Hamilton - All Rights Reserved.

Powered by

This website uses cookies.

We use cookies to analyze website traffic and optimize your website experience. By accepting our use of cookies, your data will be aggregated with all other user data.

DeclineAccept