0

In a android lib, having a public class in java which has default package level constructors

public class UserInformationException extends Exception {

    private static final String ERROR_MESSAGE = "User information wrong!!";

    UserInformationException(String msg) {
        super(msg);
    }

    UserInformationException() {
        super(ERROR_MESSAGE);
    }

This class is visible from other app (or outside the package), but the other part cannot create instance of it.

After converting this class into kotlin (using the internal modifier to restrict to visible only to this module is fine here)

class UserInformationException internal constructor(msg: String = ERROR_MESSAGE) : Exception(msg) {
    companion object {
        private const val ERROR_MESSAGE = "User information wrong!!"
    }
}

Looks like the kotlin internal constructor become public and be able to create the instance from it. The decompiled java code here:

public final class UserInformationException extends Exception {
   private static final String ERROR_MESSAGE = "User information wrong!!";
   @NotNull
   public static final UserInformationException.Companion Companion = new UserInformationException.Companion((DefaultConstructorMarker)null);

   public UserInformationException(@NotNull String msg) {
      Intrinsics.checkNotNullParameter(msg, "msg");
      super(msg);
   }

   // $FF: synthetic method
   public UserInformationException(String var1, int var2, DefaultConstructorMarker var3) {
      if ((var2 & 1) != 0) {
         var1 = "User information wrong!!";
      }

      this(var1);
   }

   public UserInformationException() {
      this((String)null, 1, (DefaultConstructorMarker)null);
   }

   public static final class Companion {
      private Companion() {
      }

      // $FF: synthetic method
      public Companion(DefaultConstructorMarker $constructor_marker) {
         this();
      }
   }
   ... ...
}

Question: how to create class with (equivalent java package level visibility - the kotlin's internal to the module is fine) internal constructor only constructor?

lannyf
  • 9,865
  • 12
  • 70
  • 152
  • 1
    There is no "package" accessibility modifier in kotlin. – Sweeper Jun 07 '22 at 15:12
  • I just brow the java word "package" here, but the `internal` modifier is ok if it restricts the constructor only visible to this module (not the outside world). The problem is that seems now the `internal` constructor become public to all. – lannyf Jun 07 '22 at 15:19
  • Restricting access to only the module is exactly what `internal` does. Did you confuse "packages", which also exist in Kotlin, with "modules", which also exist in Java? – Sweeper Jun 07 '22 at 15:21
  • I know the `internal` has module level visibility. The problem is when using internal constructor, it becomes `public`. The class could be instantiated outside the module now. – lannyf Jun 07 '22 at 15:24
  • The problem here is that Kotlin doesn't have `package` visibility and Java doesn't have `internal` visibility. I believe you can't anyhow create a constructor with `package` visibility in Kotlin. This is one of the most requested features. – broot Jun 07 '22 at 15:25
  • Do you mean you want to restrict *Java code* from other modules from accessing the constructor? – Sweeper Jun 07 '22 at 15:26
  • Again, I am NOT trying to have a `package` level visibility constructor in kotlin. Restrict the constructor to have kotlin `internal` visibility level is fine here. The problem is when using 'internal constructor` here in kotlin, it still becomes public, so that outside the module it can still be instantiated - which is not what I want. – lannyf Jun 07 '22 at 15:29
  • @Sweeper, what I want is, ideally, with kotlin `internal constrctor`, this class can only be instantiated in this module. Any code out side this module (no mater it is from java code or kotlin code. This module is a lib) can only see this class but should NOT be able to instantiated. – lannyf Jun 07 '22 at 15:33
  • 1
    Java doesn't support such visibility. It just doesn't. You would have to use Java Modules. – broot Jun 07 '22 at 15:34
  • @broot, so what you are saying is that, the kotlin's `internal constructor` can NOT really do what the kotlin `internal` modifier claims to - to be only visible in the module? – lannyf Jun 07 '22 at 15:37
  • 1
    It can, but only for the other Kotlin code. There is no way for the Kotlin compiler to produce a bytecode that the Java compiler will recognize as "internal". It just doesn't understand such visibility. `internal` is `internal` for Kotlin users and `public` for Java users. – broot Jun 07 '22 at 15:40
  • where I tried with a kotlin client, now it can still instantiate the class there (outside this module). And looking at the decompiled code it seems they are changed the constructor to have plain public visibility - so that even if the client is a kotlin code this constructor is still public. (Before converting to kotlin, the java's package level constructor prevents it to instantiated from any client of it) – lannyf Jun 07 '22 at 15:46
  • Hmm... maybe I'm entirely wrong, but I believe it should not be possible to access it from another module from Kotlin. What exactly is your setup? Is this internal constructor inside a dependent library? And yes, the visibility is "plain public", because Kotlin compiler can't add new visibility modifiers to Java bytecode. I believe it adds this kind of data inside its `@Metadata` annotation. Maybe you use proguard or other tool that stripped these annotations? – broot Jun 07 '22 at 15:50
  • @broot, I take it back, that the test with kotlin client is within a unit test but it has same package name, and in other packack it cannot instantiate this class. But in java client it is always be able to. So if we have java client there is no way to restrict the class to be created there? – lannyf Jun 07 '22 at 15:54

1 Answers1

2

You may simply do

data class MyPublicClass internal constructor(val property: String)

Every one can see it but only the current module can construct it.

Kotlin can not create package private visibility code. This is a language design choise. There are also no annotations available to tell the compiler wich visibiltiy should be choosen for this use case.

wartoshika
  • 531
  • 3
  • 10
  • cant use `object` here. and as @broot said for java client the `internal` does not work – lannyf Jun 07 '22 at 16:28
  • In case this is a library, a java wrapper class may to the trick. – wartoshika Jun 07 '22 at 16:49
  • When you realy want to hide the constructor within kotlin, a private constructor would work when using reflection to create instances of that class. But this is more a hack than a solution – wartoshika Jun 07 '22 at 16:52