Mobile Development 25 min read

Kotlin Basics: Syntax, Classes, Objects, and Functions with Examples

This article provides a comprehensive Kotlin tutorial for Android developers, covering basic syntax, variable declarations, control statements, functions, classes, objects, delegation, and standard library utilities, illustrated with clear code examples and explanations.

Sohu Tech Products
Sohu Tech Products
Sohu Tech Products
Kotlin Basics: Syntax, Classes, Objects, and Functions with Examples

Preface: Kotlin is a new choice for Android development, offering concise syntax, safety, and functional programming features that make it very convenient in real projects.

1. Basic Syntax

1.1 Variable Declaration

Kotlin uses var for mutable variables and val for read‑only variables. The compiler can infer the variable type from the assigned value, which is called automatic type inference; declaring without an explicit type is an implicit declaration.

/**  * Student class  */
class Student {
    // mutable variable declaration keyword var
    var name: String = "小明"
    // implicit type declaration without specifying the type
    var age = 10
    // read‑only variable declaration keyword val
    val sex: String = "男"

    fun learn() {
        print("$name is learning")
    }
}

1.2 Statements

when expression

The when statement can be used as an expression; the branch that matches becomes the value of the whole expression.

val b = when (num) {
    in 0..9 -> true
    else -> false
}

in keyword usage

The in keyword checks whether a value lies within a range, and can also be used in for loops to iterate over collections.

// check if within range
if (num in 1..9) {
    print("ok")
}
// not in range
if (num !in 1..9) {
    print("no")
}
// iterate over array
for (name in names) {
    print(name)
}
// check if element is in array
if (name in names) {
    print("ok")
}

Type conversion

Kotlin uses the is keyword for type checking. After a successful check, the variable is automatically cast to the checked type.

fun foo(o: Any): Int {
    if (o is String) {
        // o is automatically cast to String
        return o.length
    }
    if (o !is String) {
        return 0
    }
    return 0
}

Null‑safety

Kotlin uses the ? operator for safe calls; for example, str?.length evaluates the length only when str is not null, preventing NullPointerException.

1.3 Function Declaration

A simple function is declared with the fun keyword.

fun plus(x: Int, y: Int): Int {
    return x + y
}

The function returns an Int value. The fun keyword introduces the function, and the signature follows the pattern fun name(params): ReturnType.

1.4 Default Parameters

Kotlin allows default values for parameters, reducing the need for overloads.

fun plus(x: Int, y: Int = 10): Int {
    return x + y
}

Calling plus(11, 20) uses the supplied y, while plus(11) uses the default value 10.

1.5 Varargs

Java uses ... for varargs; Kotlin uses the vararg keyword.

// Java varargs
public void selectCourse(String... strArray) { }

// Kotlin varargs
fun selectCourse(vararg strArray: String?) { }

2. Classes and Objects

2.1 Constructors

Kotlin classes can have a primary constructor and one or more secondary constructors. The primary constructor is part of the class header and can omit the constructor keyword when there are no annotations or visibility modifiers.

class Person constructor(name: String) {
    init {
        // initialization code
    }
    // secondary constructor delegating to primary
    constructor(name: String, parent: Person) : this(name) {}
    // another secondary constructor delegating to the previous one
    constructor(name: String, parent: Person, age: Int) : this(name, parent) {}
}

2.2 Inheritance

All Kotlin classes inherit from Any, which provides equals(), hashCode(), and toString(). A derived class must call the base class constructor if the base has a primary constructor.

class Derived(p: Int) : Base(p) {}

If the derived class has no primary constructor, each secondary constructor must initialize the base type using super or delegate to another constructor.

class MyView : View {
    constructor(ctx: Context) : super(ctx)
    constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
}

2.3 Properties

Getter and Setter

Properties automatically generate getters; val generates only a getter, while var generates both getter and setter. Custom getters and setters can be defined.

var age: Int = 11
    get() { return field }
    set(value) { field = value + 1 }

Backing Field

Using field inside a custom accessor accesses the automatically generated backing field. Without a proper backing field, recursive calls can cause a stack overflow.

class Person {
    var name = ""
        set(value) { field = value }
}

Constants

Constants can be declared with const val or with the @JvmField annotation to suppress the generation of a getter.

class Person {
    companion object {
        const val TAG = "Person"
    }
    @JvmField
    val TAG = "Person"
}

Late‑init Properties

Properties declared with lateinit can be initialized later; accessing them before initialization throws an exception.

2.4 Inner Classes

By default, nested classes are static. Adding the inner modifier makes the class non‑static and allows it to access members of the outer class.

class Outer {
    private val b: Int = 1
    inner class Inner {
        fun foo(): Int = b
    }
}
val d = Outer().Inner().foo() // == 1

2.5 Data Classes

Data classes are declared with the data keyword and must have at least one val or var parameter in the primary constructor. They automatically generate toString(), equals(), hashCode(), and copy().

