使用接口Class <t>作为获取具体实例值的键?

时间:2017-02-13 15:18:05

标签: java generics inheritance hashmap

我有以下测试用例无法从Map

中检索值
package tests;

import java.util.HashMap;
import java.util.Map;

public class ClassTest {

    interface A {}
    interface B extends A {}
    interface C extends A {}

    class D implements B {}
    class E implements C {}

    public ClassTest() {
        Map<Class<? extends A>, A> map = new HashMap<>();

        A d = new D();
        A e = new E();

        map.put(d.getClass(), d);
        map.put(e.getClass(), e);

        System.out.println(B.class.getSimpleName() + ": " + map.get(B.class));
        System.out.println(C.class.getSimpleName() + ": " + map.get(C.class));
    }

    public static void main(String[] args) {
        new ClassTest();
    }

}

预期输出为:

B: D
C: E

实际输出为:

B: null
C: null

根据我的理解,案例是&#34;预期&#34;失败因为B.class不等于D.class,即使D类是B接口的实现...所以map.get(...)无法找到关联键的实例值。 (纠正我,如果我错了。)上面的案例有希望显示意图和&#34;精神&#34;在我想要完成的事情背后。

是否有一个好的/优雅的替代方案可行,但也保留了我想要做的精神?

我目前正在更新代码,以替换正在使用的枚举类型&#39; open sets&#39; for Class<T>作为类型标记,有点类似于Effective Java,2nd Ed。,Item 29。

根据@CKing在评论中提出的要求,本书的部分内容引用了我的方法。

  

客户端在设置和获取收藏夹时会显示Class个对象。   这是API:

// Typesafe heterogeneous container pattern - API
public class Favorites {
    public <T> void putFavorite(Class<T> type, T instance);
    public <T> T getFavorite(Class<T> type);
}
     

这是一个练习Favorites类的示例程序,存储,   检索并打印收藏夹StringIntegerClass   实例:

// Typesafe heterogeneous container pattern - client
public static void main(String[] args) {
    Favorites f = new Favorites();

    f.putFavorite(String.class, "Java");
    f.putFavorite(Integer.class, 0xcafebabe);
    f.putFavorite(Class.class, Favorites.class);

    String favoriteString = f.getFavorite(String.class);
    int favoriteInteger = f.getFavorite(Integer.class);
    Class<?> favoriteClass = f.getFavorite(Class.class);
    System.out.printf("%s %x %s%n", favoriteString, favoriteInteger, favoriteClass.getName());
}
     

正如您所料,此程序会打印Java cafebabe Favorites

