Java编译时类型检查多态集合

时间:2016-07-10 07:00:33

标签: java generics type-safety

我正在尝试编写一个100%类型安全的集合。我完全正常工作唯一的问题就是这个。

    AttributeMap map = new AttributeMap();

    map.put("special", 100);

    map.put("running", false);

    int special = map.get("special");   

    boolean running = map.get("running");

    System.out.println("special value: " + special + " running value: " + running); 

    // not caught at compilation time, caught at run-time
    boolean test = map.get("special");

    // caught at compilation time
    boolean test2 = map.get("special", Integer.class);

没有运行时错误的输出

special value: 100 running value: false

地图中的值变为我选择的值。特殊示例应该是一个类型整数,因为我在映射中放置的是有一种方法可以在编译时检查此错误,因此它不会成为运行时错误吗?

在发布此代码之前,这看起来过于复杂,您可能会问为什么不这样做呢?

    private Map<Object, Object> attributes = new HashMap<>();

是的,这与我正在做的一样,但是在编译时无法捕获每个演员。我正在尝试跟踪我作为值放入的类型并将其检索为相同类型,以便在编译时捕获它。

到目前为止,这是我的课程。

AttributeMap

package com.vltr.collection.attr;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

/**
* A specialized {@link Map} that ensures type safely upon a generic map.
* 
* @author Vult-R
*/
public final class AttributeMap {

/**
 * The map contains attributes.
 */
private Map<AttributeKey<?, ?>, Object> attributes;

private Set<AttributeKey<?, ?>> set = new HashSet<>();

/**
 * Creates a new {@link AttributeMap}.
 * 
 * @param attributes
 *            The map of attributes.
 */
public AttributeMap(Map<AttributeKey<?, ?>, Object> attributes) {
    this.attributes = attributes;
}

/**
 * Creates an empty {@link AttributeMap}.
 */
public AttributeMap() {
    this.attributes = new HashMap<AttributeKey<?, ?>, Object>();
}

/**
 * Places a new {@link AttributeKey} into the map.
 * 
 * @param key
 *            The key to be used.
 * 
 * @param value
 *            The value to map.
 * 
 * @param clazz
 *            The class type associated between the key and value.
 */
@SuppressWarnings("unchecked")
public <K, V> void put(K key, V value) {
    put(new AttributeKey<K, V>(key, (Class<V>) value.getClass()), value);
}

/**
 * A wrapper function for placing a new {@link AttributeKey} into the map.
 * 
 * @param key
 *            The key to be used.
 * 
 * @param value
 *            The value to map.
 * 
 * @param clazz
 *            The class type associated between the key and value.
 */
private <K, V> void put(AttributeKey<K, V> key, V value) {
    attributes.put(key, value);
    set.add(key);
}

/**
 * A wrapper function for retrieving a value.
 * 
 * @param key
 *            The key mapped to a value.
 * 
 * @throws AttributeException
 *             If an error occurs while trying to retrieve a value.
 * 
 * @return The associated value.
 */
private <K, V> V get(AttributeKey<K, V> key) throws AttributeException {

    V type = null;

    AttributeKey<K, V> k = getFromSet(key);

    try {
        type = (V) key.getClazz().cast(attributes.get(key));
    } catch (ClassCastException ex) {
        throw new AttributeException(key, attributes.get(key).getClass());
    }

    if (key.getClazz() != k.getClazz()) {
        System.out.println("not the same");         
    }

    return type;

}

/**
 * Gets a value for retrieving a value
 * 
 * @param key
 *            The key mapped to a value.
 * 
 * @param clazz
 *            The class type associated between the key and value.
 * 
 * @throws AttributeException
 *             If an error occurs while trying to retrieve a value.
 * 
 * @return The associated value.
 */
public <K, V> V get(K key, Class<V> clazz) {
    return get(new AttributeKey<K, V>(key, clazz));
}

/**
 * Gets a value for retrieving a value
 * 
 * @param key
 *            The key mapped to a value.
 * 
 * @param clazz
 *            The class type associated between the key and value.
 * 
 * @throws AttributeException
 *             If an error occurs while trying to retrieve a value.
 * 
 * @return The associated value.
 */
public <K, V> V get(K key) {

    final AttributeKey<K, V> k = new AttributeKey<K, V>(key, getFromSet(new AttributeKey<K, V>(key, null)).getClazz());

    return get(k);
}

/**
 * Removes a {@code key} and associated value from the map.
 * 
 * @param key
 *            The key and its associated value to remove.
 */
public <K, V> void remove(AttributeKey<K, V> key) {
    attributes.remove(key);
    set.remove(key);
}

AttributeKey

/**
 * Removes a {@code key} and associated value from the map.
 * 
 * @param key
 *            The key and its associated value to remove.
 */
public <K, V> void remove(K key) {

    final AttributeKey<K, V> ak = new AttributeKey<K, V>(key, getFromSet(new AttributeKey<K, V>(key, null)).getClazz());

    remove(ak);
}

/**
 * Sets a {@code key} and its associated {@code value}.
 * 
 * @param key
 *            The key to set.
 * 
 * @param value
 *            The value to set.
 */
public <K, V> void set(AttributeKey<K, V> key, V value) {
    attributes.put(key, value);
}

/**
 * Clears all keys and associated values from this map.
 */
public void clear() {
    attributes.clear();
}

/**
 * Determines if a {@code key} with associated {@code clazz} type exists
 * within this map.
 * 
 * @param key
 *            The key to check.
 * 
 * @param clazz
 *            The clazz to check.
 * 
 * @return {@code true} If this map contains a specified key and its correct
 *         class type. {@code false} Otherwise.
 */
public <K, V> boolean containsKey(K key, Class<V> clazz) {
    return attributes.containsKey(new AttributeKey<K, V>(key, clazz));
}

/**
 * Determines if a value exists within this map.
 * 
 * @param value
 *            The value to check.
 * 
 * @return {@code true} If this map contains this specified value.
 *         {@code false} Otherwise.
 */
public boolean containsValue(Object value) {
    return attributes.containsValue(value);
}

/**
 * Retrieves the undlying {@link #entrySet()} from this map.
 * 
 * @return The {@link #entrySet()}.
 */
public Set<Entry<AttributeKey<?, ?>, Object>> entrySet() {
    return attributes.entrySet();
}

@SuppressWarnings("unchecked")
private <K, V> AttributeKey<K, V> getFromSet(AttributeKey<K, V> key) {
    for(AttributeKey<?, ?> k : set) {
        if (k.getKey() == key.getKey()) {
            return (AttributeKey<K, V>) k;
        }           
    }
    return null;
}

/**
 * Determines if this attribute map equals another attribute map.
 * 
 * @param o
 *            The object to check.
 * 
 * @return {@code true} If this map equals another attribute set,
 *         {@code false} Otherwise. *
 */
public boolean equals(Object o) {
    return attributes.equals(o);
}

/**
 * Retrieves the hash code for this attribute map.
 * 
 * @return The hash code.
 */
public int hashCode() {
    return attributes.hashCode();
}

/**
 * Determines if this attribute map is empty.
 * 
 * @return {@true} If this map is empty, {@code false} Otherwise.
 */
public boolean isEmpty() {
    return attributes.isEmpty();
}

/**
 * Retrieves the underlying {@link #keySet()} from this map.
 * 
 * @return The {@link #keySet()}.
 */
public Set<AttributeKey<?, ?>> keySet() {
    return attributes.keySet();
}

/**
 * Gets the size of this map.
 * 
 * @return The size.
 */
public int size() {
    return attributes.size();
}

public int setSize() {
    return set.size();
}

/**
 * Gets the values of this map.
 * 
 * @return The values.
 */
public Collection<Object> values() {
    return attributes.values();
}

}


