I have a problem with the HashMap. It changes the references stored as values when new Key-Value-Pairs are inserted.
I use the HashMap for quicker access to Objects that are otherwise stored in a very hierarchical structure. When the first pair was inserted, its address and the original address are identical. After adding another pair, the address stored in the HashMap is changed. Therefor I cant the original Objects through the HashMap.
Why is this happening?
Here is the code how I construct the HashMap. In the second method, in the first for-loop the above described happens.
private Map<String, Parameter> createRefMap(Settings settings) {
Map<String, Parameter> result = new HashMap<String, Parameter>();
for (ParameterList parameterList : settings.getParameterList()) {
result.putAll(createRefMap(parameterList, "SETTINGS"));
}
return result;
}
private Map<String, Parameter> createRefMap(ParameterList parameterList, String preLevel) {
Map<String, Parameter> result = new HashMap<String, Parameter>();
String level = preLevel + "/" + parameterList.getName();
for (Parameter parameter : parameterList.getParameter()) {
result.put(level + "/" + parameter.getName(), parameter);
}
for (ParameterList innerParameterList : parameterList.getParameterList()) {
result.putAll(createRefMap(innerParameterList, level));
}
return result;
}
This is how I call it
this.actRefMap = createRefMap(this.actAppSettings);
If I understand you correctly, if you do something like this:
System.out.println(thing1.toString());
myMap.put(key1, thing1);
myMap.put(key2, thing2);
System.out.println(thing1.toString());
that the second println will somehow print out results from a different object? Is it any particular object, or just one at random? What you state as your problem is not possible; it would break an unthinkable number of java programs.
Part of your assertion is that the "address" changes; I'm not sure what you mean by that. The object id, visible in many debuggers? physical memory address? Again, if either of these things happened, Map would be broken.
If your actual problem is that some other reference to thing1 no longer has the contents of the reference in the map, then you are changing that external reference to thing1 somewhere.
Related
Java doc says HashMap.clone() returns a shallow copy.
So I expect that if I change the value of some key in original HashMap, the cloned one will also see the change. Thus, I have this:
public class ShallowCopy {
public static void main(String[] args) {
HashMap<Integer, String> map = new HashMap<Integer, String>();
map.put(2,"microsoft");
map.put(3,"yahoo");
map.put(1,"amazon");
// Type safety: Unchecked cast from Object to
// HashMap<Integer, String> Java(16777761)
Map<Integer, String> mClone =
(HashMap<Integer, String>)map.clone();
String previous = map.replace(3, "google");
System.out.println(previous); // yahoo
System.out.println(map.get(3)); // google
System.out.println(mClone.get(3)); // yahoo, but why?
}
}
In my code, I called HashMap.replace() and I see the value in map is changed from "yahoo" to "google".
Strangely, the last line prints the previous value when looking for it in mClone.
But the fact is it prints "yahoo" not "google" as I expected.
Where does it get wrong, please kindly fix my understandings?
Plus: I also got a compiler warning as I commented in my code(Java(16777761)), how to fix it?
TL;DR
After the cloning operation the values are simply references to the same object. So if you were to modify one reference in one map, the other would also be modified. But you didn't modify the object you replaced it. At that point it became distinct from a reference perspective.
Example
The clone operation is working just as you presumed. But you are interpreting the results incorrectly. Consider the following class.
class FooClass {
int a;
public FooClass(int a) {
this.a = a;
}
public void setA(int a) {
this.a = a;
}
#Override
public String toString() {
return a + "";
}
}
And now create a map and its clone.
HashMap<Integer, FooClass> map = new HashMap<>();
map.put(10, new FooClass(25));
HashMap<Integer,FooClass> mClone = (HashMap<Integer,FooClass>)map.clone();
The values of each key are the same object reference. As shown by the following:
System.out.println(System.identityHashCode(map.get(10)));
System.out.println(System.identityHashCode(mClone.get(10)));
prints
1523554304
1523554304
So if I modify one, it will modify the other.
The same was true for the String values of your maps. But when you replaced "yahoo" with "google" you didn't modify the String you replaced it with a different Object.
If I were to do the same for FooClass, here is the result.
System.out.println("Modifying same object");
mClone.get(10).setA(99);
System.out.println(map.get(10));
System.out.println(mClone.get(10));
prints
Modifying same object
99
99
But if I were to replace the object with a new one.
System.out.println("Replacing the instance");
FooClass previous = mClone.replace(10, new FooClass(1000));
System.out.println("Previous = " + previous);
System.out.println("map: " + map.get(10));
System.out.println("mClone: " + mClone.get(10));
prints
Replacing the instance
Previous = 99
map: 99
mClone: 1000
And this latter operation is what you did.
Method clone() creates a new map which gets populated with references to the values and keys contained in the source map, it's not a view of the initial map but an independent collection.
When you're calling map.replace(3, "google") a value mapped to the key 3 gets replaced with a new string, however the cloned map remains unaffected, it stills holds a reference to the same string "yahoo".
I have the below method, in which I am extracting the value from the entity and then setting it in map as a value of that map but my point is that for each key I am setting the value explicitly so if the count of keys grows that method code will also grow , can I make a common method based on approach Map.computeIfPresent, please advise how can I achieve both the things
private void setMap(AbcLoginDTO abcLoginDTO, Map<String, Object> getMap) {
getMap.put("XXNAME", abcLoginDTO.getUsername());
getMap.put("XX_ID", abcLoginDTO.getClientId());
getMap.put("RR_ID", abcLoginDTO.getUserId());
getMap.put("QQ_TIME", abcuserLoginDTO.getLocktime());
}
something like in this below approach I am thinking
static <E> void setIfPresent(Map<String, Object> map, String key, Consumer<E> setter, Function<Object, E> mapper) {
Object value = map.get(key);
if (value != null) {
setter.accept(mapper.apply(value));
}
}
but my point is that for each key I am setting the value explicitly so
if the count of keys grows that method code will also grow
You need to populate the Map with different values from the DTO, so you don't have other choices.
The method is long because you don't have a mapping between the key to add in the Map and the value to retrieve from the DTO.
You could write your code with a function such as :
static void setValueInMap(Map<String, Object> map, String key, Supplier<Object> mapper) {
map.put(key, mapper.get());
}
And use that :
Map<String, Object> map = ...;
AbcLoginDTO dto = ...;
setIfPresent(map, "keyUserName", dto::getUserName);
// and so for
But no real advantages.
Your second snippet has not at all relationship with the first one.
If i understand correctly, what you want to do is iterate over all of the object's members, get their value, and set them to a map according to their name. If so, then what you're looking for is called Reflection.
Every object can give you an array of its fields or methods (even private ones!) and then you can manipulate them using the Field / Method object.
Field[] members = AbcLoginDTO.class.getDeclaredFields();
Map<String, Object> values = new HashMap<>();
for(Field member : members) {
member.setAccessible(true);
values.put(member.getName(), member.get(abcLoginDTO));
}
What you end up with here, is a "Map representation" of your AbcLoginDTO instance. from here you can do with it what you want...
notice that i am "inspecting" the class itself in line 1, and then using the instance at line 6.
this code is not complete, but it's a start, and this can also be adapted to work for ANY object.
I don't know if I understood correctly, but if I did then that means all you need is a way to manually set different keys for the methods of your AbcLoginDTO class
If so then that can be done easily,
let's consider that your abcLoginDTO.getClientId() is always different for every AbcLoginDTO object:
private void setMap(AbcLoginDTO abcLoginDTO, Map<String, Object> getMap) {
getMap.put(Integer.toString(abcLoginDTO.getClientId())+"_NAME", abcLoginDTO.getUsername());
getMap.put(Integer.toString(abcLoginDTO.getClientId())+"_ID", abcLoginDTO.getClientId());
getMap.put(Integer.toString(abcLoginDTO.getClientId())+"_ID", abcLoginDTO.getUserId());
getMap.put(Integer.toString(abcLoginDTO.getClientId())+"_TIME", abcuserLoginDTO.getLocktime());
}
The program is as below:
Hash<String, HashMap<String, HashMap<String, String>>> data = new Hash<String, HashMap<String, HashMap<String, String>>>();
HashMap<String, String> person = new HashMap<String, String>();
person.put("Name", json.getString("Name"));
person.put("Contact", json.getString("Contact"));
person.put("Email", json.getString("Email"));
person.put("Rent Start", json.getString("Rent Start"));
person.put("Rent End", json.getString("Rent End"));
String period = json.getString("Rent Start").substring(0, 7) + " To " + json.getString("Rent End").substring(0, 7);
data.get(roomType).put(period, person);
Assume "data" is not empty in each level.
Problem occurs in the following step.
data.get(roomType).put(period, person);
When I do so, all values in the hashmap that in the second level become the person hashmap.
For example, in "roomtype1", there are 2 period, "2015-07 To 2016-07"
and "2015-07 To 2017-07".
When I run this code:
data.get(roomtype1).put("2015-07 To 2016-07", person);
the hashmap got by
data.get(roomtype1).get("2015-07 To 2017-07");
also becomes person.
May I know why?
(p.s. The original hashmap has 5 levels. I reduced it for this post because it will be easier to be understood)
Java objects are reference type.
data.get(key1) will get the hashmap object in the second level. with that object you are adding one more object into it.
When I do so, all values in the hashmap that in the second level
become the addition hashmap.
What does data.get(roomType) ? Is it doing something like:
public V get(K key) {
V actual = super.get(key);
if (null == actual) {
actual = getANewV();
super.put(key, actual);
}
return actual;
}
And are you sure that the getANewV() always returns a new instance and not the same (which would explains all values in the hashmap that in the second level become the addition hashmap).
And your need already exists in the matter of Multimap (see Guava). You should probably see if that work for you.
Beside, I'd personally use object rather than multiple layer of maps.
EDIT
I've tried this HashMap with multiple values under the same key, and my hashMap now looks like this HashMap<String, List<Place>> placeMap = new HashMap<>();
Also tried to put Object instead of Place(place is my superclass). But when I now create my subclasses and wants to add them to the HashMap I get:
The method put(String, List) in the type HashMap<String,List<Place>> is not applicable for the arguments (String, NamedPlace)
and
The method put(String, List) in the type HashMap<String,List<Place>> is not applicable for the arguments (String, DescPlace)
here is my adding which created the error:
NamedPlace p = new NamedPlace(x,y,answer,col,cat);
placeMap.put(answer, p);
DescPlace dp = new DescPlace(x,y,answer, desc, col, cat);
mp.add(dp);
placeMap.put(answer, dp);
NamedPlace and DescPlace are both subclasses to Place, and I want them both in the same HashMap..
OP
I'm working on a little project here. The thing is that I need to use a HashMap instead of a ArrayList on this part of the project because HashMap is alot faster for searching. I've created a HashMap like this:
HashMap<String, Object> placeMap = new HashMap<>();
The String is the name of the Object, but the thing is that more than one object can have the same name. So I search for a object in my searchfield and I want to store all those objects that has that name into an ArrayList so I can change info in just them.
The object have alot of different values, like name, position, some booleans etc.
Do I need to create a HashCode method into my object class which shall create a unique hashcode?
When using a standard Map<String, List<YourClassHere>> instance, it is important to remember that the map's values for each entry will be a List<YourClassHere>, and will not handle it in any special way. So in your case, if you have
private Map<String, List<Place>> placeMap = new HashMap<>();
Then to store values you will need to do as follows:
NamedPlace p = new NamedPlace(x,y,answer,col,cat);
List<Place> list = placeMap.get (answer);
list.add(p);
However, this piece of code has some underlying problems.
It doesn't take into account that answer might not be present in placeMap.
It assumes that there's always a List<Place> instance for each key you query.
So the best way to fix those potential problems is to do as follows (Java 7 and later):
NamedPlace p = new NamedPlace(x,y,answer,col,cat);
if (placeMap.containsKey (answer) && placeMap.get (answer) != null) {
placeMap.get (answer).add(p);
} else {
List<Place> list = new ArrayList<Place> (); // ..or whatever List implementation you need
list.add (p);
placeMap.put (answer, list);
}
If you want to scna through the list of places, the code would look like this:
if (placeMap.containsKey (key) && placeMap.get (answer) != null) {
for (Place p: placeMap.get (key)) {
// Do stuff
}
}
Below is data from 2 linkedHashMaps:
valueMap: { y=9.0, c=2.0, m=3.0, x=2.0}
formulaMap: { y=null, ==null, m=null, *=null, x=null, +=null, c=null, -=null, (=null, )=null, /=null}
What I want to do is input the the values from the first map into the corresponding positions in the second map. Both maps take String,Double as parameters.
Here is my attempt so far:
for(Map.Entry<String,Double> entryNumber: valueMap.entrySet()){
double doubleOfValueMap = entryNumber.getValue();
for(String StringFromValueMap: strArray){
for(Map.Entry<String,Double> entryFormula: formulaMap.entrySet()){
String StringFromFormulaMap = entryFormula.toString();
if(StringFromFormulaMap.contains(StringFromValueMap)){
entryFormula.setValue(doubleOfValueMap);
}
}
}
}
The problem with doing this is that it will set all of the values i.e. y,m,x,c to the value of the last double. Iterating through the values won't work either as the values are normally in a different order those in the formulaMap. Ideally what I need is to say is if the string in formulaMap is the same as the string in valueMap, set the value in formulaMap to the same value as in valueMap.
Let me know if you have any ideas as to what I can do?
This is quite simple:
formulaMap.putAll(valueMap);
If your value map contains key which are not contained in formulaMap, and you don't want to alter the original, do:
final Map<String, Double> map = new LinkedHashMap<String, Double>(valueMap);
map.keySet().retainAll(formulaMap.keySet());
formulaMap.putAll(map);
Edit due to comment It appears the problem was not at all what I thought, so here goes:
// The result map
for (final String key: formulaMap.keySet()) {
map.put(formulaMap.get(key), valueMap.get(key));
// Either return the new map, or do:
valueMap.clear();
valueMap.putAll(map);
for(Map.Entry<String,Double> valueFormula: valueMap.entrySet()){
formulaMap.put(valueFormula.getKey(), valueFormula.value());
}