- iOS/Objective-C 元类和类别
- objective-c - -1001 错误,当 NSURLSession 通过 httpproxy 和/etc/hosts
- java - 使用网络类获取 url 地址
- ios - 推送通知中不播放声音
我正在尝试编写一个 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
map 上的值成为我选择的值。 Example special 应该是一个整数类型,因为这就是我放入映射中的内容有没有办法在编译时检查这个错误,这样它就不会变成运行时错误?
在我发布这段代码之前,这看起来过于复杂,您可能会问为什么不这样做呢?
private Map<Object, Object> attributes = new HashMap<>();
是的,这与我正在做的一样,但在编译时无法捕捉到每一次转换。我正在尝试跟踪我作为值输入的类型,并将其作为相同类型检索,以便在编译时捕获它。
到目前为止,这是我的类(class)。
属性映射
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);
}
属性键
/**
* 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);
虽然我不想指定第二个参数,但我想隐藏它。这可能吗?
最佳答案
你不能这样做。
Java 不支持多态泛型集合的编译时类型检查。您可以向 Collection<?>
添加任何内容但是在检索时你总是会返回 Object
并且必须转换为适当的类型,这将始终涉及运行时检查。
编译器试图告诉您这一点,但您使用 @SuppressWarnings("unchecked")
禁用了警告,这就像将黑色胶带贴在汽车的温度警告灯上,然后在发动机过热时大吃一惊。
你说:
I can catch this problem by doing this.
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);Though I don't want to specify the second parameter I want to hide that. Is that possible?
仔细想想。 put
调用可能(将)发生在很远很远的地方(即不在当前源文件中,可能是去年编译的东西)。 编译器 不知道 Map
中包含哪些类型在运行时 用于任何特定 key 。事实上,在两次不同的执行中,一个给定的键可以映射到完全不同类型的值。编译器在编译源代码时如何将来 知道与键关联的值类型?或者类型总是相同的?
来自 OP 的评论:
Though making 100% type-safe collections using a map is possible. See here 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
仍然存在,这绝对不再是类型安全的。
关于多态集合的 Java 编译时类型检查,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38289465/
我来自 Asp.Net 世界,试图理解 Angular State 的含义。 什么是 Angular 状态?它类似于Asp.Net中的ascx组件吗?是子页面吗?它类似于工作流程状态吗? 我听到很多人
我一直在寻找 3 态拨动开关,但运气不佳。 基本上我需要一个具有以下状态的开关: |开 |不适用 |关 | slider 默认从中间开始,一旦用户向左或向右滑动,就无法回到N/A(未回答)状态。 有人
我是一名优秀的程序员,十分优秀!