0

Perhaps the hidden question is which structure to use for keys that have a sort of hierarchy (therefore my attempt in using classes and inner classes, so that a test on specific subsets is possible). I'm looking for a structure where I can add a new key to the appropriate place and having automaticallly this key in the appropriate keySet. Here my actual try: Now I work with keys as static final String and a corresponding keySet. I often need to test if a certain key is contained in the set of keys (public static final String) declared in some other class. Therefore I extend all classes with keys from a class Keys1 which has a method keySet() that gives the set of keys. That works fine.

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;
    }       
}

Now I try in vain to code a similar functionality in a class Keys2 where the keySet should also contain keys that are declared in inner classes of type 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
    "I often need to test..." maybe it's time to work on your design if that's something that comes up often? – Kayaman Mar 10 '17 at 09:35
  • Ah... the pungent aroma of an [XY Problem](http://xyproblem.info/) – shmosel Mar 10 '17 at 09:41
  • `f.get(f)` is clearly wrong. You are not reading a field of the class `java.lang.reflect.Field`. So either, you check wether it is a `static` field and use `f.get(null)` or you use `f.get(this)`, if it isn’t a `static` field. – Holger Mar 10 '17 at 15:25
  • @shmosel: point taken. Perhaps the hidden question is which structure to use for keys that have a sort of hierarchy (therefore my attempt in using classes and inner classes, so that a test on specific subsets is possible). I'll add thsi to the initial description of my problem. – Krümel Monster Mar 13 '17 at 08:34

3 Answers3

0

If I'm not mistaken at first you need to get declared constructor of inner class. Than invoke it with an instance of outer class as an argument.

briarheart
  • 1,906
  • 2
  • 19
  • 33
  • I'm using only static final. That should be accessible witout any instance but I didn't know how to do that. Holger had some good idea to solve that. – Krümel Monster Mar 13 '17 at 08:43
0

Make your inner class static or as already briarheart mentioned create nested instance through the instance of outer class (see Instantiating inner class).

Consider using enums instead of String constants.

You can use something like:

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;
    }  
}

This solution may be improved depending on your needs.

For example you can implement interface with the method

boolean contains(Enum e);

for each enum to check inclusion of arbitrary value in any enum and its nested enums.

Community
  • 1
  • 1
Andrey Koshelev
  • 211
  • 1
  • 4
  • I assume you tried to give me some hint how to solve the hidden question (see edited intro). Sorry, I didn't qutie understand the advantage of enums, enum interfaces and what do you meen by uniform processing of enum group? Seems there is something interesting to learn. I would be glad if you have the time to add further explanation. – Krümel Monster Mar 13 '17 at 08:51
0

Since you said, you are looking for public static final String fields only, you are doing unnecessary work. You are not filtering the fields to access static fields only, further, you are querying the field and checking the result’s type instead of checking the field’s type in the first place.

Also, you don’t need an object instance to retrieve a static field. If you write the code in a way that it operates on a Class, it can be used to process inner classes just as discovered, without instantiating them.

Since this procedure doesn’t need an object instance, there is also no reason to repeat that operation for every instance nor to store the result in an instance field. You only need to remember the result on a per-class basis and, thankfully, there is a class named ClassValue which provides this for free.

Putting it together, you can implement it as

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());
    }
}

The ClassValue takes care of the caching. When you call get, it checks whether there is already a computed value for the specified class, otherwise, it calls computeValue. The computeValue method in this solution utilizes this itself for processing the super class fields, so if you call it for different subclasses, they will share the result for the common base class instead of repeating the work.

The subclass doesn’t need to do anything here, the inherited keySet() method is sufficient, as it uses getClass(), which returns the actual class.

As shown in this ideone demo.


When you are running in a Java version before Java 7, you may use the following ersatz, which you should replace with the real thing as soon as you migrate to a newer Java version.

/**
 * TODO: replace with {@code java.lang.ClassValue<T>} when migrating to &gt;=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;
    }
}

The only change needed by the solution itself, is replacing the diamond operator use
new TreeSet<>() with the explicitly typed new TreeSet<String>(). Then, it should work in Java 6.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • I'm glad you took the time to give me some usefull hints what to look up and thanks for the code review! – Krümel Monster Mar 13 '17 at 08:25
  • I'm still on JAVA 6 (migration to JAVA 8 expected this summer - might be after JAVA 9 appears :-(). I could wait till then and use your proposal or find a substitute for ClassValue und computeValue. Have some idea? – Krümel Monster Mar 13 '17 at 09:19
  • You’re skipping Java 7? Why? Anyway, I added a stand-in for `ClassValue` that you can use until summer. – Holger Mar 13 '17 at 14:35
  • cool! Works fine. I would appreciate any comments on the following aspect: Since all keys are static final there might be a way to get a static access like in Keys1.keyset() – Krümel Monster Mar 14 '17 at 14:51
  • You can make a declaration like `public static TreeSet keySet() { return KEYS.get(Key1.class); }` to enable the use of `Keys1.keyset()`, but then you have to make a similar declaration in the subclasses (changing `Key1.class` to the appropriate type), as inheritance doesn’t work like with the instance method then. – Holger Mar 14 '17 at 14:58