【问题标题】:Limiting Keys in Type Safe Heterogenous Containers类型安全的异构容器中的限制键
【发布时间】:2014-01-29 06:12:57
【问题描述】:

我想使用 Joshua Bloch 的 Effective Java 中描述的泛型类型安全容器模式,但想通过使用枚举来限制可以用作键的类。下面是 Joshua 书中的代码。

public class Favorites {

  private Map<Class<?>, Object> favorites = new HashMap<Class<?>, Object>();

  public <T> void putFavorite(Class<T> type, T instance) {
    if (type == null)
      throw new NullPointerException("Type is null");
    favorites.put(type, instance);
  }

  public <T> T getFavorite(Class<T> type) {
    return type.cast(favorites.get(type));
  }

}

我想写一个类似的类,但限制键说“Dog.class”和“Cat.class”。理想情况下,可接受的键将由枚举来描述,并且“RestrictedFavorites”类会将枚举的成员作为键。我不确定我是否可以让编译器为我做所有这些事情(类型安全、枚举限制、通用性),但如果有人有建议,我会全力以赴。下面是尝试 V1,它使用运行时检查而不是编译时检查,并不完全令人满意。

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
 * Attempt V1 At a "RestrictedFavorites" class
 */
public class RestrictedFavorites {

  public static enum RestrictedKey {

    STRING(String.class),
    INTEGER(Integer.class);

    private static Set<Class<?>> classes;
    static {
      classes = new HashSet<>();
      for (RestrictedKey key: values()) {
        classes.add(key.getKlass());
      }
    }

    private final Class<?> klass;

    RestrictedKey(Class<?> klass) {
      this.klass = klass;
    }

    public Class<?> getKlass() {
      return klass;
    }

    public static boolean isValidClassKey(Class<?> klass) {
      return classes.contains(klass);
    }

  }

  private Map<Class<?>, Object> favorites =  new HashMap<Class<?>, Object>();

  //Ideally would use compile time checking
  public <T> void putFavorite(RestrictedKey key, T instance) {
    if (key == null) throw new NullPointerException("Type is null");
    if (!key.getKlass().equals(instance.getClass())) {
      throw new IllegalArgumentException(
          "The type of the key must match the type of the instance");
    }
    favorites.put(key.getKlass(), instance);
  }

  //Ideally would take a RestrictedKey as an argument
  public <T> T getFavorite(Class<T> key) {
    if (!RestrictedKey.isValidClassKey(key)) {
      throw new IllegalArgumentException(
          "The key must be a member of RestrictedKeys");
    }
    return key.cast(favorites.get(key));
  }

}

以下是一些单元测试,以验证我的班级是否大致按照我的意愿行事:

public class RestrictedFavoritesTest extends TestCase {

  public void testPutFavorite() {
    RestrictedFavorites myFavorites = new RestrictedFavorites();
    myFavorites.putFavorite(RestrictedKey.INTEGER, 1);
    myFavorites.putFavorite(RestrictedKey.STRING, "hey");
    int expectedInt = myFavorites.getFavorite(Integer.class);
    assertEquals(1, expectedInt);
    String expectedString = myFavorites.getFavorite(String.class);
    assertEquals("hey", expectedString);
  }

  public void testPutFavorite_wrongType() {
    RestrictedFavorites myFavorites = new RestrictedFavorites();
    try {
      myFavorites.putFavorite(RestrictedKey.INTEGER, "hey");
      fail();
    } catch (IllegalArgumentException expected) {}
  }

  public void testPutFavorite_wrongClass() {
    RestrictedFavorites myFavorites = new RestrictedFavorites();
    try {
      myFavorites.getFavorite(Boolean.class);
    } catch (IllegalArgumentException expected) {}
  }

}

【问题讨论】:

  • 使用enum 作为Map 中的键,并将Class 类型存储在enum 的属性中。
  • 我试过这种方法。我在我的问题中添加了一个示例,但它没有编译。如果我能解决问题,我会迭代并发布一个更好的版本。
  • 在您的getFavorite 中将返回类型转换为T。如果您想限制 Map 在没有一些公共父类的情况下可以保存的实例,您将不得不依赖运行时检查。您也可以将该方法放入您的 enum 中 - 它会稍微整理代码。
  • 是否可以选择让最喜欢的类实现标记接口?

标签: java generics enums


【解决方案1】:

回答(我自己的问题)。不要使用枚举。因为枚举不能是通用的。相反,创建一个类来表示受限键,并限制对构造函数的访问。将有效键枚举为字段。

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

public class RestrictedFavorites {

  private static final class RestrictedKey<T> {

    private final Class<T> type;

    private RestrictedKey(Class<T> type) {
      this.type = type;
    }

    private Class<T> getMyType() {
      return this.type;
    }
  }

  public static final RestrictedKey<String> STRING_KEY =
      new RestrictedKey<>(String.class);
  public static final RestrictedKey<Integer> INTEGER_KEY =
      new RestrictedKey<>(Integer.class);

  private final Map<RestrictedKey<?>, Object> favorites =
      new HashMap<RestrictedKey<?>, Object>();

  public <T> void putFavorite(RestrictedKey<T> key, T instance) {
    favorites.put(key, instance);
  }

  public <T> T getFavorite(RestrictedKey<T> key) {
    return key.getMyType().cast(favorites.get(key));
  }

}

还有单元测试:

public class RestrictedFavoritesTest extends TestCase {

  public void testPutFavorite() {
    RestrictedFavorites myFavorites = new RestrictedFavorites();
    myFavorites.putFavorite(RestrictedFavorites.STRING_KEY, "hey");
    myFavorites.putFavorite(RestrictedFavorites.INTEGER_KEY, 1);
    assertEquals(new Integer(1), myFavorites.getFavorite(RestrictedFavorites.INTEGER_KEY));
    assertEquals("hey", myFavorites.getFavorite(RestrictedFavorites.STRING_KEY));
  }

}

【讨论】:

  • 完成。感谢您的建议。
  • 这与简单地指定一组允许的Class&lt;?&gt;s 的静态(或可配置)集有何不同?您只是在 Class 对象中添加了一个包装器。我不明白。
  • @GiovanniBotta 他希望对putFavoritegetFavorite 的调用具有编译时安全性。
  • @GiovanniBotta 它是数据库客户端的一部分。键是从数据库返回的对象的类。实际的方法调用类似于 Subscription userSubscription = dbInterface.get(userId, RestrictedKeys.SUBSCRIPTION_KEY) 或 GeoLocation userGeoLocation = dbInterface.get(userId, RestrictedKeys.GEO_LOCATION)。数据库只能查询某些键,我想枚举它们。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多