1

I know that an instance of a class with a private constructor can be created using reflection but is there any way to make sure that the instance of the class can only be created within the same class using its own private constructor?

Let's take an example of a class Test, with a private constructor.

import java.lang.reflect.Constructor;

   import java.lang.reflect.InvocationTargetException;

   class Test   
   {

      private Test()  //private constructor
      {
      } 
   }

  public class Sample{

      public static void main(String args[]) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException
     {

       Class c=Class.forName("Test"); //specify class name in quotes

       //----Accessing private constructor
       Constructor con=c.getDeclaredConstructor();
       con.setAccessible(true);     
       Object obj=con.newInstance();
   }   
} 

My question is - is there any way to ensure that the instance of the class can only be created within the same class and not from outside using reflection or any other method?

Aman Singh Rajpoot
  • 1,451
  • 6
  • 26
  • 4
    check in which class the constructor is being called, throw an Exception if it's another class. But this really seems like overkill – Stultuske Apr 11 '22 at 08:11
  • 1
    https://stackoverflow.com/questions/6994393/singleton-how-to-stop-create-instance-via-reflection Might Help – seenukarthi Apr 11 '22 at 08:13
  • 1
    I knew an object can be created using reflection but the interviewer asked me to make sure the object should not be created from any other class. I felt like he was really stretching and said I don't know, but never thought to add the validation that you mentioned . @Stultuske – Aman Singh Rajpoot Apr 11 '22 at 08:16
  • 1
    This worked ```class Test { private Test() throws IllegalAccessException //private constructor { if (new Exception().getStackTrace()[1].getClassName() != this.getClass().getName()) { throw new IllegalAccessException(); } } }``` – Aman Singh Rajpoot Apr 11 '22 at 08:33

2 Answers2

3

There are several ways to prevent the creation - but it is hard to tell which one is appropriate for your use-case:

  1. Throw an exception in the constructor

    You can either unconditionally throw an exception - making it (near) impossible to instantiate an instance - or only throw under certain conditions.

    Some conditions can be:

    • Inspecting the caller - using StackWalker for example.
    • Passing a "secret" passphrase to the constructor. JMH does this for example.
  2. Use Java Modules.

    As other modules can't deeply reflect into other named modules, Constructor.setAccessible will not work on your class outside of your own module.
    Of course this restriction doesn't apply to your own module - but you should be able to control your own module ;).

  3. Install a SecurityManager.

    Prevents Constructor.setAccessible from returning successfully.
    But the security manager is deprecated for removal, so I can't recommend it's use.

Note: Most of those solutions can be circumvented in some way. And it is sometimes possible to add additional defenses against that. But at the end, it'll become a game of cat and mouse.

Johannes Kuhn
  • 14,778
  • 4
  • 49
  • 73
  • 1
    Thanks for the descriptive answer, I do not have any use case. The question was asked in a technical discussion, but do we really need this much restriction on a class in real-world application development? and as you mentioned all those solutions can be circumvented, is it worth it to do all these overhead? – Aman Singh Rajpoot Apr 11 '22 at 11:44
  • 1
    The somewhat easiest solution would be to use modules. In the end, the question is how much effort do you spend to prevent your users from shooting themself into their foot. Kids should not be able to get hold of a gun - so the measures necessary depend on the your users. – Johannes Kuhn Apr 11 '22 at 13:03
  • 2
    Exactly. This is not the right place to implement security, so this kind of protection is only against wrong uses of the class, to make clear to any other developers that if they insist on circumventing this restriction, there will be no support of any kind. – Holger Apr 12 '22 at 10:37
2

One way you already mentioned in comments by using Exception & another way to do this is using Thread.currentThread()

 package app.test;
    public class Test19 {
    ..
        private Test19() {
    
            if (Thread.currentThread().getStackTrace()[1].getClassName() == "app.test.Test19") {
                // initialize member fields etc.
            } else {
                throw new IllegalAccessException();
            }
        }
     }
    
Ashish Patil
  • 4,428
  • 1
  • 15
  • 36