如何使用新的 computeIfAbsent 函数?

我非常想使用 没有,但它已经太长时间,因为兰姆达斯在本科。

几乎直接来自文档: 它给出了一个旧方法的例子:

Map<String, Boolean> whoLetDogsOut = new ConcurrentHashMap<>();
String key = "snoop";
if (whoLetDogsOut.get(key) == null) {
Boolean isLetOut = tryToLetOut(key);
if (isLetOut != null)
map.putIfAbsent(key, isLetOut);
}

还有新方法:

map.computeIfAbsent(key, k -> new Value(f(k)));

但是在他们的例子中,我认为我没有完全“明白”我该如何转换代码来使用新的 lambda 表达方式呢?

188519 次浏览

假设您有以下代码:

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;


public class Test {
public static void main(String[] s) {
Map<String, Boolean> whoLetDogsOut = new ConcurrentHashMap<>();
whoLetDogsOut.computeIfAbsent("snoop", k -> f(k));
whoLetDogsOut.computeIfAbsent("snoop", k -> f(k));
}
static boolean f(String s) {
System.out.println("creating a value for \""+s+'"');
return s.isEmpty();
}
}

然后您将只看到一次消息 creating a value for "snoop",因为在第二次调用 computeIfAbsent时,该键已经有了一个值。Lambda 表达式 k -> f(k)中的 k只是一个占位符(参数) ,用于表示映射将传递给您的 lambda 以计算值的键。因此在示例中,键被传递给函数调用。

或者,您可以编写: whoLetDogsOut.computeIfAbsent("snoop", k -> k.isEmpty());,以便在没有 helper 方法的情况下实现相同的结果(但是您将看不到调试输出)。甚至更简单,因为它是一个简单的委托到一个现有的方法,您可以写: whoLetDogsOut.computeIfAbsent("snoop", String::isEmpty);这个委托不需要任何参数要写。

为了更接近你的问题中的例子,你可以把它写成 whoLetDogsOut.computeIfAbsent("snoop", key -> tryToLetOut(key));(命名参数 k还是 key并不重要)。或者,如果 tryToLetOutstatic或者 whoLetDogsOut.computeIfAbsent("snoop", this::tryToLetOut);,如果 tryToLetOut是一个实例方法,则将其写为 whoLetDogsOut.computeIfAbsent("snoop", MyClass::tryToLetOut);

最近我也在玩这个方法。我写了一个制表算法来计算斐波那契数字,它可以作为如何使用该方法的另一个说明。

我们可以从定义一个映射开始,并将基本情况(即 fibonnaci(0)fibonacci(1))的值放在映射中:

private static Map<Integer,Long> memo = new HashMap<>();
static {
memo.put(0,0L); //fibonacci(0)
memo.put(1,1L); //fibonacci(1)
}

对于归纳步骤,我们需要重新定义斐波那契函数如下:

public static long fibonacci(int x) {
return memo.computeIfAbsent(x, n -> fibonacci(n-2) + fibonacci(n-1));
}

正如你所看到的,方法 computeIfAbsent将使用提供的 lambda 表达式来计算当数字不在地图中时的斐波那契数列。这是对传统的树型递归算法的重大改进。

另一个例子。在构建复杂的映射时,computeIfAbsent ()方法替代了 map 的 get ()方法。通过将 computeIfAbsent ()调用链接在一起,可以通过提供的 lambda 表达式动态构造缺少的容器:

  // Stores regional movie ratings
Map<String, Map<Integer, Set<String>>> regionalMovieRatings = new TreeMap<>();


// This will throw NullPointerException!
regionalMovieRatings.get("New York").get(5).add("Boyhood");


// This will work
regionalMovieRatings
.computeIfAbsent("New York", region -> new TreeMap<>())
.computeIfAbsent(5, rating -> new TreeSet<>())
.add("Boyhood");

多重地图

如果您希望创建一个 多重地图,而不需要使用 谷歌番石榴库来实现 MultiMap,那么这非常有帮助。

例如,假设您想要存储为某一特定主题注册的学生的列表。

通常使用 JDK 库的解决方案是:

Map<String,List<String>> studentListSubjectWise = new TreeMap<>();
List<String>lis = studentListSubjectWise.get("a");
if(lis == null) {
lis = new ArrayList<>();
}
lis.add("John");


//continue....

由于它有一些样板代码,人们倾向于使用番石榴 Mutltimap

使用 Map.computeIfAbsent,我们可以像下面这样不用番石榴 Multimap 写一行代码。

studentListSubjectWise.computeIfAbsent("a", (x -> new ArrayList<>())).add("John");

Stuart Marks & Brian Goetz 对此做了一次很好的演讲 Https://www.youtube.com/watch?v=9utvxxjjuco

提出了这个比较的例子(旧的与新的) ,证明了两种方法;

static Map<String, Set<String>> playerSkills = new HashMap<>();
public static void main(String[] args) {
//desired output
//player1, cricket, baseball
//player2, swimming


//old way
add("Player1","cricket");
add("Player2","swimming");
add("Player1","baseball");
    

System.out.println(playerSkills);


//clear
playerSkills.clear();
    

//new
addNew("Player1","cricket");
addNew("Player2","swimming");
addNew("Player1","baseball");
System.out.println(playerSkills);
    

}


private static void add(String name, String skill) {
Set<String> skills = playerSkills.get(name);
if(skills==null) {
skills= new HashSet<>();
playerSkills.put(name, skills);
}
skills.add(skill);
}


private static void addNew(String name, String skill) {
playerSkills
.computeIfAbsent(name, set -> new HashSet<>())
.add(skill);
}