Java classes with dynamic fields

2019-03-16 09:01发布

问题:

I'm looking for clever ways to build dynamic Java classes, that is classes where you can add/remove fields at runtime. Usage scenario: I have an editor where users should be able to add fields to the model at runtime or maybe even create the whole model at runtime.

Some design goals:

  • Type safe without casts if possible for custom code that works on the dynamic fields (that code would come from plugins which extend the model in unforeseen ways).
  • Good performance (can you beat HashMap? Maybe use an array and assign indexes to the fields during setup?)
  • Field "reuse" (i.e. if you use the same type of field in several places, it should be possible to define it once and then reuse it).
  • Calculated fields which depend on the value of other fields
  • Signals should be sent when fields change value (no necessarily via the Beans API)
  • "Automatic" parent child relations (when you add a child to a parent, then the parent pointer in the child should be set for "free").
  • Easy to understand
  • Easy to use

Note that this is a "think outside the circle" question. I'll post an example below to get you in the mood :-)

回答1:

Type safe without casts if possible for custom code that works on the dynamic fields (that code would come from plugins which extend the model in unforeseen ways)

AFAIK, this is not possible. You can only get type-safety without type casts if you use static typing. Static typing means method signatures (in classes or interfaces) that are known at compile time.

The best you can do is have an interface with a bunch of methods like String getStringValue(String field), int getIntValue(String field) and so on. And of course you can only do that for a predetermined set of types. Any field whose type is not in that set will require a typecast.



回答2:

The obvious answer is to use a HashMap (or a LinkedHashMap if you care for the order of fields). Then, you can add dynamic fields via a get(String name) and a set(String name, Object value) method.

This code can be implemented in a common base class. Since there are only a few methods, it's also simple to use delegation if you need to extend something else.

To avoid the casting issue, you can use a type-safe object map:

    TypedMap map = new TypedMap();

    String expected = "Hallo";
    map.set( KEY1, expected );
    String value = map.get( KEY1 ); // Look Ma, no cast!
    assertEquals( expected, value );

    List<String> list = new ArrayList<String> ();
    map.set( KEY2, list );
    List<String> valueList = map.get( KEY2 ); // Even with generics
    assertEquals( list, valueList );

The trick here is the key which contains the type information:

TypedMapKey<String> KEY1 = new TypedMapKey<String>( "key1" );
TypedMapKey<List<String>> KEY2 = new TypedMapKey<List<String>>( "key2" );

The performance will be OK.

Field reuse is by using the same value type or by extending the key class of the type-safe object map with additional functionality.

Calculated fields could be implemented with a second map that stores Future instances which do the calculation.

Since all the manipulation happens in just two (or at least a few) methods, sending signals is simple and can be done any way you like.

To implement automatic parent/child handling, install a signal listener on the "set parent" signal of the child and then add the child to the new parent (and remove it from the old one if necessary).

Since no framework is used and no tricks are necessary, the resulting code should be pretty clean and easy to understand. Not using String as keys has the additional benefit that people won't litter the code with string literals.



回答3:

So basically you're trying to create a new kind of object model with more dynamic properties, a bit like a dynamic language?

Might be worth looking at the source code for Rhino (i.e. Javascript implemented in Java), which faces a similar challenge of implementing a dynamic type system in Java.

Off the top of my head, I suspect you will find that internal HashMaps ultimately work best for your purposes.

I wrote a little game (Tyrant - GPL source available) using a similar sort of dynamic object model featuring HashMaps, it worked great and performance was not an issue. I used a few tricks in the get and set methods to allow dynamic property modifiers, I'm sure you could do the same kind of thing to implement your signals and parent/child relations etc.

[EDIT] See the source of BaseObject how it is implemented.



回答4:

You can use the bytecode manipulation libraries for it. Shortcoming of this approach is that you need to do create own classloader to load changes in classes dynamically.



回答5:

I do almost the same, it's pure Java solution:

  1. Users generate their own models, which are stored as JAXB schema.
  2. Schema is compiled in Java classes on the fly and stored in user jars
  3. All classes are forced to extend one "root" class, where you could put every extra functionality you want.
  4. Appropriate classloaders are implemented with "model change" listeners.

Speaking of performance (which is important in my case), you can hardly beat this solution. Reusability is the same of XML document.