首页 > 解决方案 > 一个 java 映射,其中键是已知的,但是值应该稍后计算,因为它们很昂贵

问题描述

是否存在已知键的 java map 实现,但是应该只在第一次访问时计算值,因为计算值很昂贵。

以下演示了我希望它如何工作。

someMap.keySet(); // Returns all keys but no values are computed.
someMap.get(key); // Returns the value for key computing it if needed.

这样做的原因是我有一些包含一堆数据的东西,并且这个对象返回数据,Map<String, String>因为计算这些值的计算量很大,但是计算键很便宜。

Map 必须保持其类型,因此我不能返回Map<String, Supplier<String>>. 返回的Map可能以只读方式返回。

映射本身可以通过传入Set<String>定义键和Function<String, String>给定键返回其值的 a 来创建。

标签: javacaching

解决方案


一种解决方案可能是拥有一个 Map ,它带有一个Set键,并且Function给定一个键可以计算值。

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;

/**
 * Create a Map where we already know the keys but computing the values is expensive and so is delayed as 
 * much as possible. 
 *
 */
@AllArgsConstructor
public class MapWithValuesProvidedByFunction implements Map<String, String> {

    /**
     * All keys that are defined.
     */
    private Set<String> keys;

    /**
     * A function which maps a key to its value.
     */
    private Function<String, String> mappingFunction;

    /**
     * Holds all keys and values we have already computed.
     */
    private final Map<String, String> computedValues = new HashMap<>();

    @Override
    public int size() {
        return keys.size();
    }

    @Override
    public boolean isEmpty() {
        return keys.isEmpty();
    }

    @Override
    public boolean containsKey(Object key) {
        return keys.contains(key);
    }

    @Override
    public boolean containsValue(Object value) {
        if(computedValues.size() == keys.size()) return computedValues.containsValue(value);
        for(String k : keys) {
            String v = get(k);
            if(v == value) return true;
            if(v != null && v.equals(value)) return true;
        }
        return false;
    }

    @Override
    public String get(Object key) {
        if(keys.contains(key)) {
            return computedValues.computeIfAbsent(key.toString(), mappingFunction);
        }
        return null;
    }

    @Override
    public String put(String key, String value) {
        throw new UnsupportedOperationException("Not modifiable");
    }

    @Override
    public String remove(Object key) {
        throw new UnsupportedOperationException("Not modifiable");
    }

    @Override
    public void putAll(Map<? extends String, ? extends String> m) {
        throw new UnsupportedOperationException("Not modifiable");
    }

    @Override
    public void clear() {
        throw new UnsupportedOperationException("Not modifiable");
    }

    @Override
    public Set<String> keySet() {
        return Collections.unmodifiableSet(keys);
    }

    @Override
    public Collection<String> values() {
        return keys.stream().map(this::get).collect(Collectors.toList());
    }

    @Override
    public Set<java.util.Map.Entry<String, String>> entrySet() {
        Set<Entry<String, String>> set = new HashSet<>();
        for(String s : keys) {
            set.add(new MyEntry(s, this::get));
        }
        return set;
    }

    @AllArgsConstructor
    @EqualsAndHashCode
    public class MyEntry implements Entry<String, String> {
        private String key;
        private Function<String, String> valueSupplier;

        @Override
        public String getKey() {
            return key;
        }

        @Override
        public String getValue() {
            return valueSupplier.apply(key);
        }

        @Override
        public String setValue(String value) {
            throw new UnsupportedOperationException("Not modifiable");
        }
    }

}

正在使用的一个示例可能是:

Map<String, String> map = new MapWithValuesProvidedByFunction(
    Set.of("a", "b", "c"), // The known keys
    k -> "Slow to compute function"); // The function to make the values

将其更改为通用应该很容易。

我怀疑存在更好的解决方案,但这对其他人来说可能已经足够了。


推荐阅读