Java - Maps inside a Map or Key Object? - java

Hi I am having 3 attributes (String) that are needed to identify a certain Object.
What is the better solution:
using a key object as those post by Pierre and Joachim Sauer like this:
public class MyKey{
private String one;
private String two;
private String three;
public MyKey(Sting one,String two, String three){
this.one=one;
this.two=two;
this.three=three;
}
//getter only
#Override
public int hashcode(){
// creating Hashcode
}
#Override
public boolean equals(Object o){
// comparing
}
}
=> Map<MyKey, Object>
or using a Map inside a Map inside a Map:
=> Map<String, Map<String, Map<String, Object>>>

I think you should go with the first option. Reason being that it is more clean and flexible. Imagine a few months down the line you need to add an extra element to your key, your structure would now be invalid since it requires one more level of nesting, not to mention making it more complex.

I would go with the key class as you have specified. It's much clearer and has the advantage of being able to name the keys.

You said
I am having 3 attributes (String) that are needed to identify a
certain Object.
That immediately indicates to me that your key consists of three strings, and your first solution is correct and intuitive.

just implement equals
one.equals(o) || two.equals(o) || three.equals(o)
and hashcode
one.hashCode() ^ two.hashCode() ^ three.hashCode()

If you always need the 3 keys you should use a key object (tuple-3) for performance reasons. One object to rule them all...
Also make each key field final if you want better concurrency (you could even pre-compute the hash...)

Related

Specify which function was used on string within a map

I have a map of strings that all need to be modified by one of several functions based on some conditions.
My problem is that once I return the map, I need someway to identify which function was used on that specific string. My current idea is to add an identfier at the end of each string so I can check.
Ex. In the case the function "HPV" was used : mystring = mystring + "HPV"
Then on the return end, I can check the end of the string and know which function was used.
Is this an appropriate solution or is there a more efficient way that does not involve checking and modifying every single string?
You should probably use an object that can return both the actual String and the identifier of the function that did the work on it.
If the functions that will do the work are all known at compile-time, they can be identified by an enum, otherwise just another String that holds their ID.
Here is a simple suggestion on how to do that:
enum StringModifier {
HPV, OTHER
}
record ModifiedString(
String string,
StringModifier modifier
) {}
main(String[] args) {
Map<String, String> originalMap = ...
Map<String, ModifiedString> modifications = transform(originalMap, ...)
}
This avoids allocating new Strings and "dirty-ing" them, which can give you trouble later to clean them up as you probably want to make sure you know what the actual result String should look like.

How to create a Vector where each entry is a structure with 2 fields

I am trying to create a data structure in Java that is a Vector into which some information about an unknown number of entities will go from a database. When it comes to this information, I only care about 2 fields. Also, it is required that when I iterate through this Vector, I can extract these two fields (say, String) in pairs. Schematically speaking,
String s1 = Vector[1].Field1, String s2 = Vector[1].Field2
Is this even possible? Does anyone know a more efficient way to achieve this?
Note: I would like to keep it in a single Vector because I pass it to another class for processing.
Use
public class Entry {
public String field1;
public String field2;
}
List<Entry> vector = ...;
Why not using Map<String, String>
But map will be unique keys
Also you can create your own Class and pass it to the Vector<yourclass>
Here's how I would do it :
Create an object Pair with 2 attributes : field 1, field 2
Add this object to your vector.
Use a Vector of maps, with keys value1 and value2. Vector<Map<String, String>>.
Or define a new object with two attributes ( I suggest the second option )

Why doesn't the Java library provide `HashSet.get(Object o)` and `HashMap.getKey(Object o)`