请理解我知道这本书的例子是有效的,因为它使用了值的特定具体类(例如String.class用于实际的String,而不是一些假设来自String等的派生类型。如上所述,这只是激发了我的方法,看看我的测试用例是否有效,现在我正在寻找一个解决方案或替代方案尊重&#34;精神&#34;我打算在测试用例上做些什么。

4 个答案:

答案 0 :(得分:0)

也许不那么优雅,但您可以使用反射来获取Key.class分配的所有值:

System.out.println(B.class.getSimpleName() + ": " + getMapEntries(map, B.class));
System.out.println(C.class.getSimpleName() + ": " + getMapEntries(map, C.class));

....

private <T extends A> List<T> getMapEntries(Map<Class<? extends A>, A> map, Class<T> clazz) {
    List<T> result = new ArrayList<>();
    for (Map.Entry<Class<? extends A>, A> entry : map.entrySet()) {
        if (clazz.isAssignableFrom(entry.getKey())) {
            result.add((T) entry.getValue());
        }
    }
    return result;
}

答案 1 :(得分:0)

如果希望接口类键映射到具体实例,则需要将这些键显式添加到地图中。

UIViewController

答案 2 :(得分:0)

您可以覆盖Map.put(),以便每当有人放置D.class时,都会将其键入B.class。像这样:

package com.matt.tester;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class Tester {

        interface A {}
        interface B extends A {}
        interface C extends A {}
        interface Q {};

        class D implements B, Q {}
        class E implements C, Q {}


        // New method to get the object's interface that derives from A.
        private Class<? extends A> getHierarchyClass(Class cls) {
            List<Class<?>> classList = Arrays.asList(cls.getInterfaces()).stream().filter(i -> A.class.isAssignableFrom(i)).collect(Collectors.toList());
            if ( classList.isEmpty() ) {
                return null;
            }
            return (Class<? extends A>) classList.get(0);

        }


        public Tester() {
            Map<Class<? extends A>, A> map = new HashMap<Class<? extends A>,A>() {
                @Override
                public A put(Class<? extends A> key, A value) {
                    // Whenever anyone puts a value in, actually put it in for whatever interface it implements that derives from A
                    Class<? extends A> newKey = getHierarchyClass(key);
                    if ( newKey != null ) {
                        return super.put(newKey, value);
                    }
                    return super.put(key, value);
                }
            };

            A d = new D();
            A e = new E();

            map.put(d.getClass(), d);
            map.put(e.getClass(), e);

            System.out.println(B.class.getSimpleName() + ": " + map.get(B.class));
            System.out.println(C.class.getSimpleName() + ": " + map.get(C.class));
        }



        public static void main(String[] args) {
            new Tester();
        }

}

输出:

B: com.matt.tester.Tester$D@404b9385
C: com.matt.tester.Tester$E@6d311334

答案 3 :(得分:0)

当然,以下不是一个优雅的解决方案,但它可以考虑你的简单&#34;类型层次结构。

它的作用是在值的类层次结构中为每个类填充一个条目。类。然后,您可以使用值实现的任何接口或类从地图中获取值。

如果多个值实现相同的类/接口,则会覆盖条目,但您的设计已经存在此类限制。

public ClassTest()
{
    final Map<Class<? extends A>, A> map = new HashMap<>();

    final A d = new D();
    final A e = new E();

    getSuperClassesAndInterfaces(A.class, d.getClass()).forEach(clazz -> map.put(clazz, d));
    getSuperClassesAndInterfaces(A.class, e.getClass()).forEach(clazz -> map.put(clazz, e));

    System.out.println(B.class.getSimpleName() + ": " + map.get(B.class));
    System.out.println(C.class.getSimpleName() + ": " + map.get(C.class));
    System.out.println(B.class.getSimpleName() + ": " + map.get(D.class));
    System.out.println(C.class.getSimpleName() + ": " + map.get(E.class));
}

/**
 * Gets all the super classes and interfaces of first argument that are subclasses of the second argument.
 * 
 * @param <T> Type of the limit class.
 * @param aClass Any class.
 * @param limit We do not want anything higher in the hierarchy than this class.
 * @return A collection of classes and interfaces that 'aClass' implements and that are subclasses of 'limit'.
 */
// Did not find any elegant way to achieve this, but it works.
public <T> Collection<Class<? extends T>> getSuperClassesAndInterfaces(final Class<T> aClass, final Class<? extends A> limit)
{
    final Collection<Class<? extends T>> result = new HashSet<>();
    // Classes
    Class<?> rawClass = limit;
    while (rawClass != null && aClass.isAssignableFrom(rawClass))
    {
        result.add((Class<T>) rawClass);
        rawClass = rawClass.getSuperclass();
    }
    // Interfaces
    final Class<?>[] interfaces = limit.getInterfaces();
    for (final Class<?> itf : interfaces)
    {
        if (aClass.isAssignableFrom(itf))
        {
            result.add((Class<T>) itf);
        }
    }
    return result;
}

输出:

B: tests.ClassTest$D@41629346
C: tests.ClassTest$E@404b9385
B: tests.ClassTest$D@41629346
C: tests.ClassTest$E@404b9385

坦率地说,这段代码非常糟糕。你真的需要这样的功能吗?