992 단어
5 분
Kotlin basic concepts based on Java
2025-04-17

Before I begin developing a chat server, I decided to study Kotlin. Since the Hero Tech Course also uses Kotlin, I thought it would be a good time to study and organize my understanding together.

To get a sense of Kotlin’s design philosophy, I looked through some books. Kotlin is a multi-paradigm language. It supports object-oriented and functional programming, and apparently two more paradigms as well. Since I come from a Java background, I’m more familiar with OOP and still getting used to Kotlin, so I’ll focus on Kotlin from an object-oriented programming (OOP) perspective.

Object State and Behavior#

In OOP, the core concept is an object, and that object’s state and behavior.

Java#

Let’s take a look at how state and behavior are expressed in Java.

public class Person {
    private final String name;

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void introduce() {
        System.out.println("Hi, my name is " + name);
    }
}

In the Person class above, the name field represents the object’s state. It’s declared private final, so it can’t be directly accessed or changed from outside the class — only read. This means the value is initialized during construction and cannot be changed afterward.

The constructor Person(String name) is used to initialize the name when the object is created. In other words, this.name = name assigns the externally provided value to the object’s internal state.

Kotlin#

Now let’s see how Kotlin expresses the same idea. Here’s the equivalent of the Java code above:

class Person(private val name: String) {
    fun introduce() {
        println("Hi, my name is $name")
    }
}

In Kotlin, you don’t need to explicitly define a getName method — when you declare a property, Kotlin automatically generates the getter/setter under the hood.

Declaring and Calling Functions: Java vs Kotlin#

Here’s how you’d use the Person class in Java:

public class Demo {
	public static void main(String[] args) {
		Person person = new Person("Bob");
		System.out.println(person.getName());
	}
}

You create an object with the new keyword and call its method.

In Kotlin:

fun main() {
	val person = Person("Bob")
	println(person.name)
}

Even though we didn’t define a getName() function, Kotlin allows direct property access. This feels more concise and expressive than Java.

But why does Kotlin do it this way? The benefit becomes clearer when we look at custom getters.

Imagine the Person class has a birth year and we want to calculate age dynamically rather than storing and updating it manually.

class Person(
	private val name: String, 
	private val birthYear: Int) {

	fun introduce() {
        println("Hi, my name is $name.")
    }
    
    fun getAge(): Int {
	    return LocalDate.now().year - birthYear
    }
}

In Java, it would be common to store age or calculate it in the constructor. But that would “freeze” the state of the object at the time it’s created.

Using the Kotlin code above:

fun main() {
	val person = Person("Bob", 1999)
	println(person.name)
	println(person.getAge())
}

At first glance, this seems fine — but is age really a method? Isn’t it part of the object’s state? Storing it as a variable might be misleading because age changes over time.

Instead, Kotlin lets you define custom getters to calculate such values dynamically while keeping the API intuitive.

class Person(
	private val name: String,
	private val birthYear: Int) {
	
	val age: Int
	    get() = LocalDate.now().year - birthYear

	fun introduce() {
        println("Hi, my name is $name and I'm $age years old.")
    }
}

Now in the main function:

fun main() {
	val person = Person("Bob", 1999)
	println(person.name)
	println(person.age)
}

This way, the age still behaves like a property even though it’s dynamically calculated, which improves clarity and correctness.

Validating Constructor Arguments in Kotlin#

In Java, constructors are often the place to validate inputs and prevent invalid objects from being created. But in Kotlin, at first glance it’s unclear where to place such validation.

Kotlin provides the init block, along with validation functions like require, check, and assert.

class Person(
    val name: String,
    val birthYear: Int
) {
    init {
        require(name.isNotBlank()) { "Name must not be blank" }
        require(birthYear in 1900..LocalDate.now().year) { "Invalid birth year" }
    }

    val age: Int
        get() = LocalDate.now().year - birthYear
}
  • require() throws IllegalArgumentException
  • check() throws IllegalStateException
  • assert() throws AssertionError (enabled only with JVM -ea flag)
FunctionPurposeException Thrown
require()Validating inputIllegalArgumentException
check()Validating stateIllegalStateException
assert()Debug assertionsAssertionError

Reusing and Extending Objects#

In OOP, we often need to reuse and extend objects. This is essentially what inheritance is about. In both Java and Kotlin, only single inheritance is allowed — each class can only extend one superclass. In Java, Object is the implicit superclass; in Kotlin, it’s Any.

Inheritance in Java#

public class Animal {
    public void sound() {
        System.out.println("Some generic animal sound");
    }
}

public class Dog extends Animal {
    @Override
    public void sound() {
        System.out.println("Bark!");
    }
}

The Dog class extends Animal and overrides the sound() method, demonstrating code reuse and behavioral extension.

Inheritance in Kotlin#

In Kotlin, classes are final by default, so you need to mark them as open to allow inheritance.

open class Animal {
    open fun sound() {
        println("Some generic animal sound")
    }
}

class Dog : Animal() {
    override fun sound() {
        println("Bark!")
    }
}

Kotlin uses open to declare overridable members and override instead of Java’s @Override annotation.

Abstraction of State and Behavior#

Abstraction allows us to define what an object can do while hiding how it does it. This is often done using interfaces or abstract classes.

Abstraction in Java#

public interface Animal {
    void sound();
}

public class Dog implements Animal {
    @Override
    public void sound() {
        System.out.println("Bark!");
    }
}

public class Cat implements Animal {
    @Override
    public void sound() {
        System.out.println("Meow!");
    }
}

This structure lets us use the Animal interface as a general type without knowing the exact implementation.

Abstraction in Kotlin#

interface Animal {
    fun sound()
}

class Dog : Animal {
    override fun sound() {
        println("Bark!")
    }
}

class Cat : Animal {
    override fun sound() {
        println("Meow!")
    }
}

Kotlin also supports default method implementations inside interfaces, similar to Java 8+.

interface Animal {
    fun sound() {
        println("Some generic sound")
    }
}

So far, we’ve looked at Kotlin through an OOP lens. Since Kotlin preserves many concepts from other familiar languages, it’s relatively easy to learn — especially when you already understand the principles of object-oriented programming.

Kotlin basic concepts based on Java
https://blog-full-of-desire-v3.vercel.app/posts/kotlin-basic-concepts/
저자
SpeculatingWook
게시일
2025-04-17
라이선스
CC BY-NC-SA 4.0