I wish I had started playing around with the more idiomatic ways to write Kotlin code much sooner since it improves the quality of the code and makes writing code much more enjoyable. I agree that you can easily get by without ever using the less-known Kotlin features, however, in the hands of more dedicated software enthusiasts, those features can help improve the code dramatically.
Kotlin supports both functional and object-oriented programming. While talking about functional features of Kotlin then we have concepts like functions, higher-order functions and lambdas etc. which represent Kotlin as a functional language.
Kotlin also supports Object Oriented Programming (OOP) and provides features such as abstraction, encapsulation, inheritance, etc. This tutorial will teach you all the Kotlin OOP features in simple steps.
Kotlin Class
Kotlin class is similar to Java class, a class is a blueprint for the objects which have common properties. Kotlin classes are declared using keyword class. Kotlin class has a class header which specifies its type parameters, constructor etc. and the class body which is surrounded by curly braces.
There are different types of Kotlin classes availbale, Lets start discuss about each class one by one.
Kotlin classes are final by default. Other classes cannot inherit from a final class. To make a class inheritable, we mark it with the open keyword.
1. Inline classes
Inline classes allow creating wrappers for a value of a certain type and such wrappers would be fully inlined. This is similar to type aliases but inline classes are not assignment-compatible with the corresponding underlying types.
Use cases:
Unsigned types
@JvmInline
value class UInt(private val value: Int) { ... }
@JvmInline
value class UShort(private val value: Short) { ... }
@JvmInline
value class UByte(private val value: Byte) { ... }
@JvmInline
value class ULong(private val value: Long) { ... }
Native types like size_t for Kotlin/Native
Inline enum classes
- Int enum for Android IntDef
- String enum for Kotlin/JS (see WebIDL enums)
Example:
@JvmInline
enum class Foo(val x: Int)
{
A(0), B(1);
fun example() { … }
}
The constructor's arguments should be constant values and the values should be different for different entries.
Units of measurement
Result type (aka Try monad) KT-18608
Inline property delegates
class A {
var something by InlinedDelegate(Foo()) // no actual instantiation of `InlinedDelegate`
}
@JvmInline
value class InlinedDelegate<T>(var node: T) {
operator fun setValue(thisRef: A, property: KProperty<*>, value: T) {
if (node !== value) {
thisRef.notify(node, value)
}
node = value
}
operator fun getValue(thisRef: A, property: KProperty<*>): T {
return node
}
}
Inline wrappers
Typed wrappers
@JvmInline
value class Name(private val s: String)
@JvmInline
value class Password(private val s: String)
fun foo() {
var n = Name("n") // no actual instantiation, on JVM type of `n` is String
val p = Password("p")
n = "other" // type mismatch error
n = p // type mismatch error
}
API refinement
// Java
public class Foo {
public Object[] objects() { ... }
}
// Kotlin
@JvmInline
value class RefinedFoo(val f: Foo) {
inline fun <T> array(): Array<T> = f.objects() as Array<T>
}
Inline classes are declared using soft keyword value and must have a single property:
value class Foo(val i: Int)
In Kotlin/JVM, however, they should be annotated with additional @JvmInline annotation:
@JvmInline
value class Foo(val i: Int)
In Kotlin/Native and Kotlin/JS, because of the closed-world model, value-based classes with single read-only property are inline classes. In Kotlin/JVM we require the annotation for inline classes, since we are going to support value-based classes, which are a superset of inline classes, and they are binary incompatible with inline classes. Thus, adding and removing the annotation will be a breaking change.
The property i defines type of the underlying runtime representation for inline class Foo, while at compile time type will be Foo.
From language point of view, inline classes can be considered as restricted classes, they can declare various members, operators, have generics.
Example:
@JvmInline
value class Name(val s: String) : Comparable<Name> {
override fun compareTo(other: Name): Int = s.compareTo(other.s)
fun greet() {
println("Hello, $s")
}
}
fun greet() {
val name = Name("Kotlin") // there is no actual instantiation of class `Name`
name.greet() // method `greet` is called as a static method
}
Current limitations
Currently, inline classes must satisfy the following requirements:
Inline class must have a primary constructor with a single value parameter
Inline class must have a single read-only (val) property as an underlying value, which is defined in primary constructor
Underlying value cannot be of the same type that is containing inline class
Inline class with undefined (recursively defined) generics, e.g. generics with an upper bound equal to the class, is prohibited
@JvmInline
value class A<T : A<T>>(val x: T) // error
Inline class must be final
Inline class can implement only interfaces
Inline class cannot have backing fields
Hence, it follows that inline class can have only simple computable properties (no lateinit/delegated properties)
Inline class must be a toplevel or a nested class. Local and inner inline classes are not allowed.
Inline classes cannot have context receivers.
Inline classes cannot have var properties with backing fields.
For more detail about inline class and other. You can click here.
- Kotlin empty class In the following example, we create two empty classes.
EmptyClass.kt
package com.demo
class Being {}
class Empty
fun main() {
val b = Being()
println(b)
val e = Empty()
println(e)
}
An empty class has no members or member functions. The curly brackets can be omitted.
- Kotlin data class Some classes are desinged to hold data. With data classes, we can considerably reduce the boilerplate code. Compiler automatically creates the equals, hashCode, toString, and copy functions. A data class in Kotlin is created with the data keyword. The data classes must follow a couple of rules. The primary constructor needs to have at least one parameter. All primary constructor parameters must be marked as val or var. The data classes cannot be abstract, open, sealed or inner.
data class User(val name: String, val email: String)
fun main() {
val u = User("Peter Novak", "pnovak47@gmail.com")
println(u)
println(u.name)
println(u.email)
val (name, email) = u;
println("$name $email")
val u2 = User("Peter Novak", "pnovak47@gmail.com")
println(u == u2)
println(u === u2)
}
- Kotlin abstract class Kotlin supports abstract classes - just like Java, these are classes which you never intend to create objects from. An abstract class is incomplete or useless without some concrete (non-abstract) subclasses, from which you can instantiate objects. A concrete subclass of an abstract class implements all the methods and properties defined in the abstract class - otherwise that subclass is also an abstract class! We create an abstract class with the abstract modifier (similar to Java).
abstract class Employee (val firstName: String, val lastName: String) {
abstract fun earnings(): Double
}
Note that not all members have to be abstract. In other words, we can have method default implementation in an abstract class.
abstract class Employee (val firstName: String, val lastName: String) {
// ...
fun fullName(): String {
return lastName + " " + firstName;
}
}
Here we created the non-abstract function fullName() in an abstract class Employee. Concrete classes (subclasses of the abstract class) can override an abstract method's default implementation-but only if the method has the open modifier specified (you will learn more about this shortly).
- Kotlin sealed class A sealed class is used for representing restricted class hierarchies. A value can have one of the types from a limited set, but cannot have any other type. Sealed classes are more powerful enum classes. Note: in some languages, such as C# or Scala, a sealed class is a class which prohibits inheritance. Sealed classes are abstract and can have abstract members; this means that they cannot be instantiated directly. Sealed classes cannot have public constructors (The constructors are private by default). Sealed classes can have subclasses, but they must either be in the same file or nested inside of the sealed class declaration.
sealed class Shape
class Circle(var radius: Float) : Shape()
class Square(var width: Int) : Shape()
class Rectangle(var width: Int, var height: Int) : Shape()
fun getArea(e: Shape) =
when (e) {
is Circle -> println("Circle area is ${Math.PI * e.radius * e.radius}")
is Square -> println("Square area is ${e.width * e.width}")
is Rectangle -> println("Rectangle area is ${e.width * e.height}")
}
fun main() {
val circle = Circle(7f)
val square = Square(5)
val rectangle = Rectangle(8, 6)
getArea(circle)
getArea(square)
getArea(rectangle)
}
In the example, we have a sealed Shape class. It has three subclasses: Circle, Square, and Rectangle.
fun getArea(e: Shape) =
when (e) {
is Circle -> println("Circle area is ${Math.PI * e.radius * e.radius}")
is Square -> println("Square area is ${e.width * e.width}")
is Rectangle -> println("Rectangle area is ${e.width * e.height}")
}
The getArea function calculates the area for a shape. Note that the else statement is not needed, since the compiler knows that the list of options is exhaustive.
Circle area is 153.93804002589985
Square area is 25
Rectangle area is 48
- Kotlin nested class A nested class is declared inside another class.
class Outer {
val name = "Outer"
fun show() = "the name: $name"
class Nested {
val name = "Nested"
fun show() = "the name: $name"
}
}
fun main() {
println(Outer().show())
println(Outer.Nested().show())
}
In order to access the nested class, we specify the name of its outer class. So the show function of the nested class is invoked like this: Outer.Nested().show. A nested class cannot access the members of the outer class.
- Kotlin inner class Inner classes are created with the inner keyword. Unlike nested classes, they can access the members of their outer classes.
class Outer {
val name1 = "Outer"
fun show() = "the name: $name1"
inner class Inner {
val name2 = "Inner"
fun show() = "data: $name2 and $name1"
}
}
fun main() {
println(Outer().show())
println(Outer().Inner().show())
}
In the example, we have one inner class. Its show function outputs the name1 member of the Outer class.
Top comments (0)