Since, statckoverflow does not allow add more thing to your question in the original question (you can only add comment, not code) I am asking a sequential question to my original question here:
Can we use Synchronized for each entry instead of ConcurrentHashMap?
The problem is very simple, and I don't know why for such a simple problem that probably many people have encountered before me I should spend this much time :/
The problem is: I have a hashmap, I want when one thread is working on one of the entries of the hashMap, no any other thread access that object, and I don't want to lock the whole hashMap.
I know that java provides ConcurrentHashMap, but ConcurrentHashMap does not solve the problem, when you want to do thing more complex than simple put and get. Even newly added functions (in Java 8) like merge is not enough for complex scenarios.
For example:
Suppose I want a hash map that maps strings to ArrayLists. Then for example suppose I want to do this:
For key k, if there is any entry, add newString to its ArrayList, but if there is no entry for k, create the entry for k such that its ArrayList has newString.
I was thinking I can do it as follows:
ArrayList<String> tm =new ArrayList<String>();
tm.add(newString);
Object result = map.putIfAbsent(k, tm);
if (result != null)
{
map.get(k).add(newString);
}
But it does not work, why? suppose putIfAbset return something other than null, then it means that map already has an entry with key k, so I will try to add newString to the ArrayList of the already existing entry, but right before adding, another thread may remove the entry, and then I will get NullPointerException!
So, I found it very difficult to code such things properly.
But I was thinking that if I can simply lock that entry, life will be wonderful!
In my previous post I suggested something very simple that in fact eliminates the need for concurrentHashMap, and provide entry-level locking but some said that is not true because Long is not immutable ... that I didn't get it well.
Now, I implemented and tested it, it looks good to me, but I don't know why other more experienced developers here told me it is not thread-safe :(
This is the exact code that I tested:
MainThread:
import java.util.HashMap;
public class mainThread {
public static HashMap<String, Long> map = new HashMap<String, Long>();
public static void main (String args[])
{
map.put("k1", new Long(32));
synchronized(map.get("k1"))
{
Thread t = new Thread(new threadA());
t.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
ThreadA:
public class ThreadA implements Runnable {
#Override
public void run() {
mainThread.map.put("k2", new Long(21));
System.out.println(mainThread.map.get("k2"));
synchronized (mainThread.map.get("k1")) {
System.out.println("Insdie synchronized of threadA");
}
}
}
It works fine! It prints 21, and after 5 seconds, that mainThread release the lock of map.get("k1"), it prints "Insdie synchronized of threadA"
So, why using this simple approach we cannot provide entry-level locking?! why concurrency should be that much complicated Lol (just kidding)
First of all, there is no standard map implementation that I am aware of that provides entry level locking.
But I think you can avoid the need for that. For example
UPDATE ... corrected mistake
ArrayList<String> tm = new ArrayList<String>();
ArrayList<String> old = map.putIfAbsent(k, tm);
if (old != null) {
tm = old;
}
synchronized (tm) {
// can now add / remove entries and this will appear as an atomic
// actions to other threads that are using `synchronized` to
// access or update the list
tm.add(string1);
tm.add(string2);
}
Yes it is possible that another thread will update the list in the hashmap entry between this thread (possibly) inserting it, and this thread locking it. However, that doesn't matter. The (corrected) putIfAbsent and the test that follows ensures that everyone will use and lock the same list.
(Assumption: that all threads use this logic when inserting / updating an entry.)
Atomically removing the list if it becomes empty is difficult, but I would argue that it is usually unnecessary to do that.
UPDATE 2
There is a better way:
ArrayList<String> tm = map.computeIfAbsent(k, ArrayList::new);
synchronized (tm) {
...
}
(Thanks Stuart)
UPDATE 3
We can do it with merger too.
Maybe, yes. Something like this:
ArrayList<String> tm = new ArrayList<String>;
tm.add(...);
...
map.merge(key, tm, (oldV, newV) -> {oldV.addAll(newV); return oldV});
The downside is that you are double-handling all the elements of tm; i.e. adding to 2 separate lists (one of which you throw way).
But you could also do this:
map.merge(key, tm, (oldV, newV) -> {
oldV.removeAll(newV);
return oldV.size() == 0 ? null : oldV}
);
The thing that concerns me is that the javadoc does not state explicitly that the value oldV will be locked while this is happening. It says:
"The entire method invocation is performed atomically. Some attempted update operations on this map by other threads may be blocked while computation is in progress ..."
... but it does not explicitly state that there is mutual exclusion on the value while this is happening. (For instance, mixing this approach with putIfAbsent / computeIfAbsent and an explicit synchronized block would most likely be hazardous. The locking would most likely be on different objects.)
Well, the first huge problem is that you don't even attempt to do any locking for the put calls. Those aren't automatically threadsafe for a regular HashMap. You seem to be under the impression that separate HashMap entries are completely independent automatically, but HashMaps don't work that way.
Even if you fix the put problem (probably requiring ConcurrentHashMap or a whole-map lock anyway), the parts you actually are locking for aren't locking safely.
Say thread 1 puts the entry "k1": 1, and thread 2 tries to get("k1"). What will thread 2 see?
Well, thread 2 doesn't even try to acquire any locks until the get call is already done. The get call is completely unprotected! Without any happens-before relation between the put and the get, the get call might not see the entry, or it might see the entry, or it might see the map in an inconsistent intermediate state and crash horribly.
Synchronizing on the result of the get call is synchronizing far too late.
I think I have finally found the solution using merge function. I provide an example, I will edit this post to make it easier for others to read, but I just post now to have your feedback.
Here is the example of a ConcurrentHashMap that has ConcurrentHashMaps as its values (23 and 1 are just two random value for sake of example):
Long intialValue = new Long(3);
Long addedValue = new Long(10);
Long removingValue = new Long (5);
ConcurrentHashMap<Integer, ConcurrentHashMap<Integer, Long>> map = new ConcurrentHashMap<>();
//Initialization....
ConcurrentHashMap<Integer, Long> i = new ConcurrentHashMap<Integer, Long>();
i.put(1, intialValue);
map.put(23, i);
//......
//addition
ConcurrentHashMap<Integer, Long> c = new ConcurrentHashMap<Integer, Long>();
c.put(1, addedValue);
map.merge(23, c, (oldHashMap, newHashMap) -> {
oldHashMap.merge (1, c.get(1), (oldV, newV) -> {
if (oldV < newV) return newV; else return oldV;
});
return oldHashMap;
});
//removal
// we want to remove entry 1 from the inner HashMap if its value is less than 2, and if the entry is empty remove the entry from the outer HashMap
ConcurrentHashMap<Integer, Long> r = new ConcurrentHashMap<Integer, Long>();
r.put(1, removingValue);
map.merge (23, r, (oldHashMap, newHashMap) -> {
oldHashMap.merge(1, newHashMap.get(1), (oldV, newV) -> {if (oldV < newV) return newV; else return oldV;});
return oldHashMap;
});
map.remove(23, r);
if (map.containsKey(23))
{
System.out.println("Map contains key 23");
if (map.get(23).containsKey(1))
{
System.out.println("The value for <23,1> is " + map.get(23).get(1));
}
}
This is what the code does:
initialization: first creates the map and puts another map into it for key 23 which has value initialValue for key 1.
addition: Then checks, 1) if for key 23, there is no value, it puts a map that has value addedValue for key 1, otherwise 2) if key 23 has already a value, it checks its value if the value has a value less than addedValue, it overwrites it with the addedValue, otherwise it leaves it alone.
removal: Finally, it checks, if for key 23, and for key 1 in the value for 23, the value is less than removingValue, it removes that, and if the hashMap of key 23 is empty after this removal, it removes key 23 from the main map.
I tested this code. So for example:
for 3, 10, 5, the final value for <23,1> is 10.
for 20, 10, 11, the final value is 20.
for 3, 10, 11, the final value is nothing,because entry 23 is
removed.
I hope it is thread-safe as I just used merge method. One disadvantage of this code is that I am adding something to map and then remove it, just because ConcurrentHashMap does not have a method for remove similar to merge. I wish I had this method:
map.remove (keyToRemove, condition)
Related
Please help me to figure out what is wrong with the method bellow and how can I solve it. The method takes a stream of Person object and Map with String value (a task name) as a key and an int value (a mark). The purpose of the method is to check whether a stream contains the particular tasks from allHistoryTasks variable and if does apply to this Map .putIfAbsentmethod(taskName, 0) to ensure that all the tasks are present in the Map (the purpose is to calculate an average mark later).
When I run the test the UnsupportedOperationException error apears. When I comment the lines from the if statement and to forEach (lines 1, 2, 3, 4) test runs well. I'm new to Java and already spent several days on this issue but still can't solve it. Please tell me what is wrong here.
private Set<String> allHistoryTasks = Set.of("Phalanxing", "Shieldwalling", "Tercioing", "Wedging");
private String[] historyTasks = allHistoryTasks.toArray(new String[0]);
public Map<Person, Map<String, Integer>> addHistoryIfPresent(Stream<CourseResult> stream) {
return stream.collect(Collectors.toMap(
CourseResult::getPerson,
x -> {
if (allHistoryTasks.containsAll(x.getTaskResults().keySet())) //1
IntStream.range(0, allHistoryTasks.size()) //2
.parallel() //3
.forEach(i -> x.getTaskResults().putIfAbsent(historyTasks[i], 0)); //4
return x.getTaskResults();
}
));
}
custom classes & thread report
The x -> {} block is the 'value mapper'. It is supposed to turn an element of your stream into the value for a given map.
You have a stream of CourseResult objects, and want a Map<Person, Map<String, Integer>>, so this function turns a CourseResultobject into aMap<String, Integer>`.
You do this by mutating things and that is a biiig nono. Your stream functions should not have any side-effects. Fortunately, the author of CourseResult is way ahead of you and blocked you from making this error. You are calling .getTaskResults() on your course result object and then trying to modify it. You can't do that, as the getTaskResults() method returns a map that cannot be modified.
Presumably, you want to clone that map, and fix the clone. How do you do that? Well, you tell me, the API isn't clear. You could simply make a new ImmutableMap.builder(), loop through whatever you want to loop through, and so on. From your code it's not quite clear what end map you do want.
Note also that you're using powers without knowing what you're doing - you have a parallel stream and are then forEaching through it, mutating the same variable, which you absolutely cannot do: This results in bugs where the result of an operation depends on an evil coin flip, in the sense that it can work fine today even if you rerun the tests a million times, and fail tomorrow. Separately, using parallel() for such talks is borderline crazy - assuming the underlying stream impl actually parallelizes (.parallel() is a hint, not a demand), it would just slow everything waaay down. allHistoryTasks is tiny. This isn't what parallelism would be for.
This might be the answer to your question. Set.of method won't return the mutable set. So you need to declare a mutrable set like this to avoid this problem.
private Set<String> allHistoryTasks = new HashSet<>(Arrays.asList("Phalanxing", "Shieldwalling", "Tercioing", "Wedging"));
private String[] historyTasks = allHistoryTasks.toArray(new String[0]);
public Map<Person, Map<String, Integer>> addHistoryIfPresent(Stream<CourseResult> stream) {
return stream.collect(Collectors.toMap(
CourseResult::getPerson,
x -> {
if (allHistoryTasks.containsAll(x.getTaskResults().keySet())) //1
IntStream.range(0, allHistoryTasks.size()) //2
.parallel() //3
.forEach(i -> x.getTaskResults().putIfAbsent(historyTasks[i], 0)); //4
return x.getTaskResults();
}
));
}
I have a class with a collection of Seed elements. One of the method's return type of Seed is Optional<Pair<Boolean, String>>.
I'm trying to loop over all seeds, find if any boolean value is true and at the same time, create a set with all the String values. For instance, my input is in the form Optional<Pair<Boolean, String>>, the output should be Optional<Signal> where Signal is like:
class Signal {
public boolean exposure;
public Set<String> alarms;
// constructor and getters (can add anything to this class, it's just a bag)
}
This is what I currently have that works:
// Seed::hadExposure yields Optional<Pair<Boolean, String>> where Pair have key/value or left/right
public Optional<Signal> withExposure() {
if (seeds.stream().map(Seed::hadExposure).flatMap(Optional::stream).findAny().isEmpty()) {
return Optional.empty();
}
final var exposure = seeds.stream()
.map(Seed::hadExposure)
.flatMap(Optional::stream)
.anyMatch(Pair::getLeft);
final var alarms = seeds.stream()
.map(Seed::hadExposure)
.flatMap(Optional::stream)
.map(Pair::getRight)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
return Optional.of(new Signal(exposure, alarms));
}
Now I have time to make it better because Seed::hadExposure could become and expensive call, so I was trying to see if I could make all of this with only one pass. I've tried (some suggestions from previous questions) with reduce, using collectors (Collectors.collectingAndThen, Collectors.partitioningBy, etc.), but nothing so far.
It's possible to do this in a single stream() expression using map to convert the non-empty exposure to a Signal and then a reduce to combine the signals:
Signal signal = exposures.stream()
.map(exposure ->
new Signal(
exposure.getLeft(),
exposure.getRight() == null
? Collections.emptySet()
: Collections.singleton(exposure.getRight())))
.reduce(
new Signal(false, new HashSet<>()),
(leftSig, rightSig) -> {
HashSet<String> alarms = new HashSet<>();
alarms.addAll(leftSig.alarms);
alarms.addAll(rightSig.alarms);
return new Signal(
leftSig.exposure || rightSig.exposure, alarms);
});
However, if you have a lot of alarms it would be expensive because it creates a new Set and adds the new alarms to the accumulated alarms for each exposure in the input.
In a language that was designed from the ground-up to support functional programming, like Scala or Haskell, you'd have a Set data type that would let you efficiently create a new set that's identical to an existing set but with an added element, so there'd be no efficiency worries:
filteredSeeds.foldLeft((false, Set[String]())) { (result, exposure) =>
(result._1 || exposure.getLeft, result._2 + exposure.getRight)
}
But Java doesn't come with anything like that out of the box.
You could create just a single Set for the result and mutate it in your stream's reduce expression, but some would regard that as poor style because you'd be mixing a functional paradigm (map/reduce over a stream) with a procedural one (mutating a set).
Personally, in Java, I'd just ditch the functional approach and use a for loop in this case. It'll be less code, more efficient, and IMO clearer.
If you have enough space to store an intermediate result, you could do something like:
List<Pair<Boolean, String>> exposures =
seeds.stream()
.map(Seed::hadExposure)
.flatMap(Optional::stream)
.collect(Collectors.toList());
Then you'd only be calling the expensive Seed::hadExposure method once per item in the input list.
I have two hashmaps (resources and neededResources).
Goal is to reduce the amount of resources hashmap, but that can only happen if it has enough of both resource.
// String = name of resources available
// Integer = amount of resources available
Map<String, Integer> resources = new HashMap<>();
resources.put("gold", 10);
resources.put("silver", 10);
// String = name of resource needed
// Integer = amount of resource needed
Map<String, Integer> neededResources = new HashMap<>();
neededResources.put("gold", 2);
neededResources.put("silver", 3);
In this sample case, it would take resources 3 times since on 4th try there wouldn't be enough silver, gold value doesn't change as well.
I'm a novice at Java. So far I've tried iterating through both of them but it becomes hard to read and my attempts have looked way too difficult for this task.
I think this might be what you are after. The code can be inserted after your setup code.
Find the number of possible expenditures:
int n = resources.entrySet().stream().mapToInt(entry -> entry.getValue()/neededResources.getOrDefault(entry.getKey(), 0)).min().getAsInt();
System.out.println(n);
Prints 3
Spend it:
resources.replaceAll((k,v) -> v-n*neededResources.getOrDefault(k,0));
System.out.println(resources);
Prints {gold=4, silver=1}
Take the value from your resources map, substract the value from neededResources and check if it is >=0. This tells you that there were at least as many resources available as needed. If resources were available, update the value. Otherwise dont.
Create a method wrapper in your resources class
public boolean hasResources(String... pResources){
for(String resourceKey : pResources){
int newValue = resources.get(pResourceKey) - neededResources.get(pResourceKey);
if(newValue < 0){
return false;
}
}
return true;
}
public void takeResources(String... pResources){
for(String resourceKey : pResources){
int newValue = resources.get(pResourceKey) - neededResources.get(pResourceKey);
resources.put(resourceKey, newValue);
}
}
The key to this problem is that we don't want to modify the resources Map unless we are absolutely sure that it contains all of the resourcesNeeded. Once you learn about Streams in Java, this problem should simplify.
First, we want to check if the resources Map contains all of the keys in the resourcesNeeded Map, which may or may not already be given information which you haven't mentioned.
resources.keySet().containsAll(neededResources.keySet());
If that condition is false, then we know that there are needed resources that are not available. Otherwise, we can now check if every value in resources is greater than or equal to the value in resourcesNeeded for its respective key:
resources.entrySet()
.stream()
.allMatch(entry -> entry.getValue() >= resourcesNeeded.get(entry.getKey()));
If that condition is false, then more resources are needed then are currently available. Otherwise, we can now modify resources, essentially subtracting each respective value in resourcesNeeded:
resources.replaceAll((k, v) -> v - resourcesNeeded.getOrDefault(k, 0)));
The last two statements can be placed inside of a loop to ensure that the minimum amount of resources remain.
I'm trying to implement a complicated block that I've written using multiple for loops and if conditions to something much less convoluted. The initial code is
for(Coll_Monthly_Main monthlyAccount:monthlyList){
for(Coll_Daily_Main dailyAccount:dailyList){
if(monthlyAccount.getAccountId().trim().equals(dailyAccount.getAccountId().trim())){
for(Catg_Monthly monthlyCategory: monthlyAccount.getCatg()){
for(Catg_Daily dailyCategory: dailyAccount.getCatg()){
if(monthlyCategory.getPriCatId().trim().equals(dailyCategory.getPriCatId().trim())){
monthlyCategory.setMthTtl(dailyCategory.getMthTtl());
monthlyCategory.setMtd(dailyCategory.getMtd());
monthlyCategory.setYtd(dailyCategory.getYtd());
for(SecCatDtl_Mthly monthlySecCategory:monthlyCategory.getSecCatDtl()){
for(SecCatDtl_Daily dailySecCategory:dailyCategory.getSecCatDtl()){
if(monthlySecCategory.getCatId().trim().equals(dailySecCategory.getCatId().trim())){
monthlySecCategory.setMthTtl(dailySecCategory.getMthTtl());
monthlySecCategory.setMtd(dailySecCategory.getMtd());
monthlySecCategory.setYtd(dailySecCategory.getYtd());
}
}
}
}
}
}
}
}
}
return monthlyList;
I've followed this answer and have managed to implement the first level as below:-
monthlyList.forEach(coll_mthly->{
dailyList.stream().filter(coll_daily->coll_mthly.getAccountId().trim().equals(coll_daily.getAccountId().trim()))
.forEach(catg_mth->coll_mthly.getCatg())->{
};
});
For the next level of nesting, I need to loop over a nested list and I'm not sure how to proceed about it. I keep getting a syntax error as follows:-
Syntax error on tokens, TypeElidedFormalParameter expected instead
I'd appreciate any pointers in the right direction.
Update:-
This is how it looks like following Thomas' answer
Map<String, Coll_Daily_Main> dailies = dailyList.stream().collect(Collectors.toMap(cdm -> cdm.getAccountId(), cdm-> cdm) );
for(Coll_Monthly_Main monthlyAccount : monthlyList) {
Coll_Daily_Main dailiesForAccount = dailies.get( monthlyAccount.getAccountId().trim());
Map<String, Catg_Daily> dailyCatgories=dailiesForAccount.getCatg().stream().collect(Collectors.toMap(cv->cv.getPriCatId(), cv->cv));
for(Catg_Monthly monthlyCategory:monthlyAccount.getCatg()){
Catg_Daily dailyCategory = dailyCatgories.get(monthlyCategory.getPriCatId().trim());
if(dailyCategory!=null){
monthlyCategory.setMthTtl(dailyCategory.getMthTtl());
monthlyCategory.setMtd(dailyCategory.getMtd());
monthlyCategory.setYtd(dailyCategory.getYtd());
Map<String,SecCatDtl_Daily> dailySecCategories=dailyCategory.getSecCatDtl().stream().collect(Collectors.toMap(fg->fg.getCatId(), fg->fg));
for(SecCatDtl_Mthly monthlySecCategory:monthlyCategory.getSecCatDtl()){
SecCatDtl_Daily dailySecCategory =dailySecCategories.get(monthlySecCategory.getCatId().trim());
if(dailySecCategory!=null){
monthlySecCategory.setMthTtl(dailySecCategory.getMthTtl());
monthlySecCategory.setMtd(dailySecCategory.getMtd());
monthlySecCategory.setYtd(dailySecCategory.getYtd());
}
}
}
}
}
As the others already stated multiple times it's most likely better to rethink your approach and make it not even more readable but faster as well. One thing that comes to my mind: you have 3 levels that consist of 2 loops and an if to check whether the elements match (by id). Those levels will have O(n*m) complexity.
However, you could try to build a map or multimap (Guava has some) with the id as the key and get it down to O(n + m):
O(n) for building the map (ideally on on the larger set, i.e. daily)
O(m) for iterating over the second set (ideally the smaller set, i.e. monthly)
Lookups should be O(1) so can be ignored
I'm not sure what all those nested levels mean so I only can give an example of what you could do for one level (I'll take the first):
//I'm using Google Guava's classes here
SetMultimap<String, Coll_Daily_Main> dailies = ...;//use one that fits your needs
//Iterate over n daily entries and put them into the map which should be O(n)
dailyList.forEach( cdm -> dailies.put( cdm.getAccountId().trim(), cdm ) );
//Iterate over the (smaller) set of monthly entries and do a lookup for the dailies which should be O(m)
for(Coll_Monthly_Main monthlyAccount : monthlyList) {
Set<Coll_Daily_Main> dailiesForAccount = dailies.get( monthlyAccount.getAccountId().trim() );
//level done, either go down to the next or try to further straighten it out or optimize
}
Update:
I forgot to mention that you'd not have to use Guava with Java 8. Though the definition looks a little more awkward using a Map<String, Set<Coll_Daily_Main>> isn't that "hard" anymore:
Map<String, Set<String>> dailies = new HashMap<>();
dailyList.forEach( cdm -> dailies.computeIfAbsent( cdm.getAccountId().trim(), v -> new HashSet<>() ).add( cdm ) );
Note: you could also use collectors to make it a little shorter and in one line. Whether that's easier to read and use is up to debate.
Map<String, Set<Daily>> dailies =
dailyList.stream().collect( Collectors.groupingBy( cdm -> cdm.getAccountId().trim(),
Collectors.toSet() ) );
First you need to extract all your if statements in private methods.
Then you can start to refactor your for statements with lambdas.
You could even declare a static function (called loop in the example below) to export your nested loops logic:
public class Test {
public List<Coll_Monthly_Main> runThatThing(List<Coll_Monthly_Main> monthlyList, List<Coll_Daily_Main> dailyList) {
loop(monthlyList, dailyList, Test::updateMonthlyCategories);
return monthlyList;
}
private static void updateMonthlyCategories(Coll_Monthly_Main monthlyAccount, Coll_Daily_Main dailyAccount) {
if(monthlyAccount.getAccountId().trim().equals(dailyAccount.getAccountId().trim())){
loop(monthlyAccount.getCatg(), dailyAccount.getCatg(), Test::updateMonthlyCategory);
}
}
private static void updateMonthlyCategory(Catg_Monthly monthlyCategory, Catg_Daily dailyCategory) {
if(monthlyCategory.getPriCatId().trim().equals(dailyCategory.getPriCatId().trim())){
monthlyCategory.setMthTtl(dailyCategory.getMthTtl());
monthlyCategory.setMtd(dailyCategory.getMtd());
monthlyCategory.setYtd(dailyCategory.getYtd());
loop(monthlyCategory.getSecCatDtl(), dailyCategory.getSecCatDtl(), Test::updateMonthlySecondCategory);
}
}
private static void updateMonthlySecondCategory(SecCatDtl_Mthly monthlySecCategory, SecCatDtl_Daily dailySecCategory) {
if(monthlySecCategory.getCatId().trim().equals(dailySecCategory.getCatId().trim())){
monthlySecCategory.setMthTtl(dailySecCategory.getMthTtl());
monthlySecCategory.setMtd(dailySecCategory.getMtd());
monthlySecCategory.setYtd(dailySecCategory.getYtd());
}
}
// nested loops through list1 and list2 which apply the function `f` to all pairs.
//Using a BiConsumer because the f methods we use always return void
private static <T, U> void loop(List<T> list1, List<U> list2, BiConsumer<T, U> f) {
list1.forEach(
element1 -> list2.forEach(
element2 -> f.accept(element1, element2)
));
}
}
Is there a way to add a key to a HashMap without also adding a value? I know it seems strange, but I have a HashMap<String, ArrayList<Object>> amd I want to first be able to create keys as needed and then check if a certain key exists and, if so, put the appropriate value, namely the ArrayList<Object>
Was that confusing enough?
Since you're using a Map<String, List<Object>>, you're really looking for a multimap. I highly recommend using a third-party library such as Google Guava for this - see Guava's Multimaps.
Multimap<String, Object> myMultimap = ArrayListMultimap.create();
// fill it
myMultimap.put("hello", "hola");
myMultimap.put("hello", "buongiorno");
myMultimap.put("hello", "สวัสดี");
// retrieve
List<String> greetings = myMultimap.get("hello");
// ["hola", "buongiorno", "สวัสดี"]
Java 8 update: I'm no longer convinced that every Map<K, SomeCollection<V>> should be rewritten as a multimap. These days it's quite easy to get what you need without Guava, thanks to Map#computeIfAbsent().
Map<String, List<Object>> myMap = new HashMap<>();
// fill it
myMap.computeIfAbsent("hello", ignored -> new ArrayList<>())
.addAll(Arrays.asList("hola", "buongiorno", "สวัสดี");
// retrieve
List<String> greetings = myMap.get("hello");
// ["hola", "buongiorno", "สวัสดี"]
I'm not sure you want to do this. You can store null as a value for a key, but if you do how will be able to tell, when you do a .get("key") whether the key exists or if it does exist but with a null value? Anyway, see the docs.
You can put null values. It is allowed by HashMap
You can also use a Set initially, and check it for the key, and then fill the map.
Yes, it was confusing enough ;) I don't get why you want to store keys without values instead just putting empty arraylists instead of null.
Adding null may be a problem, because if you call
map.get("somekey");
and receive a null, then you do not know, if the key is not found or if it is present but maps to null...
//This program should answer your questions
import java.util.*;
public class attemptAddingtoHashMap { //Start of program
//MAIN METHOD #################################################
public static void main(String args[]) { //main begins
Map<String, ArrayList<Object>> hmTrial = new HashMap<String, ArrayList<Object>>();
ArrayList alTrial = new ArrayList();//No values now
if (hmTrial.containsKey("first")) {
hmTrial.put("first", alTrial); }
else {hmTrial.put("first",alTrial);}
//in either case, alTrial, an ArrayList was mapped to the string "first"
//if you choose to, you can also add objects to alTrial later
System.out.println("hmTrial is " + hmTrial); //empty now
alTrial.add("h");
alTrial.add("e");
alTrial.add("l");
alTrial.add("l");
alTrial.add("o");
System.out.println("hmTrial is " + hmTrial);//populated now
} //end of main
//#############################################################################################################
} //end of class
//Note - removing objects from alTrial will remove the from the hashmap
//You can copy, paste and run this code on https://ide.geeksforgeeks.org/