Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create an inner class where only the outer class can access the constructor, but the rest is visible everywhere?

Tags:

kotlin

I originally wanted to create a class that can abort instantiation in constructor, but according to this link I should instead use a Factory class. But now I want to prevent anyone except the factory class from creating an object of class "Inner" while giving access to the methods of the inner class to everyone.

I have already tried this answer.

import java.util.Date

object InnerFactory {

    class Inner private constructor(startDate: Date? = null, endDate: Date? = null) {
        fun getTimeDifference(): Long? {
            //calculates time difference but doesn't matter to this example
        }
    }

    fun createInnerObject(startDate: Date? = null, endDate: Date? = null): Inner? {
        if (startDate != null && endDate != null && !endDate.after(startDate)) {
            return null
        }

        return Inner(startDate, endDate)
    }

}

I would use it like the following:

val date1 = Date(1547600000)
val date2 = Date(1547600600)
val inner = InnerFactory.createInnerObject(date1, date2) //should return an instance
val invalidInner = InnerFactory.createInnerObject(date2, date1) //should not return an instance because the "endDate" is before "startDate"
val difference = inner?.getTimeDifference()

It says "cannot access '<init>': it is private in 'Inner'" when hovering over my usage of the constructor in the "createInnerObject" function.

like image 276
AndroidKotlinNoob Avatar asked Jan 16 '19 16:01

AndroidKotlinNoob


2 Answers

What you could do:

  • introduce an interface Inner with all the necessary functions that should be exposed
  • make all the class(es) private and implement that interface

Sample:

object InnerFactory {

  interface Inner {
    fun getTimeDifference(): Long?
  }

  private class InnerImpl(startDate: Date? = null, endDate: Date? = null) : Inner {
    override fun getTimeDifference(): Long? = TODO("some implementation")
  }


  fun createInnerObject(startDate: Date? = null, endDate: Date? = null): Inner? {
    if (startDate != null && endDate != null && !endDate.after(startDate)) {
      return null
    }
    return InnerImpl(startDate, endDate) // InnerImpl accessible from here but not from outside of InnerFactory...
  }
}

Now you can't access InnerImpl from outside anymore, but still have all the necessary functions available:

// the following all work as it deals with the interface
val inner = InnerFactory.createInnerObject(date1, date2) //should return an instance
val invalidInner = InnerFactory.createInnerObject(date2, date1) //should not return an instance because the "endDate" is before "startDate"
val difference = inner?.getTimeDifference()

// the following will not compile:
InnerImpl()
like image 113
Roland Avatar answered Nov 20 '22 02:11

Roland


Unfortunately, private members of Kotlin inner classes are not accessible from the outer instance:

private means visible inside this class only
Kotlin reference / Visibility modifiers

However, Java is not this restrictive with its visibility modifiers:

access is permitted if and only if it occurs within the body of the top level type (§7.6) that encloses the declaration of the member or constructor.
Java Language Specification / §6 Names / §6.6 Access Control / §6.6.1 Determining Accessibility

This is one of the only (annoying) cases I have found where Kotlin's rules make a common Java pattern impossible.

The only workarounds (if you want to keep your current structure) would be to rewrite this class in Java, or to expose this constructor with a less restrictive visibility (e.g. internal.)

There was a discussion about this on the Kotlin forums - it seems that this is a JVM limitation, and that it only works in Java because the compiler generates appropriate synthetic accessors.

like image 26
Salem Avatar answered Nov 20 '22 00:11

Salem