我有以下测试用例无法从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
类的示例程序,存储, 检索并打印收藏夹String
,Integer
和Class
实例:// 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;我打算在测试用例上做些什么。
答案 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
坦率地说,这段代码非常糟糕。你真的需要这样的功能吗?