Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Kotlin using apply in companion object throws an unexpected error

Tags:

kotlin

Let's say I'd want to instantiate an object of class A by copying values from class B which is a common practice when, for example, mapping DTO's. To accomplish this in Java or Groovy I'd create a static method on the appropriate DTO with the signature of fromB(A a) and then copy values either with a.val = b.val... in Java or using a.with { val = b.val... } in Groovy.

In Kotlin I've noticed that instance.apply{} is very similar to Groovy's with in that it allows me to directly access the object variables without constantly refering to the object itself since the reference seems to be implied within the closure.

However I've ran into a weird and unexpected error when using apply within companion objects. If I use A().apply {} inside a function of A's companion object I get an error Expression is inaccessible from a nested class 'Companion', use 'inner' keyword to make the class inner Which is weird since I'm calling apply directly on an instance of an object and would thus expect that I should always be able to access it's public properties. Not to mention that it seems like companion objects cannot be set to be inner thus the suggestion in the error message isn't all too helpful.

Here's the full example code:

fun main(args: Array<String>) {
    val b = B("Hello", "World")
    val a = A.fromB(b)

    print("$a.value1 $a.value2")
}


class A() {
    var value1: String? = null
    var value2: String? = null

    companion object {
        //This fails with "Expression is inaccessible from a nested class 'Companion', use 'inner' keyword to make the class inner"
        fun fromB(b: B): A {
            return A().apply {
                value1 = b.value3
                value2 = b.value4
            }
        }
    }
}

class B(val value3: String, val value4: String) {}

//This works
fun bToA(b: B): A {
    return A().apply {
                value1 = b.value3
                value2 = b.value4
            }
}

What is going on here? What am I doing wrong?

like image 247
MrPlow Avatar asked Dec 08 '15 19:12

MrPlow


2 Answers

This looks like a bug to me. Probably something to do with inline functions (e.g. apply) and companion objects. I suggest searching the JetBrains Bug & Issue Tracker and if you don't find something similar to this create a new issue.

In the meantime I see some alternatives:

  1. Use this (not ideal):

    fun fromB(b: B): A {
        return A().apply {
            this.value1 = b.value3
            this.value2 = b.value4
        }
    }
    
  2. Move value1 and value2 to A's primary constructor and change fromB(B) to use named arguments (this will still let you define defaults, skip properties when copying, etc.):

    class A(var value1: String? = null, var value2: String? = null) {
        companion object {
            fun fromB(b: B): A {
                return A(
                        value1 = b.value3,
                        value2 = b.value4
                )
            }
        }
    }
    

    UPDATE: In addition to the above you can use b with with:

    fun fromB(b: B) = with(b) {
        A(
                value1 = value3,
                value2 = value4
        )
    }
    
like image 164
mfulton26 Avatar answered Oct 19 '22 15:10

mfulton26


@MrPlow

I think this is more straightforward way to do, what you want:

fun B.toA(): A {
    val self = this;
    return A().apply {
        value1 = self.value3
        value2 = self.value4
    }
}

Compare with your example:

val b = B("Hello", "World")

val a = A.fromB(b)

// vs 

val a = b.toA();
like image 32
Ruslan Avatar answered Oct 19 '22 16:10

Ruslan