Kotlin Visibility and Scope - mariamaged/Java-Android-Kotlin GitHub Wiki

Kotlin - Visibility and Scope

  1. Visibility: What is allowed to see these classes, functions, and properties?
  2. Scope: Where so 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.
    • 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.
  • This is why code like this works.
  • Both Foo and something are public, and so code that lives outside of Foo can:
    • Reference Foo (for creating instances).
    • something() (for calling that function on instances of Foo).
class Foo {
	fun something() {
		println("Hello, world")
	}
}

val foo = Foo()
foo.something()
foo.something()
foo.something()

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.
  • The will not compile, as something() is not visible to code outside of Foo:
     

enter image description here

  • 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()
	}
}

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 of them as 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()
	}
}

val notFoo = Bar()
notFoo.somethingElse()
  • This works, because somethingElse() is public by default, and Bar can access the 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: (1) properties and (2) functions.

  • In 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.
    • It is also transitive.
  • In case of 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.
       
  • 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 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.

Scope

  • Visibility controls who can see what.
  • Scope controls where things exist.
    • This has an indirect impact on visibility: if something does not exist, it cannot be accessed by anything.
       
  • However, scope more directly controls things like garbage collection, as scope helps to determine the lifetime of objects.

Global

  • Top-level definitions in a Kotlin source file are global in scope.
  • Immutable globals - classes, val, etc. - will exist for the life of the process that is executing the Kotlin code.
  • Mutable globals - such as var:
    • Have references that will live forever.
    • But the objects that those references reference might come and go as the value of that reference changes.
Example Code
class Foo {
	fun something() = println("Hello, world!")
}

val foo = Foo()
foo.something()

var bar = Foo()
bar.something()

bar = Foo()
bar.something()
  • Both the Foo class and the foo property are global and immutable.
  • bar is considered to be global.
  • The difference is that bar is mutable.
  • So while anything that bar points to cannot be garbage collected, once bar is pointing to something else (e.g., another instance of Foo), whatever bar used to point to can be garbage collected.

Instance

  • Properties defined inside of a class have instance scopes.
  • Each instance of the enclosing class will have its own set of instance properties, independent of any other instance of that same class.
class Foo(val count: Int) 

val instanceOne = Foo(1)
val instanceTwo = Foo(2)
println(instanceOne.count) 
println(instanceTwo.count)

Local

Any val or var within something smaller than a class is some form of local variable.

Local to Functions

A val or var directly inside of a function body is local to that function:

  • Here, length is considered to be local to the something() function.
  • You would be unable to access length from other functions on Foo, let alone from outside Foo.
class Foo {
	fun something(message: String) {
		val length = message.length

		println("'$message' has $length characters")
	}
}

Foo().something()

Local to Blocks/Lamdas

A property defined within a block or lamda (i.e., chunk of code in {}) will be local to that block or lamda.

  • Now, message is local not local to something(), but instead is local to the lamda expression supplied to forEach().
  • You cannot access message from elsewhere within something(), let alone from other methods on Foo or from outside Foo.
class Foo {
	fun something(messages: List<String>) {
		messages.forEach{ message ->
				val length = message.length
				println("'$message' has $length charcters")
        }
	}
}

Foo().something(listOf("this", "is", "a", "test"))
⚠️ **GitHub.com Fallback** ⚠️