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?
CodePudding user response:
There are several ways to prevent the creation - but it is hard to tell which one is appropriate for your use-case:
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.
- Inspecting the caller - using
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 ;).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.
CodePudding user response:
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();
}
}
}