package com.vltr.collection.attr;

/**
 * Represents a wrapper that wraps a {@link Map}s key value. This class will       help enforce the correct type.
 * 
 * @author Vult-R
 */
public final class AttributeKey<K, V> {

/**
 * The key that will be used.
 */
private final K key;

/**
 * The class type associated with this key.
 */
private final Class<V> clazz;

/**
 * Creates a new {@link AttributeKey}.
 * 
 * @param key
 *      The key that will be used.
 * 
 * @param clazz
 *      The associated class type.
 */
public AttributeKey(K key, Class<V> clazz) {
    this.key = key;
    this.clazz = clazz;
}   

/**
 * Gets the key for this attribute.
 * 
 * @return The key.
 */
public K getKey() {
    return key;
}

/**
 * Gets the associated class type for this attribute.
 * 
 * @return The class type.
 */
public Class<V> getClazz() {
    return clazz;
}

@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((key == null) ? 0 : key.hashCode());
    return result;
}

@Override
public boolean equals(Object obj) {
    if (this == obj) {
        return true;
    }
    if (obj == null) {
        return false;
    }
    if (getClass() != obj.getClass()) {
        return false;
    }
    AttributeKey<?, ?> other = (AttributeKey<?, ?>) obj;
    if (key == null) {
        if (other.key != null) {
            return false;
        }
    } else if (!key.equals(other.key)) {
        return false;
    }
    return true;
}

