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.
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 defaultIn 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.
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.
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.
class Engine // TODO: restrict Engine to its own module with 'internal'
fun main() {
Engine()
println("engine ready")
} Show answer Hide answer
internal class Engineinternal 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.
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.
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.
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.
// 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.
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.sqrtThe 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.
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.
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: no — internal is visible only within the module that declares it (a compile unit / source set). Java in the same module: technically yes — internal 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