Kotlin object keyword – Singletons, companion objects and object expressions

Use of the object keyword in Kotlin.

fun main() {
    println(CompanyCommunications.getTagLine()) // Call the class name as there is only one instance
    println(CompanyCommunications.getCopyRightLine())

    SomeOtherClass().printPrivateVar() // Will allow us access to privateVar despite being private, thanks to Companion object
    SomeClass2.SomeCompanion.accessPrivateVar() // Using companion name, but again redundant

    val someClass = SomeClass4.justAssign("this is the string as is")
    val someClass2 = SomeClass4.upperOrLowerCase("this is the string as is", false)
    println(someClass.someString)
    println(someClass2.someString)
//    val someClass3 = SomeClass4("This will not work") // Constructor is private so cannot instantiate directly

    var thisIsMutable = 45

    wantsSomeInterface(object: SomeInterface { // Uses object to create an instance of the someInterface interface - NOT a Singleton. Is generated and then destroyed. Can use multiple interfaces, unlike Java
        override fun mustImplement(num: Int): String {
            return "This is from mustImplement: ${num * 100 + thisIsMutable}" // This expression can access variables from outside it (val or var, doesn't need to be final), unlike Java
        }
    })
}

// 'object' keyword's 3 uses: Singletons, companion objects, object expressions

// Singletons
// //In Java, you would create a private constructor that allows only one instance which other classes can access
// In Kotlin, there can only be one instance of an 'object' class, so we use that:
object CompanyCommunications { // No class keyword needed. Also no constructors as instance is created instantaneously
    val currentYear = Year.now().value
    fun getTagLine() = "Our company rocks!"
    fun getCopyRightLine() = "Copyright \u00A9 $currentYear Our Company. All rights reserved."
}

// Companion Objects
// No static keyword in Kotlin - instead we can use top-level functions and properties, and Object declarations (above)
// But what if you want an equivalent to Java static (see Java objects below), where you can access functions and properties without creating an instance of the class?
// We create companion objects inside the class which can be accessed without having to generate an instance of the class
class SomeClass {

    companion object {
        private var privateVar = 6 // Because is private no getter/setter generated

        fun accessPrivateVar() {
            println("I'm accessing privateVar: $privateVar")
        }
    }

}

class SomeClass2 {

    companion object SomeCompanion { // You can also name your companion object, but little use
        private var privateVar = 6

        fun accessPrivateVar() {
            println("I'm accessing privateVar: $privateVar")
        }
    }

}

class SomeOtherClass() {
    fun printPrivateVar () {
        println(SomeClass.accessPrivateVar()) // Could use SomeClass.Companion.accessPrivateVar() but redundant
    }
}

class SomeClass3 {

    val someString: String

    constructor(str: String) { // First secondary constructor
        someString = str
    }

    constructor(str: String, lowerCase: Boolean) { // Second secondary constructor
        if (lowerCase) {
            someString = str.toLowerCase()
        } else {
            someString = str.toUpperCase()
        }
    }
// But instead of doing it this way we can create a companion class that returns an instance of the class (see below)
}

class SomeClass4 private constructor(val someString: String){ // We want to implement the Factory pattern here. This class should generate instances of the class - nobody should be able to do it directly as in SomeClass3, so primary is made private

    companion object{
        private var privateVar = 6

        fun accessPrivateVar() {
            println("I'm accessing privateVar in SomeClass4: $privateVar")
        }

        fun justAssign(str: String) = SomeClass4(str) // Can only call the private constructor from inside class therefore controlling how class is instantiated...
        fun upperOrLowerCase(str: String, lowerCase: Boolean): SomeClass4 { // so to get an instance of this class you have to go through either the justAssign or upperOrLowerCase function
            if (lowerCase) {
                return SomeClass4(str.toLowerCase())
            } else {
                return SomeClass4(str.toUpperCase())
            }
        }

    }

}

// Object expressions - equivalent to an anonymous object in Java
interface SomeInterface {
    fun mustImplement(num: Int): String
}

fun wantsSomeInterface(si: SomeInterface) {
    println("Printing from wantsSomeInterface - ${si.mustImplement(22)}")
}

Companion object equivalent in Java

public class SomeClass {

    private static int privateVar = 6;

    public static void main(String[] args) {
        new SomeOtherClass().someOtherMethod();
    }

    public static void accessPrivateVar() {
        System.out.println("I'm accessing privateVar: " + privateVar);
    }
}
public class SomeOtherClass {

    public void someOtherMethod() {
        SomeClass.accessPrivateVar();
    }
}