6

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.

JorgeAmVF
  • 1,660
  • 3
  • 21
  • 32
  • Just a note: Kotlin differentiates between "inner" and "nested" classes. Your `Inner` class is actually a nested class. An actual inner class is defined with the `inner` keyword. The difference is that an inner class carries a reference to an object of the outer class. You can read more about it here: https://kotlinlang.org/docs/reference/nested-classes.html – marstran Jan 16 '19 at 19:29

3 Answers3

7

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()
Roland
  • 22,259
  • 4
  • 57
  • 84
  • good idea! +1 the only drawback is that you first need to define properties / functions in the interface and then have to override it in InnerImpl which is a little redundant. – Willi Mentzel Jan 16 '19 at 21:49
  • yes, that's true... but I think it's common to use an `interface` for such constructs... – Roland Jan 17 '19 at 10:40
4

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.

Salem
  • 13,516
  • 4
  • 51
  • 70
0

You could make make the constructor protected. This way you only expose it to subclasses, in this case PrivateClass. Then you will create an instance of PrivateClass or null but return it as InnerClass?.

object InnerFactory {

    fun createInnerObject(startDate: Date? = null, endDate: Date? = null): Inner? {
        // return null here if conditions are not met
        return PrivateClass(startDate, endDate)
    }

    open class Inner protected constructor(val startDate: Date?, val endDate: Date?) {
        fun getTimeDifference(): Long? { /* ... */ }
    }

    private class PrivateClass(startDate: Date?, endDate: Date?): Inner(startDate, endDate)
}
Willi Mentzel
  • 27,862
  • 20
  • 113
  • 121
  • Note that you do not hide that constructor from `Inner` then... you can still call something like `val myFakedInner = object : Inner(null, null) {}` which will then construct a valid `Inner`-object which reuses the functionality you wanted to hide in the first place (if I understood that requirement correctly)... nonetheless depending on the use-case (and if holding that `val`s is ok) that's a valid variant too... you may however want to set the `val`s to `private` at least... – Roland Jan 17 '19 at 10:45
  • @Roland sure you can inherit from Inner. I even said that in my answer "This way you only expose it to subclasses...", but you cannot instantiate it directly. That's the weakness of my approach. :D – Willi Mentzel Jan 17 '19 at 18:20
  • true. due to "in this case `PrivateClass`" I just thought I will bring that up so that that isn't misinterpreted.. – Roland Jan 17 '19 at 18:33
  • @Roland sure, thing :) – Willi Mentzel Jan 17 '19 at 18:37