Part of Github Copilot with Kotlin

Kotlin and Copilot: create a person class with a name

Sandy LaneSandy Lane

Video: Kotlin and Copilot: create a person class with a name by Taught by Celeste AI - AI Coding Coach

Take the quiz on the full lesson page
Test what you've read · interactive walkthrough

Kotlin: A Person Class With Just a Name

class Person(val name: String) { fun printName() = println("Name: $name") }. The simplest meaningful class. Property in the constructor; one method that uses it.

A minimalist version of episode 34. One property, one method. The "Hello, World!" of Kotlin classes.

The Copilot prompt

// A simple Person class with just a name property
class Person(val name: String) {

Copilot generates:

class Person(val name: String) {
  fun printName() {
    println("Name: $name")
  }
}

fun main() {
  val person = Person("Alice")
  person.printName()   // "Name: Alice"
}

Walkthrough

class Person(val name: String) — primary constructor takes name, makes it a public read-only property.

fun printName() reads name (no this. needed) and prints. Same shape as episode 34's greet(), just renamed.

When you only need data, use data class

If Person doesn't have any behavior beyond holding a name:

data class Person(val name: String)

val a = Person("Alice")
val b = Person("Alice")
println(a == b)         // true (structural equality)
println(a)              // "Person(name=Alice)"
val (name) = a          // destructuring; name = "Alice"

data class auto-generates: - equals / hashCode (structural) - toString (shows all properties) - copy(...) for instance with one field changed - componentN() for destructuring

For one-property records, this is overkill — but harmless and future-proof for when you add a second field.

Plain class vs data class

Feature class Person(val name: String) data class Person(val name: String)
Equality Reference (a == b only when same instance) Structural (a == b if same name)
Hashing Identity hash Hash of properties
toString Class name + memory address Shows properties
Destructure No (without componentN) Yes
Copy No (without copy method) Yes

For value-like types (records, immutable data), data class. For identity-bearing entities (User in a database with an ID), plain class.

Adding methods

class Person(val name: String) {
  fun printName() = println("Name: $name")
  fun describe() = "Person named $name"
  fun nameUpper() = name.uppercase()
}

Three methods. Each reads name. Adding more methods is the same pattern.

For a lot of methods, consider whether they all belong to the class or if some are better as extension functions or free utilities.

Properties with custom getters

For derived properties:

class Person(val name: String) {
  val firstLetter: Char
    get() = name.first()

  val displayName: String
    get() = name.replaceFirstChar { it.uppercaseChar() }
}

val p = Person("alice")
println(p.firstLetter)   // 'a'
println(p.displayName)   // "Alice"

val firstLetter: Char declares the property; get() = ... is the custom getter. No backing field — recomputed on each access.

For caching:

class Person(val name: String) {
  val displayName: String by lazy {
    name.replaceFirstChar { it.uppercaseChar() }
  }
}

by lazy { ... } computes once, caches. Useful for expensive derivations.

Equality and hashing

For data class, Kotlin generates structural equality:

data class Person(val name: String)
val a = Person("Alice")
val b = Person("Alice")
println(a == b)           // true
println(a.hashCode() == b.hashCode())   // true
println(a === b)          // false (different instances)

For plain class, you'd write them yourself:

class Person(val name: String) {
  override fun equals(other: Any?): Boolean {
    if (this === other) return true
    if (other !is Person) return false
    return name == other.name
  }
  override fun hashCode(): Int = name.hashCode()
  override fun toString(): String = "Person(name=$name)"
}

That's a lot of code for what data class does in one line.

In a list

val people = listOf(
  Person("Alice"),
  Person("Bob"),
  Person("Charlie"),
)

people.forEach { it.printName() }
// Name: Alice
// Name: Bob
// Name: Charlie

val names = people.map { it.name }   // ["Alice", "Bob", "Charlie"]

The Person class is just a value type — fits naturally into list operations.

Common mistakes

Forgetting val. class Person(name: String)name is a constructor parameter, not a property. Person("Alice").name errors.

Using var when val is enough. var makes the property mutable. For immutable records, always val.

Plain class for a record. Comparing two Persons with == returns false (different instances) — usually surprising. Use data class.

Skipping printName() thinking the constructor printed it. Constructor only sets the property. init { println(...) } would print on construction; printName() prints when called.

Confusing === and ==. === is reference equality (same instance). == is equals (structural for data class, reference for plain class).

What's next

Episode 36: List of months in 3-letter format. Generate ["JAN", "FEB", ...] from java.time.Month.

Recap

class Person(val name: String) { fun printName() = println("Name: $name") }. Primary-constructor val name makes it a public read-only property. data class Person(val name: String) adds equals/hashCode/copy/toString automatically. For derived properties, val x get() = ...; for cached, val x by lazy { ... }. Plain class for identity types; data class for value types.

Next episode: months in 3-letter format.

Ready? Take the quiz on the full lesson page →
Test what you've learned. Watch the lesson and try the interactive quiz on the same page.