How should I write when I want to convert Map values () to another class at once?
Let's take a look at a sample that converts Map <String, String>
to Map <String, Integer>
.
public Map<String, Integer> strToInt(final Map<String, String> map) {
final Map<String, Integer> ret = new HashMap<>();
for (final Map.Entry<String, String> entry : map.entrySet()) {
ret.put(entry.getKey(), Integer.valueOf(entry.getValue()));
}
return ret;
}
For the time being, it was written up to Java 7. There is a lot of code that is verbose and non-essential.
public Map<String, Integer> strToInt(final Map<String, String> map) {
return map.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> Integer.valueOf(e.getValue())));
}
It's shorter than Plan 1, but it's still a bit long.
public Map<String, Integer> strToInt(final Map<String, String> map) {
return Maps.transformValues(map, new Function<String, Integer>() {
@Override
public Integer apply(String input) {
return Integer.valueOf(input);
}
});
}
that? Is it longer than Plan 2? No, since the abstract method is a functional interface, you can use lambda expression.
public Map<String, Integer> strToInt(final Map<String, String> map) {
return Maps.transformValues(map, v -> Integer.valueOf(v));
}
You can also use Method Reference, so you can make it a little shorter.
public Map<String, Integer> strToInt(final Map<String, String> map) {
return Maps.transformValues(map, Integer::valueOf);
}
public Map<String, Integer> strToInt(final Map<String, String> map) {
return ImmutableMap.copyOf(Maps.transformValues(map, Integer::valueOf));
}
Proposal 3 with ImmutableMap :: copyOf Wrapped. The explanation will be described later.
Now let's test if it works properly.
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
interface MapValuesTransformer {
Map<String, Integer> strToInt(final Map<String, String> map);
/**
*Proposal 1:Use for statement
*/
static class ForImpl implements MapValuesTransformer {
@Override
public Map<String, Integer> strToInt(final Map<String, String> map) {
final Map<String, Integer> ret = new HashMap<>();
for (final Map.Entry<String, String> entry : map.entrySet()) {
ret.put(entry.getKey(), Integer.valueOf(entry.getValue()));
}
return ret;
}
}
/**
*Proposal 2:Use Java 8 Stream API
*/
static class StreamImpl implements MapValuesTransformer {
@Override
public Map<String, Integer> strToInt(final Map<String, String> map) {
return map.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> Integer.valueOf(e.getValue())));
}
}
/**
*Proposal 3:Guava Maps::Use transformValues
*/
static class TranformValuesImpl implements MapValuesTransformer {
@Override
public Map<String, Integer> strToInt(final Map<String, String> map) {
return Maps.transformValues(map, Integer::valueOf);
}
}
/**
*Proposal 4:Guava Maps::Another solution using transformValues
*/
static class CopyOfTranformValuesImpl implements MapValuesTransformer {
@Override
public Map<String, Integer> strToInt(final Map<String, String> map) {
return ImmutableMap.copyOf(Maps.transformValues(map, Integer::valueOf));
}
}
}
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class MapValuesTramsformerTest {
private final MapValuesTransformer transformer;
public MapValuesTramsformerTest(MapValuesTransformer transformer) {
this.transformer = transformer;
}
@Parameters(name = "{0}")
public static Iterable<MapValuesTransformer> testClasses() {
return Arrays.asList(
new MapValuesTransformer.ForImpl(),
new MapValuesTransformer.StreamImpl(),
new MapValuesTransformer.TranformValuesImpl(),
new MapValuesTransformer.CopyOfTranformValuesImpl()
);
}
//Write various tests here
}
@Test
public void Is the value converted?() {
Map<String, String> source = new HashMap<>();
source.put("one", "1");
source.put("zero", "0");
source.put("min", "-2147483648");
Map<String, Integer> expected = new HashMap<>();
expected.put("one", 1);
expected.put("zero", 0);
expected.put("min", Integer.MIN_VALUE);
// [Proposal 1: ForImpl] Passed
// [Proposal 2: StreamImpl] Passed
// [Proposal 3: TranformValuesImpl] Passed
// [Proposal 4: CopyOfTranformValuesImpl] Passed
assertThat(transformer.strToInt(source), is(expected));
}
I passed it safely.
@Test
public void Is the order maintained?() {
Map<String, String> source = new LinkedHashMap<>();
source.put("one", "1");
source.put("zero", "0");
source.put("min", "-2147483648");
Map<String, Integer> transformed = transformer.strToInt(source);
// [Proposal 1: ForImpl] Expected: is "[one, zero, min]" but: was "[zero, min, one]"
// [Proposal 2: StreamImpl] Expected: is "[one, zero, min]" but: was "[zero, min, one]"
// [Proposal 3: TranformValuesImpl] Passed
// [Proposal 4: CopyOfTranformValuesImpl] Passed
assertThat(transformed.keySet().toString(), is(source.keySet().toString()));
}
(Although it is a rough test to toString the result) Plan 1 and Plan 2 have failed.
In the implementation of Plan 1 and Plan 2, it changes to HashMap, so LinkedHashMap and [TreeMap](https://docs.oracle.com/javase/jp/8/ If you pass docs / api / java / util / TreeMap.html), the guarantee of the order is lost.
@Test(expected = UnsupportedOperationException.class)
public void Immutable Is it immutable?() {
Map<String, String> source = new HashMap<>();
source.put("one", "1");
Map<String, Integer> expected = new HashMap<>();
expected.put("one", 1);
Map<String, Integer> transformed = transformer.strToInt(source);
// [Proposal 1: ForImpl] Failed
// [Proposal 2: StreamImpl] Failed
// [Proposal 3: TranformValuesImpl] Passed
// [Proposal 4: CopyOfTranformValuesImpl] Passed
transformed.put("two", 2);
}
In case of plan 3 and plan 4, when the put method of the converted Map is called, [UnsupportedOperationException](https://docs.oracle.com/javase/jp/8/docs/api/java/lang/UnsupportedOperationException] .html) is thrown.
Proposals 1 and 2 were Mutable, and Proposals 3 and 4 were Immutable.
@Test
Is the public void Map copied?() {
Map<String, String> source = new HashMap<>();
source.put("one", "1");
Map<String, Integer> expected = new HashMap<>();
expected.put("one", 1);
Map<String, Integer> transformed = transformer.strToInt(source);
source.put("two", "2");
// [Proposal 1: ForImpl] Passed
// [Proposal 2: StreamImpl] Passed
// [Proposal 3: TransformValuesImpl]: Expected: is <{one=1}> but: was <{one=1, two=2}>
// [Proposal 4: CopyOfTranformValuesImpl] Passed
assertThat(transformed, is(expected));
}
Only in the implementation of Proposal 3, the changes made to the conversion source Map after conversion were reflected in the conversion destination Map.
This is [JavaDoc for Maps :: transformValues](https://guava.dev/releases/snapshot-jre/api/docs/com/google/common/collect/Maps.html#transformValues-java.util.Map- As described in com.google.common.base.Function-), this method "returns a view of Map".
Therefore, if you want a copy (you do not want to reflect the changes to the conversion source Map in the conversion destination), as in Proposal 4, ImmutableMap :: copyOf You need to Wrap with -jre / api / docs / com / google / common / collect / ImmutableMap.html # copyOf-java.util.Map-).
Test perspective | Proposal 1 | Proposal 2 | Proposal 3 | Proposal 4 |
---|---|---|---|---|
Is the value converted? | Yes | Yes | Yes | Yes |
Is the order maintained? | No | No | Yes | Yes |
Immutable(Immutable)Or | No | No | Yes | Yes |
Is the Map copied? | Yes | Yes | No | Yes |
If you are aware of Advantages of immutability, the implementation method of Plan 3 or Plan 4 may be better. ?? It seems that.
Recommended Posts