I know this question has already been asked on SO a couple of times, but I still haven't found a satisfying solution, and I'm unsure which way to go. The question is:
Why doesn't the Java library provide HashSet.get(Object o) and HashMap.getKey(Object o) methods that return the actual instance in the map providing an equal instance? Example:
// Retrieve a house with ID=10 that contains additional information like size,
// location and price.
houses.get(new House(10));
I think the best answer can be found here. So here's a mixture of answers that I'm aware of:
Why would you need the instance when you already have it? It doesn't make sense to try to get the same object you already have. The object has an identifier (which controls it's equality to other Foo types) plus any number of other fields that do not contribute to it's identity. I want to be able to get the object from the Set (which includes the extra fields) by constructing an 'equal' Foo object (text is taken from one of the comments). -> no answer
Iterate the Collection and search for the instance using equals(). This uses linear search and is extremely slow in big collections. -> bad answer
Use a HashMap instead of a HashSet I don't need a map and I think it's not adequate to return a map in a method like getHouses(). The getter should return a Set and not a Map.
Use TreeSet.ceiling - don't know
This hacky code below (Java 8 HashSet only) uses reflection and provides the missing functionality. I did not find something like this in other answers (no surprise). This could have been an acceptable solution if the target Java version is defined and future Java versions would finally provide such a method, now that we have default methods for interfaces. One could think of default E get(E o){stream().filter(e->e.equals(o)).findAny().orElse(null);}
// Alternative: Subclass HashSet/HashMap and provide a get()/getKey() methods
public static <T> T getFromSet(HashSet<T> set, T key) throws Exception {
Field mapField = set.getClass().getDeclaredField("map");
mapField.setAccessible(true);
HashMap<T, Object> map = (HashMap) mapField.get(set);
Method getNodeMethod = map.getClass().getDeclaredMethod("getNode",
int.class, Object.class);
getNodeMethod.setAccessible(true);
return (T) ((Map.Entry) getNodeMethod.invoke(map, key.hashCode(),
key)).getKey();
}
Here are the questions:
Is the best solution the use of HashMap<House, House> instead of HashSet<House>?
Is there another library out there that provides this functionality and supports concurrent access?
Do you know of a bug addressing this feature?
Similar questions on SO:
Why doesn't java.util.HashSet have a get(Object o) method?
Java: Retrieving an element from a HashSet
Why does the java.util.Set interface not provide a get(Object o) method?
The reason this behaviour hasn't been catered for is that creating a House instance with invalid data just to obtain one with valid data is really poor design.
Composition is the correct solution here:
/** immutable class containing all the fields defining identity */
public final class HouseIdentifier {
private final String id;
}
public class House {
private final HouseIdentifier id;
/** all the mutable, ephemeral properties of the house should go here */
private int size;
private Person owner;
}
If you design your class hierarchy like this, then all you need for your lookups is a simple and straightforward Map<HouseIdentifier, House>.
Map doesn't have a getKey(Object o) because it's not a bidirectional map. It only maps keys to values, not the other way around.
Set doesn't have get(Object o) because that's the job for a Map.
Mapping a House object to another House object is just bad design on your part. You want to get a House by an address or a number or similar, so you have one or more maps that give you those mappings (or more likely, a database). Your question makes sense only to you, because you're thinking "in the wrong way".
Your "wrong way of thinking" is evidenced by your statement
I don't need a map and I think it's not adequate to return a map in a
method like getHouses(). The getter should return a Set and not a Map.
I have never heard that a getter can't return a Map. Although I would probably name it getHouseMap(). You're creating a huge problem out of a trivial little issue. This is the job for a database anyways, so your dataset must be quite small.

Keeping track of pairs of Strings

My current project requires me to utilize pairs of Strings for various use cases. I am currently using the Apache Lang3 Pair tuple to handle this case. The current implementation looks like the following
private List<Pair<String, String>> configParameters;
private List<Pair<String, String>> sensorReadingMeasures;
After discussion with the rest of the team it was decided that because we would need to expose these pairs of strings it would be best to find native Java solution.
I have attempted to find a solution using the AbstractMap in the java util library but I haven't had much luck being able to instantiate it, and a google search hasn't provided much information on helping me along.
I have attempted to recreate the above by doing,
AbstractMap<String, String> pair = new AbstractMap<String, String>();
I would be able to pull the required information using the keyset() function and pulling the needed information out by the get() function for each key value.
Beyond my instantiation problem this seems like a terribly inefficient way to get the same functionality, and I am curious if there is a better option. If there isn't can someone please provide an example on how to instantiate an AbstractMap appropriately.
If one of the two strings in each pair is unique, as your suggestion to use an AbstractMap seems to suggest, you may want to expose an API that returns Map<String,String>, and use an implementation of your choice for that interface (say, LinkedHashMap<String,String>).
The users of your class would be able to pull the pairs of Strings from the Map by using Map.Entry<String,String>, rather than calling a get for each key:
Map<String,String> getAllPairs() {
...
}
...
for (Map.Entry<String,String> pair : myClass.getAllPairs()) {
// You can use pair.getKey() and pair.getValue() here
}
Can you not just use a POJO:
public class Tuple {
public String oneString;
public String otherString;
}
You can have lists of them:
public List<Tuple> tuples = new ArrayList<Tuple>();
Or you can have Collections of them that can be searched if you:
#Override
public boolean equals(Object obj) {
// Code to handle non-Tuples and null goes here
// Code to handle null oneString values goes here (if you allow that)
return ((Tuple)that).oneString.equals(oneString);
}
#Override
public int hashCode() {
// handle null oneString values here
return oneString.hashCode();
}

