Как поместить все строковые константы, объявленные в классе, и его внутренние классы в один набор ключей
Возможно, скрытый вопрос заключается в том, какую структуру использовать для ключей, которые имеют своего рода иерархию (поэтому я пытался использовать классы и внутренние классы, чтобы можно было проверить определенные подмножества). Я ищу структуру, где я могу добавить новый ключ в соответствующее место и автоматически иметь этот ключ в соответствующем наборе ключей. Вот моя реальная попытка: теперь я работаю с ключами как static final String и соответствующим keySet. Мне часто нужно проверить, содержится ли определенный ключ в наборе ключей (public static final String), объявленном в каком-то другом классе. Поэтому я расширяю все классы ключами из класса Keys1, у которого есть метод keySet(), который предоставляет набор ключей. Это отлично работает.
public class Keys1
{
private TreeSet<String> m_keySet = new TreeSet<String>();
public Keys1()
{
initKeySet();
}
private void initKeySet()
{
Field[] felder = this.getClass().getFields();
for (Field f : felder)
{
if (Modifier.isFinal(f.getModifiers()))
{
try
{
if (f.get(f) instanceof String)
{
m_keySet.add(f.get(f).toString());
}
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
}
public TreeSet<String> keySet()
{
return m_keySet;
}
}
Теперь я тщетно пытаюсь закодировать аналогичную функциональность в классе Keys2, где набор ключей должен также содержать ключи, которые объявлены во внутренних классах типа Keys2.
public class Keys2 extends Keys1
{
@Override
protected void initKeySet()
{
super.initKeySet();
Class<?>[] innerClasses = this.getClass().getDeclaredClasses();
for (Class<?> innerClass : innerClasses )
{
if (innerClass.getClass().isInstance(Keys1.class))
{
Keys1 newKeys;
try
{
newKeys = (Keys1) innerClass.newInstance(); // Doesn't work
keySet().addAll(newKeys.keySet());
}
catch (InstantiationException e)
{
e.printStackTrace();
}
catch (IllegalAccessException e)
{
e.printStackTrace();
}
}
}
}
}
3 ответа
Сделайте ваш внутренний класс статичным или, как уже упоминалось, создайте вложенный экземпляр через экземпляр внешнего класса (см. Создание внутреннего класса).
Попробуйте использовать перечисления вместо строковых констант.
Вы можете использовать что-то вроде:
public enum A {
A1,
A2;
public static enum B {
B1,
B2
}
public static enum C {
C1,
C2
}
static Set<Enum> allValues() {
Set<Enum> allValues = new HashSet<>();
allValues.addAll(Arrays.asList(A.values()));
allValues.addAll(Arrays.asList(A.B.values()));
allValues.addAll(Arrays.asList(A.C.values()));
return allValues;
}
}
Это решение может быть улучшено в зависимости от ваших потребностей.
Например, вы можете реализовать интерфейс с методом
boolean contains(Enum e);
для каждого перечисления проверять включение произвольного значения в любое перечисление и его вложенные перечисления.
Если я сначала не ошибаюсь, вам нужно получить объявленный конструктор внутреннего класса. Чем вызвать его с экземпляром внешнего класса в качестве аргумента.
Так как вы сказали, вы ищете public static final String
только поля, вы делаете ненужную работу. Вы не фильтруете поля для доступа static
только для полей, кроме того, вы запрашиваете поле и проверяете тип результата вместо проверки типа поля в первую очередь.
Кроме того, вам не нужен экземпляр объекта для получения static
поле. Если вы пишете код таким образом, что он работает на Class
, он может быть использован для обработки внутренних классов так же, как обнаружен, без их создания.
Поскольку эта процедура не нуждается в экземпляре объекта, также нет причин повторять эту операцию для каждого экземпляра или сохранять результат в поле экземпляра. Вам нужно помнить результат только для каждого класса, и, к счастью, есть класс с именем ClassValue
который предоставляет это бесплатно.
Собрав его вместе, вы можете реализовать его как
public class Keys1 {
static final ClassValue<TreeSet<String>> KEYS = new ClassValue<TreeSet<String>>() {
@Override protected TreeSet<String> computeValue(Class<?> type) {
final int desired=Modifier.PUBLIC|Modifier.STATIC|Modifier.FINAL;
Field[] fields=type.getDeclaredFields();
TreeSet<String> set = new TreeSet<>();
for(Field f: fields) {
if((f.getModifiers()&desired)==desired && f.getType()==String.class) try {
set.add((String)f.get(null));
} catch(IllegalAccessException ex) {
throw new AssertionError(ex);
}
}
for(Class<?> inner: type.getDeclaredClasses()) {
set.addAll(get(inner));
}
type = type.getSuperclass();
if(type != null && type != Object.class) set.addAll(get(type));
return set;
}
};
public TreeSet<String> keySet() {
return KEYS.get(getClass());
}
}
ClassValue
заботится о кешировании. Когда вы звоните get
, он проверяет, существует ли уже вычисленное значение для указанного класса, в противном случае он вызывает computeValue
, computeValue
метод в этом решении использует это само для обработки полей суперкласса, поэтому, если вы вызовете его для разных подклассов, они будут делить результат для общего базового класса вместо повторения работы.
Подкласс не должен ничего делать здесь, унаследованный keySet()
Метод достаточно, так как он использует getClass()
, который возвращает фактический класс.
Как показано в этой демонстрации ideone.
Когда вы работаете в версии Java до Java 7, вы можете использовать следующий ersatz, который вы должны заменить реальным, как только вы перейдете на более новую версию Java.
/**
* TODO: replace with {@code java.lang.ClassValue<T>} when migrating to >=7.
*/
abstract class ClassValue<T> {
private final ConcurrentHashMap<Class<?>,T> cache=new ConcurrentHashMap<Class<?>,T>();
protected abstract T computeValue(Class<?> type);
public final T get(Class<?> key) {
T previous = cache.get(key);
if(previous != null) return previous;
T computed = computeValue(key);
previous = cache.putIfAbsent(key, computed);
return previous!=null? previous: computed;
}
}
Единственное изменение, необходимое для самого решения, это замена использования оператора бриллианта. new TreeSet<>()
с явно напечатанным new TreeSet<String>()
, Затем он должен работать в Java 6.