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:
- Visibility: What is allowed to see these classes, functions, and properties?
- 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:
-
private
top-level things are visible to other things in the same Kotlin source file -
private
top-level things are inaccessible to other things in other Kotlin files
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:
- Anything left as public (the default) is accessible by other files
- Anything
protected
is accessible only by subclasses implemented in other files - Anything marked as
private
is inaccessible in other files
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.