@Override
public String toString() {
    return key.getClass().getSimpleName();
}
}

package com.vltr.collection.attr;

/**
 * The {@link RuntimeException} implementation specifically for {@link    Attribute}s.
 *
 * @author Seven
 */
public final class AttributeException extends RuntimeException {

private static final long serialVersionUID = 1L;

/**
 * Creates a new {@link AttributeException}.
 *
 * @param key
 *    The key or this attribute.
 *
 * @param value
 *    The value for this attribute.
 */
public AttributeException(AttributeKey<?, ?> key, Object value) {
    super(String.format("Invalid value type: %s for [key=%s], only accepts type of %s", value.getClass().getSimpleName(), key.getKey().toString(), key.getClazz().getClass().getSimpleName()));
}

/**
 * Creates a new {@link AttributeException}.
 * 
 * @param key
 *      The key which contains an error.
 */
    public AttributeException(AttributeKey<?, ?> key) {
    super(String.format("Could not retrieve a value for [key= %s]",   key.getKey()));
    }

    public AttributeException(AttributeKey<?, ?> key, Class<?> clazz) {
    super(String.format("Could not cast [key= %s] from [type= %s] to [type=   %s]. ", key.getKey(), key.getClazz().getSimpleName(), clazz.getSimpleName()));
    }

}

我可以通过这样做来解决这个问题。

        map.put("special", 100);

    // correct
    int special = map.get("special", Integer.class);

    // incorrect and caught at compile-time     
    boolean special = map.get("special", Integer.class);

虽然我不想指定我要隐藏的第二个参数。这可能吗?

1 个答案:

答案 0 :(得分:2)

你不能这样做。

Java不支持多态泛型集合的编译时类型检查。您可以向Collection<?>添加任何内容,但在检索时,您总是返回Object并且必须转换为适当的类型,这将始终涉及运行时检查。

编译器试图告诉你这个,但你用@SuppressWarnings("unchecked")禁用了警告,这就像把黑色磁带放在汽车的温度警告灯上,然后在发动机过热时感到惊讶。

你说:

  

我可以通过这样做来解决这个问题。

map.put("special", 100);
// correct
int special = map.get("special", Integer.class);

// incorrect and caught at compile-time     
boolean special = map.get("special", Integer.class);
     

虽然我不想指定我要隐藏的第二个参数。这可能吗?

想一想。 put调用可能(将)发生在很远的地方(即不在当前的源文件中,可能是去年编译的内容)。对于任何特定密钥, 编译器 都不知道Map 在运行时 中包含哪些类型。事实上,在两个不同的执行中,给定的键可以映射到完全不同类型的值。在编译源代码时,编译器应该如何知道与 以后的 相关联的值类型 ?或者类型总是相同的?

来自OP的评论:

  

虽然可以使用地图制作100%类型安全的集合。见https://github.com/atomicint/aj8/tree/master/server/src/main/java/org/apollo/game/attribute

请注意AttributeMap.java

@SuppressWarnings("unchecked")  
public <T> T get(AttributeKey<T> key) { 
    ...

所有代码都将运行时检查推送到AttributeMap<>#get(),它也会转到@SuppressWarnings("unchecked")。它只是隐藏运行时检查,因此您的代码不必隐藏它。运行时检查和潜在的ClassCastException仍然存在,这绝对不是更安全的类型。