Is it possible to have a hashmap with 4 objects?

Can I have an hashMap with say ID as my key and info, name, quantity as my values?
ok, say I have a class (Products) already that sets my variables, getters and setters. In my Invoice class, which is where the hashMap would be. Would I put like:
private HashMap<String, Products> keys = new HashMap<String, Products>
I'm not quite sure how to access the HashMap though. Say I implement a class that allows me to add and remove invoices from the HashMap, I do not know what the values would be:
keys.put(??value of id??,??not sure what goes here??);
Sure. Make another class that contains your info, name and quantity and put that as the value of your HashMap.
No, but the best way is to wrap the information you want to keep in the map in a class:
public class Info {
private String info;
private String name;
private int quantity;
...
public Info(String info, String name, int quantity) {
...
}
}
Then do this to put something in the map:
Info info = new Info("info", "name", 2);
Map map = new HashMap<Integer, Info>();
map.put(22, info);
And do this to get something out:
Info info = map.get(22)
How about HashMap<Integer, ArrayList<String>> ?
UPDATE: Please try to avoid this, this is a better approach.
Not exactly.
A Map defines a strictly 1 to 1 relationship between keys and values. One key in the map has one value.
If you want to associate multiple values with one key you need to do one of the following:
Define a Values class to represent the values as a single object; e.g. as per #Starkey's and #Javed's answers. Then the map becomes a Map<String, Values> (assuming that the key type is String).
Define the map as a Map<String,List<Object>> or Map<String,Object[]> and represent the values as an untyped list / array
Define the map as a Map<String,Properties> or Map<String,Map<String,Object>> and represent the values as the Java equivalent of an associative array.
Of these, the first option is both the safest (smallest chance of runtime errors), the most efficient and the best style.
(Aside: an Apache commons MultiMap might be considered as another possibility, but the conceptual model and APIs don't really match this use-case.)
Sure. Depending on how flexible your datastructe is you can use a Hashmap a la:
HashMap<IdType, List<String>>, with IdType String or Integer, depending on the Keys you like to use.
HashMap<IdType, String[]>
HashMap<IdType, YourObjectType>, with YourObjectType beeing a Object you defined yourself, holding the values you like
YourObjectType can of course be anything you can define as an Object. Also another HashMap if you like.
One of the concerns while using a Map would be use of hardcoded keys. If the key is a string, and the key changes. Can consider using a constant instead of a hardcoded string.
Having a dedicated class has the benefit of compiler to check for name changes. However, as mentioned in the earlier comments.. It can become a concern...
In my opinion both are feasible. We need to weigh which option is better depending on the situation
Create an object that encapsulates the four together. Something like:
public class Foo {
private String s1;
private String s2;
private int v3;
private MyObject obj1
// constructors, getters, helper functions.
}
I think MultiMap from google library could serve the purpose
https://google.github.io/guava/releases/19.0/api/docs/com/google/common/collect/Multimap.html
Multimap<String, String> map = ArrayListMultimap.create();
String key = "uniqueKey";
map.put(key, "value1");
map.put(key, "value2");
map.put(key, "value3");
System.out.println(map);//{uniqueKey=[value1, value2, value3]}
Of course, you could for example declare it like this: HashMap<Integer, HashMap<String,Object>> You use the outer hashmap to link your id with your inner HashMap, and in the inner one, you create keys "info", "name", "quantity" and associate values with them.
Of course, you could also use an ArrayList as the outer collection (it could be a better match for your ID: ArrayList<HashMap<String,Object>> that way you have indexed (id based) access to each of your "info", "name", "quantity" hashmap "records"
You could have ID as key and a List or Set (Collection in general) of objects as value.

Categories

Resources