Kotlin and Copilot: Add a method to a class
Video: Kotlin and Copilot: Add a method to a class by Taught by Celeste AI - AI Coding Coach
Kotlin: Add a Method to a Class
class Person(val name: String) { fun greet() { println("Hello, $name!") } }. The class body holds methods; the constructor parameters become properties when prefixedval.
A small OOP puzzle: define a class with a property and a method, then call it. Demos Kotlin's primary-constructor + class-body shape.
The Copilot prompt
// A Person class with a greet() method
class Person(val name: String) {
Copilot generates:
class Person(val name: String) {
fun greet() {
println("Hello, my name is $name!")
}
}
fun main() {
val person = Person("Alice")
person.greet() // "Hello, my name is Alice!"
}
Walkthrough
1. Primary constructor. class Person(val name: String) — the parameter list is the primary constructor. val name makes it a property; without val/var, the parameter is only available inside the constructor (not as a property).
2. Method body. fun greet() is a regular function inside the class. It can read properties (name) without explicit this.
3. Instantiation. Person("Alice") calls the primary constructor. No new keyword in Kotlin.
4. Method call. person.greet() — same as any other function call, on the instance.
Class essentials
class Person(
val name: String, // public read-only property
var age: Int, // public mutable property
private val id: Int, // private read-only property
) {
fun greet() = println("Hello, $name!")
fun haveBirthday() { age++ }
fun describe() = "$name (age $age, id $id)"
}
val alice = Person("Alice", 30, 1)
alice.haveBirthday()
println(alice.age) // 31
println(alice.describe()) // "Alice (age 31, id 1)"
// alice.id → compile error, id is private
val for read-only, var for mutable. private/protected/internal/public for visibility (default is public).
Adding a method
To "add a method," put a function in the class body:
class Person(val name: String) {
fun greet() { println("Hello, $name!") }
fun shout() { println("HEY, ${name.uppercase()}!") }
fun whisper() { println("(psst, $name)") }
}
Three methods. Each can read properties; each is callable on instances.
Methods vs extension functions
You can also add methods outside the class via extensions:
class Person(val name: String)
fun Person.greet() {
println("Hello, $name!")
}
val alice = Person("Alice")
alice.greet() // works, looks like a member method
Difference:
- Member function — defined inside the class. Has access to
privatemembers. Can be overridden in subclasses. - Extension function — defined outside. Static dispatch (no override). Can't access privates.
For data class and library-like types, members are preferred. For adding utility functions to types you don't own, extensions are the only option.
Single-expression methods
class Person(val name: String) {
fun greet() = "Hello, $name!"
fun isAdult(age: Int) = age >= 18
}
fun name(args) = expression is the single-expression form — no braces, no explicit return. Idiomatic for one-liners.
init blocks
For initialization logic that runs when the constructor is called:
class Person(val name: String, val age: Int) {
init {
require(age >= 0) { "Age must be non-negative" }
require(name.isNotBlank()) { "Name must not be blank" }
}
init {
println("Created Person: $name")
}
}
init { ... } blocks run in declaration order during construction. Multiple init blocks are allowed.
Companion object for static members
class Person(val name: String) {
companion object {
const val DEFAULT_GREETING = "Hello"
fun fromString(s: String): Person {
val (name) = s.split(",")
return Person(name.trim())
}
}
}
println(Person.DEFAULT_GREETING)
val p = Person.fromString("Alice, hi")
companion object holds class-level (static-like) members. Person.DEFAULT_GREETING and Person.fromString(...) work without an instance.
data class for value types
If your class is mostly data (with equals, hashCode, toString, copy), use data class:
data class Person(val name: String, val age: Int)
val a = Person("Alice", 30)
val b = Person("Alice", 30)
println(a == b) // true (data class equality is structural)
val older = a.copy(age = 31) // new instance with age changed
println(a) // Person(name=Alice, age=30)
Auto-generates:
- equals/hashCode based on properties.
- toString showing all properties.
- copy(...) for creating modified copies.
- componentN() for destructuring (val (name, age) = person).
For most "record" types, data class is the right choice.
Common mistakes
Forgetting val/var on primary constructor params. Without it, the parameter isn't a property — person.name errors. With val, it's a public read-only property.
Using new. No new in Kotlin. Just Person(...).
Public-by-default surprise. Kotlin's default visibility is public. Java's is package-private. If you migrate Java code, you may unintentionally expose more.
Mixing class and data class. data class adds equals/hashCode/etc. — the difference is significant for collections (HashMap keys, etc.). Use data class for value types; plain class for entities with identity.
init order confusion. init blocks run in source order, interleaved with property initializers. val a = ...; init { ... }; val b = ... runs property a init, then the init block, then property b. Be careful with cross-references.
What's next
Episode 35: Create a Person class with just a name. A minimalist version — single property, no methods.
Recap
class Person(val name: String) { fun greet() = "Hello, $name!" }. Primary-constructor params with val/var become properties. Methods inside class body can read those properties. Single-expression methods: fun name(args) = expression. init { } blocks for construction logic. companion object for static-like members. data class for value types — auto-generates equals/hashCode/copy/toString.
Next episode: minimal Person class.