如何将在类中声明的所有String常量及其内部类放在单个keySet中

时间:2017-03-10 09:33:35

标签: java constructor initialization inner-classes keyset

也许隐藏的问题是哪个结构用于具有某种层次结构的键(因此我尝试使用类和内部类,以便可以对特定子集进行测试)。我正在寻找一种结构,我可以在适当的位置添加新密钥,并在相应的密钥集中自动使用此密钥。在这里我的实际尝试: 现在我使用key作为静态final String和相应的keySet。 我经常需要测试某个键是否包含在某个其他类中声明的键集(public static final String)中。 因此,我使用Keys1类中的键扩展所有类,其中key1具有提供键集的方法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中编写类似的功能,其中keySet还应该包含在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 个答案:

答案 0 :(得分:0)

如果我一开始没有弄错,你需要获得内部类的声明构造函数。比调用外层类的实例作为参数。

答案 1 :(得分:0)

使你的内部类静态或者已经提到briarheart通过外部类的实例创建嵌套实例(参见Instantiating inner class)。

考虑使用枚举而不是String常量。

您可以使用以下内容:

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

为每个枚举检查在任何枚举中包含任意值 及其嵌套的枚举。

答案 2 :(得分:0)

既然你说过,你只是在寻找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 demo所示。

当您在Java 7之前运行Java版本时,您可以使用以下ersatz,一旦迁移到较新的Java版本,就应该将其替换为真实版本。

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

解决方案本身所需的唯一变化是取代钻石操作员使用
明确键入new TreeSet<>()的{​​{1}}。然后,它应该在Java 6中工作。