data class Person(val name: String) {
    var age: Int = 0
}

fun foo() {
    val person1 = Person("John")
    val person2 = Person("John")
    person1.age = 10
    person2.age = 20
    // person1 and person2 are considered equal despite different ages
    person1.equals(person2) // true
}

2.6 Enum Classes

Each enum constant is an object. Enum classes can have properties, methods, and abstract methods that each constant must implement.

enum class Direction { NORTH, SOUTH, WEST, EAST }

enum class Color(val rgb: Int) {
    RED(0xFF0000) {
        override fun print() { print("red") }
    },
    GREEN(0x00FF00) {
        override fun print() { print("green") }
    },
    BLUE(0x0000FF) {
        override fun print() { print("blue") }
    };
    abstract fun print()
}

fun main() { Color.BLUE.print() }

2.7 Delegation

Class Delegation

Delegation can be implemented with the by keyword.

// interface
interface Base { fun print() }

// class that implements the interface
class BaseImpl(val x: Int) : Base {
    override fun print() { print(x) }
}

// delegate class
class Agent(b: Base) : Base by b

fun main(args: Array<String>) {
    val b = BaseImpl(1)
    Agent(b).print() // prints 1
}

Property Delegation

Property delegation is achieved by providing a delegate object that defines getValue and setValue operators.

class Delegate {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return "thank you for delegating '${property.name}' to me"
    }
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        print("$value has been assigned to '${property.name}'")
    }
}

class Example {
    var p: String by Delegate()
}

fun foo() {
    val e = Example()
    print(e.p) // prints thank you ...
    e.p = "new" // prints assignment message
}

Standard Delegates

Lazy: val lazyStr: String by lazy { "123" } Not‑null: var name: String by Delegates.notNull() Observable:

var name: String by Delegates.observable("old") { prop, old, new -> print("${prop.name} changed: $old -> $new") }

3. Functions

3.1 Local Functions

Kotlin allows functions to be defined inside other functions; the inner function can access variables from the outer scope (closure).

fun outer(str: String) {
    fun inner(index: Int) {
        str.substring(0, index)
    }
    inner(2)
}

3.2 Function Types

Function types are expressed as (A, B) -> C. They can also have a receiver type, e.g., A.(B) -> C. Suspend functions are prefixed with suspend.

// (A, B) -> C
val fun1: (String, String) -> Unit = { s1, s2 ->
    print("$s1 and $s2")
}

// A.(B) -> C
val fun2: String.(String) -> Unit = { s ->
    print("$this $s")
}

3.3 Lambda Expressions

Lambdas are written inside curly braces; parameters are declared before the -> arrow.

val sum = { x: Int, y: Int -> x + y }

Trailing lambda syntax allows the lambda to be placed outside the parentheses when it is the last argument.

fun sum(m: Int, n: Int, param: (Int, Int) -> Int): Int {
    return param.invoke(m, n)
}

fun main(args: Array<String>) {
    sum(1, 2) { x, y -> x + y }
}

3.4 Anonymous Functions

Anonymous functions are similar to lambdas but allow an explicit return type.

fun(x: Int, y: Int): Int = x + y

3.5 Higher‑Order Functions

Functions that take other functions as parameters or return them are called higher‑order functions.

fun sum(m: Int, n: Int, param: (Int, Int) -> Int): Int {
    return param.invoke(m, n)
}

3.6 Infix Notation

Functions marked with infix can be called using infix notation.

infix fun Int.sum(x: Int): Int { return this + x }

fun main(args: Array<String>) {
    1 sum 2 // equivalent to 1.sum(2)
}

3.7 Inline Functions

Marking a function with inline causes the compiler to inline the function body and any lambda arguments, eliminating the overhead of function objects and virtual calls.

inline fun show(str: String) { print(str) }

fun main(args: Array<String>) {
    print("start")
    show("123")
    print("end")
}

Without inline, the call to show remains a regular method call.

3.8 Standard Library Functions

Function

Definition

Purpose

run

public inline fun run (block: () -> R): R = block()

Executes the block and returns its last expression.

apply

public inline fun T.apply (block: T.() -> Unit): T { block(); return this }

Runs a block with this as the receiver and returns the object.

let

public inline fun T.let (block: (T) -> R): R = block(this)

Passes the object as an argument ( it) to the block and returns the block result.

also

public inline fun T.also (block: (T) -> Unit): T { block(this); return this }

Similar to let but returns the original object.

with

public inline fun with (receiver: T, block: T.() -> R): R = receiver.block()

Calls a block with the given receiver as this and returns the block result.

Summary

The article presents a practical overview of Kotlin fundamentals across three areas: basic syntax, class/object features, and function capabilities. Understanding functions—Kotlin's first‑class citizens—is especially important, as they enable concise and expressive code that can greatly improve development efficiency.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Mobile DevelopmentAndroidprogrammingKotlinKotlin Basics
Sohu Tech Products
Written by

Sohu Tech Products

A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.