Java classes with dynamic fields
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.
If you look at my type-safe object map, you’ll see it’s in fact possible to be dynamic and type safe — without casts and without one method per type. When that idea struck me, I was curious if there was an even better solution. Hence my question.
Yes, but each of the attributes with a TypedMapKey
To clarify: The user of the map doesn’t have to cast all the time plus code-completion will insert the correct type.
Sorry, you won’t convince me that this gives you typesafe dynamic fields w/o typecasts. The names of the fields have to be static, and the values are only typesafe because of the hidden typecasts. Its a sham.
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 list = new ArrayList (); map.set( KEY2, list ); List valueList = map.get( KEY2 ); // Even with generics assertEquals( list, valueList );
The trick here is the key which contains the type information:
TypedMapKey KEY1 = new TypedMapKey( "key1" ); TypedMapKey KEY2 = new TypedMapKey( "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.
Java классы с динамическими полями
Очевидным ответом является использование HashMap (или a LinkedHashMap , если вы заботитесь о порядке полей). Затем вы можете добавлять динамические поля с помощью методов get(String name) и set(String name, Object value) .
Этот код может быть реализован в общем базовом классе. Поскольку существует только несколько методов, это также просто использовать делегирование, если вам нужно расширить что-то еще.
Чтобы избежать проблемы с кастингом, вы можете использовать тип-безопасную карту объектов:
TypedMap map = new TypedMap(); String expected = "Hallo"; map.set( KEY1, expected ); String value = map.get( KEY1 ); // Look Ma, no cast! assertEquals( expected, value ); List list = new ArrayList (); map.set( KEY2, list ); List valueList = map.get( KEY2 ); // Even with generics assertEquals( list, valueList );
Трюк здесь — это ключ, который содержит информацию о типе:
TypedMapKey KEY1 = new TypedMapKey( "key1" ); TypedMapKey KEY2 = new TypedMapKey( "key2" );
Производительность будет в порядке.
Повторное использование полей осуществляется с использованием одного и того же типа значений или путем расширения класса ключей карты типа безопасных объектов с дополнительной функциональностью.
Вычисленные поля могут быть реализованы со второй картой, в которой хранятся экземпляры Future , которые выполняют расчет.
Так как все манипуляции происходят только в двух (или, по крайней мере, нескольких) методах, отправные сигналы просты и могут быть сделаны любым способом.
Чтобы реализовать автоматическую обработку родителя/дочернего элемента, установите прослушиватель сигналов на сигнал «установить родительский» дочернего элемента, а затем добавьте его в новый родительский элемент (и удалите его из старого, если необходимо).
Поскольку никакие рамки не используются и никаких трюков не требуется, полученный код должен быть довольно чистым и понятным. Не использовать String в качестве ключей имеет дополнительное преимущество, которое люди не будут засорять код строковыми литералами.
Динамическое добавление свойств в языке Java
Listener’ы и Interceptor’ы позволяют добавить в сущности дополнительные данные. В некоторых случаях их применение оправдано, но предпочитаю не засорять сущности уровня хранения структурами и данными, не имеющими к уровню хранения никакого отношения.
Mapping
Можно пронаследоваться от каждого класса, который требует расширения, и начинить его нужными данными уже на сервисном уровне. Это концептуально верный путь: слой хранения сохраняет чистоту, слой контроллеров не требует модификации. Однако, возникают проблемы с производительностью, ленивая загрузка перестает быть ленивой, т.к. маппер не знает, какие из полей нужны контроллеру, и вынужден перекладывать все. Управление маппером со стороны контроллера теоретически возможно, но это невозможно без модификации кода контроллера.
Dynamic proxy
package ru.bdm.reflection; //some imports omitted import static junit.framework.Assert.assertEquals; import static org.apache.commons.beanutils.PropertyUtils.getProperty; public class PropertyJoinerTest < public static class AnyType < public Object getAnyProperty() < return "anyPropertyValue"; >> @Test public void testWithPropertyExtractor() throws Exception < PropertyJoiner propertyJoiner = new PropertyJoiner(new PropertyExtractor() < @Override public Object get(Object o, String property) < return property + "Value"; >>, "first", "second"); AnyType src = new AnyType(); AnyType dst = propertyJoiner.joinProperties(src); assertEquals("firstValue", getProperty(dst, "first")); assertEquals("secondValue", getProperty(dst, "second")); assertEquals("anyPropertyValue", getProperty(dst, "anyProperty")); > >
Что под капотом?
public interface FirstHolder < Object getFirst(); >public interface SecondHolder
Динамически создается класс proxy, который наследует AnyType и реализует FirstHolder и SecondHolder .
Методы, определенные в AnyType , proxy перенаправляет к src , методы, определенные в FirstHolder и SecondHolder , перенаправляются в PropertyExtractor , который содержит логику вычисления добавочных свойств.
Таким образом мы получили возможность расширения представления, не меняя при этом код контроллеров и не засоряя сущности уровня хранения посторонними структурами и данными, не получая падения производительности из-за проблем с ленивой загрузкой.
Плата за это оказалась не очень велика: доступ к свойствам через прокси примерно в 150 раз медленнее, чем непосредственный. Это стоит учитывать при использовании инструмента.
Нагрузка нашего приложения была всего несколько запросов в секунду, за каждый запрос читалось максимум 50 сущностей (размер страницы), так что долей потерь в proxy можно было пренебречь.
Creating fields dynamically java
i was wondering if their is any way to dynamically create a static field for a class during run-time using reflection or a related API. If needed i can use the java native interface to accomplish this task if someone could tell me the appropriate steps. I don’t want to use any data structures such as a hash-map, as i am doing this completely for fun. Please don’t suggest using maps as i am not using this for any real program this is a completely theoretical situation. Thanks.
Doesn’t the static field belong to the class definition, and not to any given runtime object of the class type? Reflection is a runtime mechanism; I doubt what you’re proposing is possible. The closest thing I can think of is a Singleton, since a Singleton always refers to the same runtime instance.
The compiler pulls static final fields into referenced classes as hardcoded constants so if your field is static final the answer is a resounding No.
2 Answers 2
You could do this during class load time using bytecode manipulation.
This is a very complex solution though, so I’d consider other options.
It also does not help too much to have a new field that is not known at compile-time, because you cannot compile anything against it. If you are going to use reflection to access it, you might as well use a Map in the first place.
yes, take a look at bytecode manipulation libraries. But again, why? How are you going to access these fields? You cannot compile against fields that a compiler does not know about.
reasonable: In your case, I doubt that. You may want to add code/functionality dynamically, but adding fields at runtime defeats the purpose of a statically typed compiled language. Maybe you want to use Jython or Groovy or JRuby or something.
For a certain class of use cases, you could look into AOP / AspectJ. It’s basically a structured way of doing some kinds of metaprogramming in Java. (Including introducing entirely new members.) It’s at least marginally less crazy than direct bytecode manipulation!
Java doesn’t support metaprogramming or runtime programming in a way that is particularly nice or effective.
You could use a decorator pattern. You could pass the object that you want to add a static field to into a wrapper object. This wrapper would have the static field and the calls to the wrapper object would relate to the wrapped object.
If you could provide more details about the functionality you’re looking for I could try to provide a better solution. You might be better off looking into another language that does support runtime programming if you absolutely need it to be done in that way.