Visibility Workbook

Ten short exercises on Kotlin visibility — the default, private and protected, the module-scoped internal, private setters, private constructors, and file organization.

Practice problems for Kotlin Has a Visibility Level Java Doesn’t: internal. Each takes a minute or two. Most are about declarations and modifiers, so they stay attempt-then-reveal: fill in the TODO, press Run to compile on JetBrains’ Kotlin server, then click Show answer to check yourself. One (the Email factory) auto-checks: implement the function and press Run to watch hidden tests go green or red. Nothing here is a trick question, just direct practice of the syntax from the lesson.

defaults and the basics

1. Declare without a modifier

Declare a top-level class and a top-level function with no visibility modifier, and note (as comments) what visibility they get.

Try it — edit, then press Run class Service // TODO: note its default visibility in this comment fun helper() {} // TODO: note its default visibility here too fun main() { Service() helper() println("declared") }
Show answer Hide answer
class Service      // public by default
fun helper() { }   // public by default

In Kotlin the default is public, so you annotate to restrict, not to expose.

2. Hide a member

Give Account a balance property that is visible only inside the class.

Try it — edit, then press Run class Account { var balance = 0 // TODO: make this visible only inside the class fun deposit(n: Int) { balance += n } fun current() = balance } fun main() { val account = Account() account.deposit(100) println(account.current()) // want 100 }
Show answer Hide answer
class Account {
    private var balance = 0
}

3. Read-public, write-private

Declare Counter whose value can be read anywhere but written only inside the class.

Try it — edit, then press Run class Counter { var value: Int = 0 // TODO: make the setter private so value is read-only from outside fun increment() { value++ } } fun main() { val counter = Counter() counter.increment() counter.increment() println(counter.value) // want 2 }
Show answer Hide answer
class Counter {
    var value: Int = 0
        private set
}

internal and protected

4. Module-only

Declare a class Engine that is usable anywhere in the same module but invisible to code that depends on the module.

Try it — edit, then press Run class Engine // TODO: restrict Engine to its own module with 'internal' fun main() { Engine() println("engine ready") }
Show answer Hide answer
internal class Engine

internal is the level Java has no equivalent for.

5. For subclasses only

Give an abstract class Repository a connect() method visible to subclasses but not to outside callers.

Try it — edit, then press Run abstract class Repository { abstract fun connect(): String // TODO: make connect() protected (subclasses only) fun open() = connect() } class SqlRepository : Repository() { override fun connect() = "connected" } fun main() { println(SqlRepository().open()) // want connected }
Show answer Hide answer
abstract class Repository {
    protected abstract fun connect(): Connection
}

Unlike Java, Kotlin’s protected does not also leak to the package.

6. File-private

Declare a top-level helper function that’s visible only within its own file.

Try it — edit, then press Run fun audit() { println("audited") } // TODO: restrict to this file with 'private' fun main() { audit() }
Show answer Hide answer
private fun audit() { }

For a top-level declaration, private means file scope.

constructors and structure

7. A private constructor

Give class Email a private constructor and a companion of(raw: String): Email? factory that returns null for input without an @. Implement of.

Implement it, then press Run to check import org.junit.Test import org.junit.Assert class Test { @Test fun of() { Assert.assertEquals("keeps an address containing '@'", "[email protected]", Email.of("[email protected]")?.address) Assert.assertEquals("returns null when there is no '@'", null, Email.of("nope")?.address) } } //sampleStart class Email private constructor(val address: String) { companion object { fun of(raw: String): Email? = Email(raw) // TODO: return null when raw has no '@' } } //sampleEnd
Show answer Hide answer
class Email private constructor(val address: String) {
    companion object {
        fun of(raw: String): Email? =
            if ("@" in raw) Email(raw) else null
    }
}

8. Many declarations, one file

Write a single file Geometry.kt holding two classes and a top-level function — something Java’s one-class-per-file rule forbids.

Try it — edit, then press Run // Kotlin allows many top-level declarations in one file. class Point(val x: Int, val y: Int) class Line(val from: Point, val to: Point) fun distance(a: Point, b: Point): Double = 0.0 // TODO: add one more top-level declaration above (another class or function). fun main() { val line = Line(Point(0, 0), Point(3, 4)) println(distance(line.from, line.to)) }
Show answer Hide answer
// Geometry.kt
class Point(val x: Int, val y: Int)
class Line(val from: Point, val to: Point)
fun distance(a: Point, b: Point): Double = 0.0

9. Package and import

Write the top two lines of a file: declare it in package geometry, then import kotlin.math.sqrt.

Try it — edit, then press Run import kotlin.math.sqrt fun main() { // In a real file the very first line would be a package declaration, e.g. // package geometry // TODO: write that package line at the top of a file in your head — here we // only show the import, since the playground runs in the default package. println(sqrt(9.0)) // want 3.0 }
Show answer Hide answer
package geometry
import kotlin.math.sqrt

The package need not mirror the directory path (matching them is just convention).

10. Three levels in one type

Declare class Token(val id: String) with a public id, an internal fun validate(), and a private secret field.

Try it — edit, then press Run class Token(val id: String) { val secret = id.hashCode() // TODO: make secret private fun validate() = secret != 0 // TODO: make validate() internal } fun main() { val token = Token("abc") println(token.id) // id is public — always visible println(token.validate()) // want true }
Show answer Hide answer
class Token(val id: String) {
    private val secret = id.hashCode()
    internal fun validate() = secret != 0
}

Each member exposes the smallest surface that still works.


Going deeper: internal, and private set

12. Read anywhere, write only here

Give Counter a value that outside code can read but not assign, with an inc() that bumps it.

Implement it, then press Run to check import org.junit.Test import org.junit.Assert class Test { @Test fun counter() { val c = Counter() c.inc(); c.inc() Assert.assertEquals(2, c.value) } } //sampleStart class Counter { var value: Int = 0 // TODO: make the setter private fun inc() { value++ } } //sampleEnd
Show answer Hide answer
class Counter {
    var value: Int = 0
        private set
    fun inc() { value++ }
}

The setter carries its own, stricter visibility — public to read, private to write — with no separate backing field needed.

13. What does internal really mean?

You mark a class internal in a library. Two questions: can code in a different Gradle module use it, and can Java code in the same module reach it? Reveal to check.

Show answer Hide answer

A different module: nointernal is visible only within the module that declares it (a compile unit / source set). Java in the same module: technically yesinternal has no JVM equivalent, so it compiles to public with a mangled name (like doWork$mymodule). The module boundary is enforced within Kotlin; at the JVM edge it’s only a name-mangling fiction. There is no package-private level in Kotlin at all.


Back to the lesson, Kotlin Has a Visibility Level Java Doesn’t, or on to the next one: delegation.

Comments