我实现了以下方法,概述了String
和Map<String, List<String>>
的值中出现的情况:
public static Map<String, Long> getValueItemOccurrences(Map<String, List<String>> map) {
Map<String, Long> occurrencesOfValueItems = new HashMap<>();
map.forEach((key, value) -> {
value.forEach(item -> {
if (occurrencesOfValueItems.containsKey(item)) {
occurrencesOfValueItems.put(item, occurrencesOfValueItems.get(item) + 1);
} else {
occurrencesOfValueItems.put(item, 1L);
}
});
});
return occurrencesOfValueItems;
}
我已经用一个JUnit测试对它进行了测试,测试成功了。在这里(现在还包括进口):
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
class TryoutTest {
static Map<String, List<String>> items = new HashMap<>();
static List<String> largeList = new ArrayList<String>();
static List<String> mediumList = new ArrayList<String>();
static List<String> smallList = new ArrayList<String>();
static List<String> differentLargeList = new ArrayList<String>();
static List<String> differentSmallList = new ArrayList<String>();
static List<String> anotherList = new ArrayList<String>();
static List<String> someList = new ArrayList<String>();
static List<String> justAList = new ArrayList<String>();
@BeforeAll
static void setup() {
largeList.add("Alfred");
largeList.add("Bakari");
largeList.add("Christian");
largeList.add("Dong");
largeList.add("Etienne");
largeList.add("Francesco");
largeList.add("Guido");
largeList.add("Henrik");
largeList.add("Ivan");
largeList.add("Jos");
largeList.add("Kumar");
largeList.add("Leonard");
largeList.add("Marcin");
largeList.add("Nico");
largeList.add("Olof");
items.put("fifteen-01", largeList);
mediumList.add("Petar");
mediumList.add("Quentin");
mediumList.add("Renato");
mediumList.add("Sadio");
mediumList.add("Tomislav");
mediumList.add("Ulrich");
mediumList.add("Volkan");
mediumList.add("Wladimir");
items.put("eight-01", mediumList);
smallList.add("Xavier");
smallList.add("Yves");
smallList.add("Zinedine");
smallList.add("Alfred");
items.put("four-01", smallList);
differentLargeList.add("Bakari");
differentLargeList.add("Christian");
differentLargeList.add("Dong");
differentLargeList.add("Etienne");
differentLargeList.add("Francesco");
differentLargeList.add("Xavier");
differentLargeList.add("Yves");
differentLargeList.add("Wladimir");
differentLargeList.add("Jens");
differentLargeList.add("Hong");
differentLargeList.add("Le");
differentLargeList.add("Leigh");
differentLargeList.add("Manfred");
differentLargeList.add("Anders");
differentLargeList.add("Rafal");
items.put("fifteen-02", differentLargeList);
differentSmallList.add("Dario");
differentSmallList.add("Mohammad");
differentSmallList.add("Abdul");
differentSmallList.add("Alfred");
items.put("four-02", differentSmallList);
anotherList.add("Kenneth");
anotherList.add("Hong");
anotherList.add("Bakari");
anotherList.add("Ulrich");
anotherList.add("Henrik");
anotherList.add("Bernd");
anotherList.add("Samuel");
anotherList.add("Ibrahim");
items.put("eight-02", anotherList);
someList.add("Kumar");
someList.add("Konrad");
someList.add("Bakari");
someList.add("Francesco");
someList.add("Leigh");
someList.add("Yves");
items.put("six-01", someList);
justAList.add("Bakari");
items.put("one-01", justAList);
}
@Test
void valueOccurrencesTest() {
Map<String, Integer> expected = new HashMap<>();
expected.put("Abdul", 1);
expected.put("Alfred", 3);
expected.put("Anders", 1);
expected.put("Bakari", 5);
expected.put("Bernd", 1);
expected.put("Christian", 2);
expected.put("Dario", 1);
expected.put("Dong", 2);
expected.put("Etienne", 2);
expected.put("Francesco", 3);
expected.put("Guido", 1);
expected.put("Henrik", 2);
expected.put("Hong", 2);
expected.put("Ibrahim", 1);
expected.put("Ivan", 1);
expected.put("Jens", 1);
expected.put("Jos", 1);
expected.put("Kenneth", 1);
expected.put("Konrad", 1);
expected.put("Kumar", 2);
expected.put("Le", 1);
expected.put("Leigh", 2);
expected.put("Leonard", 1);
expected.put("Manfred", 1);
expected.put("Marcin", 1);
expected.put("Mohammad", 1);
expected.put("Nico", 1);
expected.put("Olof", 1);
expected.put("Petar", 1);
expected.put("Quentin", 1);
expected.put("Rafal", 1);
expected.put("Renato", 1);
expected.put("Sadio", 1);
expected.put("Samuel", 1);
expected.put("Tomislav", 1);
expected.put("Ulrich", 2);
expected.put("Volkan", 1);
expected.put("Wladimir", 2);
expected.put("Xavier", 2);
expected.put("Yves", 3);
expected.put("Zinedine", 1);
assertThat(FunctionalMain.getValueItemOccurrences(items), is(expected));
}
}
当我将方法的实现更改为
public static Map<String, Long> getValueItemOccurrences(Map<String, List<String>> map) {
return map.values().stream()
.flatMap(Collection::stream)
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
}
测试用例失败,指出结果图不等于预期的图。请看这张eclipse屏幕快照,该屏幕截图显示,显然,元素的顺序使测试失败:
是真的吗?我想我已经读过HashMap
通常不能保证键的顺序。
我的问题(很长)是:我该怎么做才能使流API调用产生通过测试的结果,或者我必须更改测试用例,也许使用其他断言?
一些子问题是:
Map
,我是否必须返回特定的TreeMap
实现?答案 0 :(得分:5)
TL; DR ,您的测试已损坏,请解决此问题。
首先,使用以下方法更容易重现:
List<String> list = ImmutableList.of("Kumar", "Kumar", "Jens");
public static Map<String, Long> getValueItemOccurrences1(List<String> list) {
return list
.stream()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
}
public static Map<String, Long> getValueItemOccurrences2(List<String> list) {
Map<String, Long> occurrencesOfValueItems = new HashMap<>();
list.forEach(item -> {
if (occurrencesOfValueItems.containsKey(item)) {
occurrencesOfValueItems.put(item, occurrencesOfValueItems.get(item) + 1);
} else {
occurrencesOfValueItems.put(item, 1L);
}
});
return occurrencesOfValueItems;
}
问题在于,在内部HashMap::hash
(也称为重新哈希)之后,在决定选择哪个存储桶时获得实际上重要的最后一位,它们具有相同的值:
System.out.println(hash("Kumar".hashCode()) & 15);
System.out.println(hash("Jens".hashCode()) & 15);
简单地说,HashMap
根据条目hashCode
决定将条目放置在何处(选择了存储桶)。好吧,差不多,一旦计算出hashCode
,内部就会完成另一个hash
-以更好地分散条目。 int
的最终hashCode
值用于确定存储桶。当您创建默认容量为16
(例如,通过new HashMap
的HashMap时,只有最后4位与条目的去向无关紧要(这就是我在此处进行& 15
的原因) -查看最后4位)。
其中hash
是:
// xor first 16 and last 16 bits
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
现在,事实证明,在应用上述算法后,["Kumar" and "Jens"]
或 ["Xavier", "Kenneth", "Samuel"]
的后4位相同(第一种情况下为3,第二种情况下为1)情况)。
现在我们知道了这些信息,实际上,这可以进一步简化:
Map<String, Long> map = new HashMap<>();
map.put("Kumar", 2L);
map.put("Jens", 1L);
System.out.println(map); // {Kumar=2, Jens=1}
map = new HashMap<>();
map.computeIfAbsent("Kumar", x -> 2L);
map.computeIfAbsent("Jens", x -> 1L);
System.out.println(map); // {Jens=1, Kumar=2}
我使用过map.computeIfAbsent
,因为Collectors.groupingBy
是在引擎盖下使用的。
事实证明,put
和computeIfAbsent
以不同的方式将元素放入HashMap
中;完全允许这样做,因为Map始终没有任何顺序-而且这些元素无论如何最终都位于同一存储桶中,这是导入部分。因此,逐项测试您的代码,先前的测试代码已损坏。
如果您愿意的话,这甚至更有趣:
HashMap::put
将以Linked
的方式添加元素(直到创建Tree
条目),因此,如果存在一个元素,则将添加所有其他元素,如:
one --> next --> next ... so on.
元素在进入end of this queue
方法时会附加到put
。
另一方面,computeIfAbsent
有所不同,它将元素添加到队列的开头。如果我们以上面的示例为例,则首先添加Xavier
。然后,当添加Kenneth
时,成为第一个:
Kenneth -> Xavier // Xavier was "first"
添加Samuel
后,它将成为第一个:
Samuel -> [Kenneth -> Xavier]
答案 1 :(得分:0)
我强烈建议您开始使用AssertJ而不是JUnit的内置断言。 这样做,您可以使用以下AssertJ断言:
assertThat(FunctionalMain.getValueItemOccurrences(items))containsOnly(expected);
containsOnly()
检查您的地图以任何顺序具有完全相同的元素
除此优势外,AssertJ的assertThat()
还利用了流畅的语法(与JUnit的内置assertThat不同),因此您的IDE可以为您提供上下文相关的帮助,以使您知道AssertJ的数百种特定于类型的断言中的哪些是可用于您的测试值。