java 具有泛型优势的具有多种值类型的映射

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10429230/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-31 01:00:26  来源:igfitidea点击:

Map with multiple value types with advantages of generics

javagenericsmapcastingcompiler-errors

提问by amaidment

I want to create a map that will provide the benefits of generics, whilst supporting multiple different types of values. I consider the following to be the two key advantages of generic collections:

我想创建一个可以提供泛型优势的地图,同时支持多种不同类型的值。我认为以下是泛型集合的两个主要优点:

  • compile time warnings on putting wrong things into the collection
  • no need to cast when getting things out of collections
  • 将错误的东西放入集合的编译时警告
  • 从集合中取出东西时无需强制转换

So what I want is a map:

所以我想要的是一张地图:

  • which supports multiple value objects,
  • checks values put into the map (preferably at compile-time)
  • knows what object values are when getting from the map.
  • 支持多个值对象,
  • 检查放入映射中的值(最好在编译时)
  • 知道从地图获取时的对象值是什么。

The base case, using generics, is:

使用泛型的基本情况是:

Map<MyKey, Object> map = new HashMap<MyKey, Object>();
// No type checking on put();
map.put(MyKey.A, "A");  
map.put(MyKey.B, 10);
// Need to cast from get();
Object a = map.get(MyKey.A); 
String aStr = (String) map.get(MyKey.A);

I've found a way to resolve the second issue, by creating an AbstractKey, which is generified by the class of values associated with this key:

我找到了一种解决第二个问题的方法,即创建一个 AbstractKey,它由与该键关联的值类泛型化:

public interface AbstractKey<K> {
}
public enum StringKey implements AbstractKey<String>{
  A,B;  
}
public enum IntegerKey implements AbstractKey<Integer>{
  C,D;
}

I can then create a TypedMap, and override the put() and get() methods:

然后我可以创建一个 TypedMap,并覆盖 put() 和 get() 方法:

public class TypedMap extends HashMap<AbstractKey, Object> {
  public <K> K put(AbstractKey<K> key, K value) {
    return (K) super.put(key, value);
  }
  public <K> K get(AbstractKey<K> key){
    return (K) super.get(key);
  }
}

This allows the following:

这允许以下内容:

TypedMap map = new TypedMap();
map.put(StringKey.A, "A");
String a = map.get(StringKey.A);

However, I don't get any compile errors if I put in the wrong value for the key. Instead, I get a runtime ClassCastExceptionon get().

但是,如果我为键输入了错误的值,我不会收到任何编译错误。相反,我ClassCastException在 get() 上获得了运行时。

map.put(StringKey.A, 10); // why doesn't this cause a compile error?
String a = map.get(StringKey.A); // throws a ClassCastException

It would be ideal if this .put() could give a compile error. As a current second best, I can get the runtime ClassCastExceptionto be thrown in the put() method.

如果这个 .put() 可以给出编译错误,那将是理想的。作为当前第二好的,我可以ClassCastException在 put() 方法中抛出运行时。

// adding this method to the AbstractKey interface:
public Class getValueClass();

// for example, in the StringKey enum implementation:
public Class getValueClass(){
  return String.class;
}

// and override the put() method in TypedMap:
public <K> K put(AbstractKey<K> key, K value){
  Object v = key.getValueClass().cast(value);
  return (K) super.put(key, v);
}

Now, the ClassCastExceptionis thrown when put into the map, as follows. This is preferable, as it allows easier/faster debugging to identify where an incorrect key/value combination has been put into the TypedMap.

现在,ClassCastException放入地图时会抛出 ,如下所示。这是更可取的,因为它允许更容易/更快的调试来识别错误的键/值组合被放入 TypedMap 的位置。

map.put(StringKey.A, 10); // now throws a ClassCastException

So, I'd like to know:

所以,我想知道:

  • Why doesn't map.put(StringKey.A, 10)cause a compile error?
  • How could I adapt this design to get meaningful compile errors on put, where the value is not of the associated generic type of the key?

  • Is this is a suitable design to achieve what I want (see top)? (Any other thoughts/comments/warnings would also be appreciated...)

  • Are there alternative designs that I could use to achieve what I want?

  • 为什么不会map.put(StringKey.A, 10)导致编译错误?
  • 我如何调整这种设计以在放置时获得有意义的编译错误,其中值不是键的关联泛型类型?

  • 这是一个合适的设计来实现我想要的(见顶部)?(任何其他想法/评论/警告也将不胜感激......)

  • 是否有替代设计可以用来实现我想要的?

EDIT - clarifications:

编辑 - 澄清:

  • If you think this is a bad design - can you explain why?
  • I've used String and Integer as example value types - in reality I have a multitude of different Key / value type pairs that I would like to be able to use. I want to use these in a single map - that's the objective.
  • 如果您认为这是一个糟糕的设计 - 您能解释一下原因吗?
  • 我已经使用 String 和 Integer 作为示例值类型 - 实际上我有许多不同的键/值类型对,我希望能够使用它们。我想在一张地图中使用这些 - 这就是目标。

采纳答案by Marko Topolnik

You are messing with generics and overloading in a bad way. You are extending HashMap<AbstractKey, Object>and so your class is inheriting the method Object put(AbstractKey k, Object v). In your class you are defining another putmethod with a different signature, which means you are just overloading the putmethod, instead of overriding it.

您正在以一种糟糕的方式使用泛型和重载。您正在扩展HashMap<AbstractKey, Object>,因此您的类正在继承该方法Object put(AbstractKey k, Object v)。在您的类中,您正在定义另一个put具有不同签名的方法,这意味着您只是重载该put方法,而不是覆盖它。

When you write map.put(StringKey.A, 10), the compiler tries to find a method that conforms to the argument types put(StringKey, Integer). Your method's signature doesn't apply, but the inherited put's does -- StringKeyis compatible with AbstractKeyand Integeris compatible with Object. So it compiles that code as a call to HashMap.put.

编写时map.put(StringKey.A, 10),编译器会尝试找到符合参数类型的方法put(StringKey, Integer)。您的方法的签名不适用,但继承put的方法的签名适用-StringKey与 兼容AbstractKey并且Integer兼容Object。因此它将该代码编译为对HashMap.put.

A way to fix this: rename putto some custom name, like typedPut.

解决此问题的方法:重命名put为某个自定义名称,例如typedPut.

BTW talking from experience your approach is very fun and engaging, but in real life it just isn't worth the trouble.

顺便说一句,从经验谈起,您的方法非常有趣且引人入胜,但在现实生活中,这不值得麻烦。

回答by trashgod

Item 29: Consider typesafe heterogeneous containers.Joshua Bloch, Effective Java, Second Edition, Chapter 5: Generics.

第 29 条:考虑类型安全的异构容器。Joshua Bloch,Effective Java,第二版,第 5 章:泛型

回答by JB Nizet

IMHO, every problem comes from the original design smell: wanting to put values of different types into the map. I would wrap your Integer and String values into a common Valuetype instead. Something like this:

恕我直言,每个问题都来自最初的设计味道:想要将不同类型的值放入地图中。我会将您的 Integer 和 String 值包装成一个通用Value类型。像这样的东西:

public class Value {
    private enum Type {
        STRING, INTEGER;
    }

    private Type type;
    private Object value;

    private Value(Object value, Type type) {
        this.value = value;
        this.type = type;
    }

    public static Value fromString(String s) {
        return new Value(s, Type.STRING);
    }

    public static Value fromInteger(Integer i) {
        return new Value(i, Type.INTEGER);
    }

    public Type getType() {
        return this.type;
    }

    public String getStringValue() {
        return (String) value;
    }

    public Integer getIntegerValue() {
        return (Integer) value;
    }

    // equals, hashCode
}

This way, you just need a Map<SomeKey, Value>, and you can safely get the value from the map:

这样,您只需要一个Map<SomeKey, Value>,就可以安全地从地图中获取值:

Value v = map.get(someKey);
if (v.getType() == Type.STRING) {
    String s = v.getStringValue();
}
else if (v.getType() == Type.INTEGER) {
    Integer i = v.getIntegerValue();
}

回答by maress

Consider this: (Thanks to Effective java)

考虑一下:(感谢 Effective java)

public class TypedMap {

    private Map<AbstractKey<?>, Object> map = new HashMap<AbstractKey<?>, Object>();

    public <T> T get(AbstractKey<T> key) {
        return key.getType().cast(map.get(key));
    }

    public <T> T put(AbstractKey<T> key, T value) {
        return key.getType().cast(map.put(key, key.getType().cast(value)));
    }

    public static interface AbstractKey<K> {

        Class<K> getType();
    }

    public static enum StringKey implements AbstractKey<String> {

        A, B;

        public Class<String> getType() {
            return String.class;
        }
    }

    public static enum IntegerKey implements AbstractKey<Integer> {

        C, D;

        public Class<Integer> getType() {
            return Integer.class;
        }
    }
  }

This generate compile time error

这会生成编译时错误

TypedMap map = new TypedMap();
TypedMap.AbstractKey<Integer> intKey = TypedMap.IntegerKey.C;
TypedMap.AbstractKey<String> strKey = TypedMap.StringKey.A;
map.put(strKey, "A");
map.put(intKey, 10);
map.put(strKey, 10); // this cause a compile error?