J'ai un Map<String, Double>
et que vous voulez multiplier par 2 toutes les valeurs de la carte, tout en conservant les valeurs NULL comme telles.
Je peux évidemment utiliser une boucle for pour faire cela, mais je me demandais s'il y avait une façon plus propre de le faire?
Map<String, Double> someMap = someMapFunction();
Map<String, Double> adjustedMap = new Hashmap<>();
if (someMap != null) {
for (Map.Entry<String,Double> pair : someMap.entryset()) {
if (pair.getValue() == null) {
adjustedMap.put(pair.getKey(), pair.getValue());
} else {
adjustedMap.put(pair.getKey(), pair.getValue()*2)
}
}
}
De plus, parfois, la carte renvoyée par someMapFunction
est une carte immuable, ce qui ne peut donc pas être fait en place avec Map.replaceAll
. Je ne pouvais pas proposer une solution de flux plus propre.
Mon premier instinct a été de suggérer un Stream
de l'entrée Map
de entrySet
qui mappe les valeurs sur de nouvelles valeurs et se termine par collectors.toMap()
.
Malheureusement, Collectors.toMap
jette NullPointerException
lorsque la fonction mappeur de valeur renvoie null
. Par conséquent, cela ne fonctionne pas avec les valeurs null
de votre entrée Map
.
Comme alternative, puisque vous ne pouvez pas muter votre entrée Map
, je vous suggère de créer une copie de celle-ci, puis appelez replaceAll
:
Map<String, Double> adjustedMap = new HashMap<>(someMap);
adjustedMap.replaceAll ((k,v) -> v != null ? 2*v : null);
En guise d'alternative aux solutions de diffusion en continu et/ou de copie, la méthode de l'utilitaire Maps.transformValues()
existe dans Google Guava :
Map<String, Double> adjustedMap = Maps.transformValues(someMap, value -> (value != null) ? (2 * value) : null);
Cela renvoie une vue paresseuse de la carte d'origine qui ne fait aucun travail à part, mais applique la fonction donnée si nécessaire. Cela peut être à la fois un pro (s'il est peu probable que vous ayez jamais besoin de toutes les valeurs, cela vous fera économiser un peu de temps de calcul) et un inconvénient (si vous avez besoin de la même valeur plusieurs fois, ou si vous devez encore changer someMap
sans adjustedMap
voir les changements) en fonction de votre utilisation.
Il y a déjà beaucoup de réponses. Certains me semblent un peu douteux. Dans tous les cas, la plupart d'entre eux sont intégrés au chèque null
- sous une forme ou une autre.
Une approche qui monte d'un échelon sur l'échelle d'abstraction est la suivante:
Vous souhaitez appliquer un opérateur unaire aux valeurs de la carte. Vous pouvez donc implémenter une méthode qui applique un opérateur unaire aux valeurs de la carte. (Jusqu'ici tout va bien). Maintenant, vous voulez un opérateur unaire "spécial" qui soit null
-safe. Ensuite, vous pouvez envelopper un opérateur unaire sûr null
- en toute sécurité.
Ceci est montré ici, avec trois opérateurs différents (l’un d’eux étant Math::sin
, d'ailleurs) :
import Java.util.LinkedHashMap;
import Java.util.Map;
import Java.util.Map.Entry;
import Java.util.function.UnaryOperator;
public class MapValueOps
{
public static void main(String[] args)
{
Map<String, Double> map = new LinkedHashMap<String, Double>();
map.put("A", 1.2);
map.put("B", 2.3);
map.put("C", null);
map.put("D", 4.5);
Map<String, Double> resultA = apply(map, nullSafe(d -> d * 2));
System.out.println(resultA);
Map<String, Double> resultB = apply(map, nullSafe(d -> d + 2));
System.out.println(resultB);
Map<String, Double> resultC = apply(map, nullSafe(Math::sin));
System.out.println(resultC);
}
private static <T> UnaryOperator<T> nullSafe(UnaryOperator<T> op)
{
return t -> (t == null ? t : op.apply(t));
}
private static <K> Map<K, Double> apply(
Map<K, Double> map, UnaryOperator<Double> op)
{
Map<K, Double> result = new LinkedHashMap<K, Double>();
for (Entry<K, Double> entry : map.entrySet())
{
result.put(entry.getKey(), op.apply(entry.getValue()));
}
return result;
}
}
Je pense que c'est propre, parce que cela sépare bien les problèmes d'application de l'opérateur et de vérification null
-. Et c'est null
- sûr, parce que ... le nom de la méthode le dit.
(On pourrait argumenter en tirant l'appel pour envelopper l'opérateur dans une méthode nullSafe
dans la méthode apply
, mais ce n'est pas le point ici)
Modifier:
En fonction du modèle d'application souhaité, vous pouvez faire quelque chose de similaire et appliquer la transformation à la place , sans créer de nouvelle carte, en appelant Map#replaceAll
Vous pouvez y parvenir en convertissant en flux, avec quelque chose comme:
someMap.entrySet()
.forEach(entry -> {
if (entry.getValue() != null) {
adjustedMap.put(entry.getKey(), someMap.get(entry.getKey()) * 2);
} else {
adjustedMap.put(entry.getKey(), null);
}
});
qui peut être réduit à:
someMap.forEach((key, value) -> {
if (value != null) {
adjustedMap.put(key, value * 2);
} else {
adjustedMap.put(key, null);
}
});
Donc, si vous avez une carte avec:
Map<String, Double> someMap = new HashMap<>();
someMap.put("test1", 1d);
someMap.put("test2", 2d);
someMap.put("test3", 3d);
someMap.put("testNull", null);
someMap.put("test4", 4d);
Vous obtiendrez cette sortie:
{test4=8.0, test2=4.0, test3=6.0, testNull=null, test1=2.0}
Que dis-tu de ça?
Map<String, Double> adjustedMap = new HashMap<>(someMap);
adjustedMap.entrySet().forEach(x -> {
if (x.getValue() != null) {
x.setValue(x.getValue() * 2);
}
});
Ça peut se faire comme ça
someMap.entrySet().stream()
.filter(stringDoubleEntry -> stringDoubleEntry.getValue() != null) //filter null values out
.forEach(stringDoubleEntry -> stringDoubleEntry.setValue(stringDoubleEntry.getValue() * 2)); //multiply values which are not null
Au cas où vous auriez besoin d’une deuxième carte dans laquelle seules les valeurs non nulles utiliseraient le forEach
pour les insérer dans votre nouvelle carte.
Vous pouvez le faire avec ce code:
Map<String, Double> map = new HashMap<>();
map.put("1", 3.0);
map.put("3", null);
map.put("2", 5.0);
Map<String, Double> res =
map.entrySet()
.stream()
.collect(
HashMap::new,
(m,v)->m.put(v.getKey(), v.getValue() != null ? v.getValue() * 2 : null),
HashMap::putAll
);
System.out.println(res);
et le résultat sera:
{1 = 6,0, 2 = 10,0, 3 = nul}
Cela vous permettra de garder null
valeurs dans la carte.
Encore une autre manière:
Map<String, Double> someMap = someMapFunction();
int capacity = (int) (someMap.size() * 4.0 / 3.0 + 1);
Map<String, Double> adjustedMap = new HashMap<>(capacity);
if (someMap != null) someMap.forEach((k, v) -> adjustedMap.put(k, v == null ? v : v * 2));
Notez que je construis la nouvelle carte avec le facteur de charge par défaut (0.75 = 3.0 / 4.0
) et une capacité initiale toujours supérieure à size * load_factor
. Cela garantit que adjustedMap
ne sera jamais redimensionné/redécoré.
Pour conserver les valeurs NULL, vous pouvez utiliser quelque chose d'aussi simple que:
someMap.keySet()
.stream()
.forEach(key -> adjustedMap.put(key, (someMap.get(key)) == null ? null : someMap.get(key) * 2));
Editez en réponse à commentaire Petr Janeček : vous pouvez appliquer la proposition sur une copie de someMap
:
adjustedMap.putAll(someMap);
adjustedMap.keySet()
.stream()
.forEach(key -> adjustedMap.put(key, (adjustedMap.get(key)) == null ? null : adjustedMap.get(key) * 2));
Si vous êtes d'accord avec les valeurs Optional
, ce qui suit peut vous convenir:
import Java.util.HashMap;
import Java.util.Map;
import Java.util.Optional;
import Java.util.function.Function;
import static Java.util.stream.Collectors.toMap;
public static Map<String, Optional<Double>> g(Map<String, Double> map, Function<Double, Double> f) {
return map.entrySet().stream().collect(
toMap(
e -> e.getKey(),
e -> e.getValue() == null ? Optional.empty() : Optional.of(f.apply(e.getValue()))
));
}
puis:
public static void main(String[] args) throws Exception {
Map<String, Double> map = new HashMap<>();
map.put("a", 2.0);
map.put("b", null);
map.put("c", 3.0);
System.out.println(g(map, x -> x * 2));
System.out.println(g(map, x -> Math.sin(x)));
}
impressions:
{a=Optional[4.0], b=Optional.empty, c=Optional[6.0]}
{a=Optional[0.9092974268256817], b=Optional.empty, c=Optional[0.1411200080598672]}
Ceci est assez propre avec la création de la nouvelle carte déléguée à Collectors
et l’avantage supplémentaire du type de retour Map<String, Optional<Double>>
indiquant clairement la possibilité null
s et encourageant les utilisateurs à les gérer.