番石榴:Set<K> + Function<K,V> = Map<K,V>?
Posted
技术标签:
【中文标题】番石榴:Set<K> + Function<K,V> = Map<K,V>?【英文标题】:Guava: Set<K> + Function<K,V> = Map<K,V>? 【发布时间】:2011-04-21 14:53:44 【问题描述】:有没有一种惯用的方法来获取Set<K>
和Function<K,V>
,并获得Map<K,V>
实时视图? (即Map
由Set
和Function
组合支持,例如,如果将一个元素添加到Set
,则相应的条目也存在于Map
中。
(请参阅例如Collections2.filter
以了解有关实时视图的更多讨论)
如果不需要实时取景怎么办?还有比这更好的吗:
public static <K,V> Map<K,V> newMapFrom(Set<K> keys, Function<? super K,V> f)
Map<K,V> map = Maps.newHashMap();
for (K k : keys)
map.put(k, f.apply(k));
return map;
【问题讨论】:
“实时取景”是什么意思? 啊,这听起来可能非常有用。 稍微改进一下是使用 Maps.newHashMapWithExpectedSize(keys.size()) 还有Maps.transformEntries()
,它是活的,但不是正确的签名。
Guava 现在将其提供为 Maps.asMap
...
【参考方案1】:
Guava 14 现在有 Maps.asMap
用于查看 Set,Maps.toMap
用于不可变副本。
您可以在这里看到很多关于所涉及问题的讨论: https://github.com/google/guava/issues/56
【讨论】:
而且已经过去了两年左右,所以不要指望它会很快发生...... 不,不是很快,但看起来它最终会出现在 Guava 14(@Beta)中。【参考方案2】:我不知道这是否是您所说的实时取景。任何方式都是我的尝试。
public class GuavaTst
public static void main(String[] args)
final Function<String, String> functionToLower = new Function<String, String>()
public String apply (String input)
return input.toLowerCase();
;
final Set<String> set=new HashSet<String>();
set.add("Hello");
set.add("BYE");
set.add("gOOd");
Map<String, String> testMap = newLiveMap(set,functionToLower);
System.out.println("Map :- "+testMap);
System.out.println("Set :- "+set);
set.add("WoRld");
System.out.println("Map :- "+testMap);
System.out.println("Set :- "+set);
testMap.put("OMG","");
System.out.println("Map :- "+testMap);
System.out.println("Set :- "+set);
static <K,V> Map<K,V> newLiveMap(final Set<K> backEnd,final Function<K,V> fun)
return new HashMap<K,V>()
@Override
public void clear()
backEnd.clear();
@Override
public boolean containsKey(Object key)
return backEnd.contains(key);
@Override
public boolean isEmpty()
return backEnd.isEmpty();
@Override
public V put(K key, V value)
backEnd.add(key);
return null;
@Override
public boolean containsValue(Object value)
for(K s:backEnd)
if(fun.apply(s).equals(value))
return true;
return false;
@Override
public V remove(Object key)
backEnd.remove(key);
return null;
@Override
public int size()
return backEnd.size();
@Override
public V get(Object key)
return fun.apply((K)key);
@Override
public String toString()
StringBuilder b=new StringBuilder();
Iterator<K> itr=backEnd.iterator();
b.append("");
if(itr.hasNext())
K key=itr.next();
b.append(key);
b.append(":");
b.append(this.get(key));
while(itr.hasNext())
key=itr.next();
b.append(", ");
b.append(key);
b.append(":");
b.append(this.get(key));
b.append("");
return b.toString();
;
实现不完整,覆盖的功能没有测试,但我希望它传达的想法。
更新:
我对@987654321@ answer 做了一些小改动,这样在地图中所做的更改也将反映在集合中。
public class SetBackedMap<K, V> extends AbstractMap<K, V> implements SetFunctionMap<K, V>
public class MapEntry implements Entry<K, V>
private final K key;
public MapEntry(final K key)
this.key = key;
@Override
public K getKey()
return this.key;
@Override
public V getValue()
V value = SetBackedMap.this.cache.get(this.key);
if(value == null)
value = SetBackedMap.this.funk.apply(this.key);
SetBackedMap.this.cache.put(this.key, value);
return value;
@Override
public V setValue(final V value)
throw new UnsupportedOperationException();
public class EntrySet extends AbstractSet<Entry<K, V>>
public class EntryIterator implements Iterator<Entry<K, V>>
private final Iterator<K> inner;
public EntryIterator()
this.inner = EntrySet.this.keys.iterator();
@Override
public boolean hasNext()
return this.inner.hasNext();
@Override
public Map.Entry<K, V> next()
final K key = this.inner.next();
return new MapEntry(key);
@Override
public void remove()
throw new UnsupportedOperationException();
private final Set<K> keys;
public EntrySet(final Set<K> keys)
this.keys = keys;
@Override
public boolean add(Entry<K, V> e)
return keys.add(e.getKey());
@Override
public Iterator<Map.Entry<K, V>> iterator()
return new EntryIterator();
@Override
public int size()
return this.keys.size();
@Override
public boolean remove(Object o)
return keys.remove(o);
private final WeakHashMap<K, V> cache;
private final Set<Entry<K, V>> entries;
private final Function<K, V> funk;
public SetBackedMap(final Set<K> keys, final Function<K, V> funk)
this.funk = funk;
this.cache = new WeakHashMap<K, V>();
this.entries = new EntrySet(keys);
@Override
public Set<Map.Entry<K, V>> entrySet()
return this.entries;
public boolean putKey(K key)
return entries.add(new MapEntry(key));
@Override
public boolean removeKey(K key)
cache.remove(key);
return entries.remove(key);
接口SetFunctionMap:
public interface SetFunctionMap<K,V> extends Map<K, V>
public boolean putKey(K key);
public boolean removeKey(K key);
测试代码:
public class SetBackedMapTst
public static void main(String[] args)
Set<Integer> set=new TreeSet<Integer>(Arrays.asList(
1, 2, 4, 8, 16));
final SetFunctionMap<Integer, String> map =
new SetBackedMap<Integer, String>(set,
new Function<Integer, String>()
@Override
public String apply(final Integer from)
return Integer.toBinaryString(from.intValue());
);
set.add(222);
System.out.println("Map: "+map);
System.out.println("Set: "+set);
map.putKey(112);
System.out.println("Map: "+map);
System.out.println("Set: "+set);
map.removeKey(112);
System.out.println("Map: "+map);
System.out.println("Set: "+set);
输出:
Map: 1=1, 2=10, 4=100, 8=1000, 16=10000, 222=11011110//change to set reflected in map
Set: [1, 2, 4, 8, 16, 222]
Map: 1=1, 2=10, 4=100, 8=1000, 16=10000, 112=1110000, 222=11011110
Set: [1, 2, 4, 8, 16, 112, 222]//change to map reflected in set
Map: 1=1, 2=10, 4=100, 8=1000, 16=10000, 222=11011110
Set: [1, 2, 4, 8, 16, 222]//change to map reflected in set
【讨论】:
@seanizer:手动是指函数 put(key,value) 吗?但是这个函数不起作用。它会抛出不受支持的异常。所以映射键的唯一方法是使用该函数。 我误会了你。等几分钟,我正在寻找更好的答案。【参考方案3】:从集合和函数创建映射
这里有两个类应该各自完成这项工作。第一个只是显示集合的地图视图,而第二个可以通过特殊接口将值写回集合。
调用语法:
Map<K,V> immutable = new SetBackedMap<K,V>(Set<K> keys, Function<K,V> func);
Map<K,V> mutable = new MutableSetBackedMap<K,V>(Set<K> keys, Function<K,V> func);
把这段代码放在哪里?
旁注:如果 guava 是我的库,我会通过 Maps 类访问它们:
Map<K,V> immutable = Maps.immutableComputingMap(Set<K> keys, Function<K,V> func);
Map<K,V> mutable = Maps.mutableComputingMap(Set<K> keys, Function<K,V> func);
不可变版本:
我已将其实现为单向视图:
对集合的更改反映在 地图,但反之亦然(无论如何您都无法更改地图,put(key, value)
方法未实现)。
entrySet()
迭代器使用
在内部设置迭代器,因此它将
也继承了内部迭代器的
处理
ConcurrentModificationException
。
put(k,v)
和
entrySet().iterator().remove()
会
扔
UnsupportedOperationException
。
值缓存在WeakHashMap
中,
没有特殊的并发处理,即没有同步
任何级别。这适用于大多数情况,但如果您的功能很昂贵,您可能需要添加一些锁定。
代码:
public class SetBackedMap<K, V> extends AbstractMap<K, V>
private class MapEntry implements Entry<K, V>
private final K key;
public MapEntry(final K key)
this.key = key;
@Override
public K getKey()
return this.key;
@Override
public V getValue()
V value = SetBackedMap.this.cache.get(this.key);
if(value == null)
value = SetBackedMap.this.funk.apply(this.key);
SetBackedMap.this.cache.put(this.key, value);
return value;
@Override
public V setValue(final V value)
throw new UnsupportedOperationException();
private class EntrySet extends AbstractSet<Entry<K, V>>
public class EntryIterator implements Iterator<Entry<K, V>>
private final Iterator<K> inner;
public EntryIterator()
this.inner = EntrySet.this.keys.iterator();
@Override
public boolean hasNext()
return this.inner.hasNext();
@Override
public Map.Entry<K, V> next()
final K key = this.inner.next();
return new MapEntry(key);
@Override
public void remove()
throw new UnsupportedOperationException();
private final Set<K> keys;
public EntrySet(final Set<K> keys)
this.keys = keys;
@Override
public Iterator<Map.Entry<K, V>> iterator()
return new EntryIterator();
@Override
public int size()
return this.keys.size();
private final WeakHashMap<K, V> cache;
private final Set<Entry<K, V>> entries;
private final Function<? super K, ? extends V> funk;
public SetBackedMap(
final Set<K> keys, Function<? super K, ? extends V> funk)
this.funk = funk;
this.cache = new WeakHashMap<K, V>();
this.entries = new EntrySet(keys);
@Override
public Set<Map.Entry<K, V>> entrySet()
return this.entries;
测试:
final Map<Integer, String> map =
new SetBackedMap<Integer, String>(
new TreeSet<Integer>(Arrays.asList(
1, 2, 4, 8, 16, 32, 64, 128, 256)),
new Function<Integer, String>()
@Override
public String apply(final Integer from)
return Integer.toBinaryString(from.intValue());
);
for(final Map.Entry<Integer, String> entry : map.entrySet())
System.out.println(
"Key: " + entry.getKey()
+ ", value: " + entry.getValue());
输出:
Key: 1, value: 1
Key: 2, value: 10
Key: 4, value: 100
Key: 8, value: 1000
Key: 16, value: 10000
Key: 32, value: 100000
Key: 64, value: 1000000
Key: 128, value: 10000000
Key: 256, value: 100000000
可变版本:
虽然我认为单向是一个好主意,但这里有一个 Emil 版本,它提供了双向视图(它是 Emil 对我的解决方案的变体的一种变体 :-))。它需要一个扩展的地图接口,我将调用ComputingMap
以明确这是一个调用put(key, value)
没有意义的地图。
地图界面:
public interface ComputingMap<K, V> extends Map<K, V>
boolean removeKey(final K key);
boolean addKey(final K key);
地图实施:
public class MutableSetBackedMap<K, V> extends AbstractMap<K, V> implements
ComputingMap<K, V>
public class MapEntry implements Entry<K, V>
private final K key;
public MapEntry(final K key)
this.key = key;
@Override
public K getKey()
return this.key;
@Override
public V getValue()
V value = MutableSetBackedMap.this.cache.get(this.key);
if(value == null)
value = MutableSetBackedMap.this.funk.apply(this.key);
MutableSetBackedMap.this.cache.put(this.key, value);
return value;
@Override
public V setValue(final V value)
throw new UnsupportedOperationException();
public class EntrySet extends AbstractSet<Entry<K, V>>
public class EntryIterator implements Iterator<Entry<K, V>>
private final Iterator<K> inner;
public EntryIterator()
this.inner = MutableSetBackedMap.this.keys.iterator();
@Override
public boolean hasNext()
return this.inner.hasNext();
@Override
public Map.Entry<K, V> next()
final K key = this.inner.next();
return new MapEntry(key);
@Override
public void remove()
throw new UnsupportedOperationException();
public EntrySet()
@Override
public Iterator<Map.Entry<K, V>> iterator()
return new EntryIterator();
@Override
public int size()
return MutableSetBackedMap.this.keys.size();
private final WeakHashMap<K, V> cache;
private final Set<Entry<K, V>> entries;
private final Function<? super K, ? extends V> funk;
private final Set<K> keys;
public MutableSetBackedMap(final Set<K> keys,
final Function<? super K, ? extends V> funk)
this.keys = keys;
this.funk = funk;
this.cache = new WeakHashMap<K, V>();
this.entries = new EntrySet();
@Override
public boolean addKey(final K key)
return this.keys.add(key);
@Override
public boolean removeKey(final K key)
return this.keys.remove(key);
@Override
public Set<Map.Entry<K, V>> entrySet()
return this.entries;
测试:
public static void main(final String[] args)
final ComputingMap<Integer, String> map =
new MutableSetBackedMap<Integer, String>(
new TreeSet<Integer>(Arrays.asList(
1, 2, 4, 8, 16, 32, 64, 128, 256)),
new Function<Integer, String>()
@Override
public String apply(final Integer from)
return Integer.toBinaryString(from.intValue());
);
System.out.println(map);
map.addKey(3);
map.addKey(217);
map.removeKey(8);
System.out.println(map);
输出:
1=1, 2=10, 4=100, 8=1000, 16=10000, 32=100000, 64=1000000, 128=10000000, 256=100000000
1=1, 2=10, 3=11, 4=100, 16=10000, 32=100000, 64=1000000, 128=10000000, 217=11011001, 256=100000000
【讨论】:
我只想将final Function<K, V> funk
更改为final Function<? super K, V> funk
,但这确实是一个不错的实现:)
@Colin 是的,但必须是 final Function<? super K, ? extends V> funk
才能更准确:-)。我用这个版本更新了我的代码。
@seanizer:我觉得如果能覆盖 toString 就好了,这样就可以直接打印地图了。
@Emil 很明显,但是AbstractMap
已经提供了hashCode()
、equals()
和toString()
。
@seanizer: 其实我的意思是如果我们可以像这样打印地图 1:1, 2:10 ,4:100... 而不是 [1,2,4 ..] 这是 toString 的当前输出【参考方案4】:
对于非实时视图,代码存在于lambdaJ 和Lambda.map(Set, Converter)
。
Set<K> setKs = new Set<K>();
Converter<K, V> converterKv = new Converter<K,V>
@Override
public V convert(K from)
return null; //Not useful here but you can do whatever you want
Map<K, V> mapKvs = Lambda.map(setKs, converterKv);
我尝试了自己的实现:http://ideone.com/Kkpcn
正如 cmets 中所说,我必须扩展另一个类,所以我只是实现了Map
,这就是为什么有这么多代码。
有一个完全没用(或没有?)的功能可让您随时更改转换器。
【讨论】:
【参考方案5】:Maps.uniqueIndex()呢
【讨论】:
那是倒退。 OP 希望计算值,而不是键。【参考方案6】:小心。肖恩帕特里克弗洛伊德的答案虽然非常有用,但有一个缺陷。一个简单的,但我花了一些时间来调试所以不要落入同一个陷阱:MapEntry 类需要equals 和hashcode 实现。这是我的(来自 javadoc 的简单副本)。
@Override
public boolean equals(Object obj)
if (!(obj instanceof Entry))
return false;
Entry<?, ?> e2 = (Entry<?, ?>) obj;
return (getKey() == null ? e2.getKey() == null : getKey().equals(e2.getKey()))
&& (getValue() == null ? e2.getValue() == null : getValue().equals(e2.getValue()));
@Override
public int hashCode()
return (getKey() == null ? 0 : getKey().hashCode()) ^
(getValue() == null ? 0 : getValue().hashCode());
此回复作为对相关答案的评论会更好,但 AFAIU 我无权发表评论(或没有找到如何发表评论!)。
【讨论】:
+1:确实,按照Map.Entry
的合约,这两种方法都是必须的。 (而且你目前缺乏 *** 的“声誉”来发布 cmets。通过写好答案,您可以增加“声誉”并解锁本网站的许多功能。)
我花了 3 年时间才看到这个答案 :-) 当然没错,但作为一般经验法则:在 SO 上看到的任何代码都不应被视为完整的生产就绪解决方案,即使如果它有时包含有价值的想法。以上是关于番石榴:Set<K> + Function<K,V> = Map<K,V>?的主要内容,如果未能解决你的问题,请参考以下文章
参数 'Set<Future<DocumentReference<Map<String, dynamic>>>> Function(String)' 不