Visibility and Scope

Now that we are starting to define classes with properties and functions, we need to start thinking about “who can see what” — what particular aspects of our code are accessible by other aspects of our code. Principally, that amounts to two things:

  1. Visibility: What is allowed to see these classes, functions, and properties?
  2. Scope: Where do these classes, functions, and properties actually exist?

Visibility

Many programming languages have visibility options, to control what objects can reference functions and properties from other objects. In both Java and Ruby, for example, you can have public, private, and protected methods. Kotlin has those too, though Kotlin adheres a bit closer to Java’s definition of those terms than Ruby’s. Kotlin also has an internal visibility option that is a little strange.

Default = Public

In some languages, everything is public by default. Any object can reference public things. In Java, though, everything is “package-private” by default, meaning that it is public to things in the same Java package, but private to things in other packages.

Kotlin takes the public-by-default route. You will not see a public keyword used in typical Kotlin. Everything that is defined by default is visible to anything else. This is why code like this — seen in the chapter on classes — actually works:

class Foo {
  fun something() {
    println("Hello, world!")
  }
}

fun main() {
  val foo = Foo()

  foo.something()
}

Both Foo and something are public, and so code that lives outside of Foo can reference Foo (for creating instances) and something() (for calling that function on instances of Foo).

Private

private, by contrast, limits access to instances of its class, and nothing else.

class Foo {
  private fun something() {
    println("Hello, world!")
  }
}

val foo = Foo()

foo.something()

This is the same as the previous snippet, except that something() is marked as private. This will not compile, as something() is not visible to code outside of Foo:

error: cannot access 'something': it is private in 'Foo'
foo.something()

Private properties and functions can be referenced by other properties and functions within the same class:

class Foo {
  private fun something() {
    println("Hello, world!")
  }

  fun somethingElse() {
    something()
  }
}

fun main() {
  val foo = Foo()

  foo.somethingElse()
}

This works, because we are calling somethingElse(), which is public (by default). While code outside of Foo cannot call something(), somethingElse() can call something(), as both something() and somethingElse() are part of Foo.

Protected

The protected visibility modifier works a lot like private, but it also allows subclasses to access the property or function:

open class Foo {
  protected fun something() {
    println("Hello, world!")
  }
}

class Bar : Foo() {
  fun somethingElse() {
    something()
  }
}

fun main() {
  val notFoo = Bar()

  notFoo.somethingElse()
}

This works, because somethingElse() is public (by default), and Bar can access the protected function inside of Foo. However, code outside of Foo and Bar cannot access that protected function, and so this fails:

open class Foo {
  protected fun something() {
    println("Hello, world!")
  }
}

class Bar : Foo() {
  fun somethingElse() {
    something()
  }
}


val notFoo = Bar()

notFoo.something()

What About Top-Level Functions and Properties?

So far, we have looked at how private and protected work with aspects of classes: properties and functions.

In the case of protected, that is the only place you can use that particular visibility. protected is defined as “accessible by this class and its subclasses”, so it only makes sense in the context of classes.

private, though, is not just available for aspects of classes. Classes themselves can be private. Properties and functions defined outside of classes can also be marked as private. In these cases, the determination of what can and cannot access the private items is based on the file:

In a REPL scenario, everything in the REPL’s editor is in a single file, and so private has little meaning. private has a lot more meaning in a traditional software project, where you might have dozens or hundreds of source files. There, you can use private for access control:

What About Package-Private?

The closest thing that Java has to this sort of private-to-the-file rule is its default “package-private” visibility. By default, any class, method, or field is visible to other code in the same Java package, as determined by the package statement. Code in other packages cannot access that package-private code.

Kotlin does not offer package-private visibility. While packages can be useful for code organization, and may be important when interoperating with Java code, Kotlin does not use them to control visibility.

What About Internal?

There is a fourth visibility option in Kotlin, called internal.

For most simple projects, internal is useless and is equivalent to being public.

Where internal comes into play is when your project is divided into a collection of some type of “modules”. For example, in an Android project, by default you have a single module (app), but you can elect to define additional modules. The precise definition of “module” depends entirely on where the Kotlin code is being used (e.g., an Android project) and is not defined by the language itself.

What is defined is what internal means: anything marked as internal is visible to code in the same module but is not visible to code in other modules.

This allows you to better define the API that a module exposes to other modules. Anything that you mark as internal is visible within your module, so you can use it without issue, but other modules should not be able to access it. Only the public and protected things will be visible to other modules (and protected is visible only to subclasses).

So, if you plan on publishing some form of Kotlin library, internal will be important. On a large project that you have divided into modules, internal can be useful. For small single-module projects, internal is pointless. And for REPLs, internal is just silly.


Prev Table of Contents Next

This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.