Search code examples
javareflectionencapsulationapi-design

Can I provide an interface to allow another class to let me access its private variables?


First, a clarification: By "interface", I mean any point of interaction between components, not necessarily the "abstract type" kind of interface. (Although if I can use such an interface to do this, great.)

I'm writing a class which is designed to be extended. The problem domain is global optimization, so any end-user-programmer who subclasses my class (I'll call them Steve) will be providing their own problem and algorithm to solve it; I just provide the framework (networking, user interface, etc.). Steve's program might require any number of configuration settings; I want my code to be able to get those settings from the user the same way it gets its own settings (be that from a GUI control panel, an XML configuration file, or whatever). Steve should not have to duplicate any of my code, nor should he have to break Java's type safety or encapsulation model.

Ideally, Steve would be able to write something along these lines:

public class SteveClass extends MyFrameworkClass {

    @Configurable
    private int foo;

    @Configurable
    private String bar;

    private boolean baz;

    // implementations of my abstract methods, etc.

}

Configurable is an annotation that I would provide, that indicates that the annotated variable is a configuration setting that I should get from the user. As you can see, I also want Steve to be able to declare other instance variables for his own internal use, that I never touch.

I was going to implement this by including a method in MyFrameworkClass that would iterate through getClass().getDeclaredFields() and identify the fields that have Configurable annotations. It would then writes the user input to those fields. It doesn't have to look exactly like this, with annotations and all, but you get the idea.

The problem with this, of course, is that foo and bar are private and code in MyFrameworkClass can't write to them, even reflectively. It wouldn't help if they were package-private, since Steve's code won't be in the same package as mine, and I'd rather not require him to make them public, or to add any other interface that would allow anyone but me to access the fields.

Is there any way to do what I want, either by fiddling with SecurityManager or doing something completely different?


Solution

  • You can use Class.getDeclaredFields to get all of the class's declared fields (including non-public ones), and then Field.setAccessible(true) to gain access to it.

    Field foo = HasPrivate.class.getDeclaredField("foo");
    foo.setAccessible(true);
    SteveClass steve = new SteveClass();
    System.out.println(steve); // "0" -- I set up SteveClass.toString() to just print foo
    foo.setInt(hp, 1234);
    System.out.println(steve); // "1234"
    

    But that means you're depending on the SecurityManager to allow you to do that. It does by default, but that may not work for all users. If that's a prohibitive requirement, there are other, less convenient but potentially more robust options (like requiring that the constructor take a Configuration object, which the user can then get values from).