Reputation: 121
I am trying to add value for the List which is stored in HashMap and that has one parent List.
When I try to do so I get "The method get in type is not compatible with the List"
I am trying the following code, logic is :
List < HashMap > txnvalues = new ArrayList < HashMap > ();
for (LinkedHashMap < String, Object > linkedHashMap: resultset) {
HashMap data = new HashMap < > ();
HashMap attrData = new HashMap < > ();
List values = new ArrayList < > ();
data.put("values", new ArrayList < > ());
attrData.put("attrID", linkedHashMap.get("ID"));
attrData.put("attrVal", linkedHashMap.get("VAL"));
String txnID = linkedHashMap.get("T_ID").toString();
if (!txnvalues.stream().anyMatch(list -> list.containsValue(txnID))) {
data.put("tID", linkedHashMap.get("T_ID"));
values.add(attrData);
data.put("Values", values);
txnvalues.add(data);
} else {
txnvalues.get("Values").add(attrData); // this Line throws error
}
}
Example :
[{
"tID":123,
"Values":[{attrID:1,attrVal:123}]
}]
//Here If linkedHashmap.get("T_ID") = 123 which matches with tID then I want to add data in the Values
[{
"tID":123,
"Values":[{attrID:1,attrVal:123},{attrID:11,attrVal:467}]
}]
//If it doesn't match then I want to create new Hashmap and update txnValues Like this
[{
"tID":123,
"Values":[{attrID:1,attrVal:123},{attrID:2,attrVal:3435}]
},
{
"tID":456,
"Values":[{attrID:2,attrVal:233}]
}
]
Upvotes: 3
Views: 1239
Reputation: 28988
I'm probably late with this answer. Nevertheless, I'll introduce a possible remedy accompanied by a detailed explanation.
At the first glance, such a deeply nested collection seems contrived and incomprehensible. But problems that you can see in this code aren't something unusual, they could be observed in many questions on StackOverflow, and in many repositories. The only difference is in concentration.
Let's try to examine it closely. A map is a data structure that is commonly misused by beginners because it allows to combine objects of different nature. I am pretty sure that provided code models something more or less tangible. Did you notice that PO tries to access an entry that has a string key called "id"? That's a clear indicator that collections here are used in place of objects.
If I say object graph can be far more complex, it probably wouldn't be something new. But how to reason about the code that is written in such a way?
Let's step aside for a moment and consider the following task:
It is very likely that somebody can come up with such a solution:
Map<String, List<Double>>
, where the key is a sailboat's name and the value is a list that contains displacement and weight;Only a couple of methods, and it seems that a separate class for a sailboat will allegedly increase the overall complexity and amount of code. That's a common delusion for many students. The creation of a separate class will provide a logical structure to the code and will pay off if you would wish to extend or reuse it. Note that not only attributes of the sailboat must belong to this class but also the methods that allow to compute sailboat's speed and compare sailboats based on it.
Decomposition is a skill and it has to be exercised. And for those of you who didn't realize from the beginning that a sailboat in the previous example has to be represented by an object, I advise to try the next exercise: describe a university, a candy shop, a grocery store, a cat, anything you like but without using objects. First, think about a couple of use-cases that entail accessing some properties of the elements of the system that you're trying to model. Then draw diagrams and write the code using warriors collections and arrays, pay attention that the more complex your system becomes, the more cumbersome become all nested maps and lists, which make you write your code like this:
map.get(something).get(something).add(somethingElse);
And then, when you see the problems, you are ready to implement the classes that make sense in your domain model and compare the two approaches.
Disclaimer: understanding decomposition is a crucial thing but class design is a very broad topic, there are lots of things to study in this area like classic principles and design patterns. But before diving into these topics, you have to have a firm understanding of decomposition and OOP. Without this knowledge even with an object-oriented approach, your solution could become convoluted and difficult to manage. But this is a step in the right direction. The fact alone that you are using an object-oriented language doesn't automatically make your solution object-oriented. It's a skill, and it has to be exercised.
It was a very long digression, now let's get to the point.
As I already said, I'm convinced that the post author had in mind some kind of natural use case. Instead of names that describe the system in this maze of data structures we can see only dump get()
and put()
. But there's a clue in the usage of map. An id as a key is a clear indicator that it has to be an object which is substituted by a map.
That is a start of a journey, I'll try to provide a scenario that makes sense (at least a bit) and pieces of a system that fits into a structure depicted in the scheme provided at the start of this post.
Let's consider an organization that sells something (I'm not trying to guess what was the author's intention, but providing a use case that will allow to reason about the code). There are a bunch of departments, each with a unique identifier.
Each department has a collection of products that it sells. Department gets different products from different suppliers. And in turn, each product has a unique id a collection of suppliers represented by plain string (it looks contrived, but keep in mind it's just an illustration of what the code does).
As a use-case, let's assume that the company launches a new product and it must be accessible in all its departments. The code checks whether the department has this product already, if not, the product will be added with a default set of suppliers, otherwise it merges the existing set of suppliers and the default one.
As you can see the code in the main method is very concise. Note that all the miscellanies of data structures are still there, but we are not accessing them directly. As the information expert principle suggests, this logic is hidden inside the objects. That makes this solution reusable and less error-prone.
public static void main(String[] args) {
// this list is a rough equivalent of the "List<Map<String, List<Map<String, Object>>>> txnvalues"
List<Department> departments =
List.of(new Department("dep11"), new Department("dep12"));
Product newProd = new Product("id123"); // a NEW Product with id = "id123"
newProd.addAllSuppliers(List.of("supplierA", "supplierB"));
for (Department dep: departments) { // launching the new Product
dep.mergeProduct(newProd);
}
}
public class Department {
private final String departmentId;
private final Map<String, Product> idToProduct;
public Department(String departmentName) {
this.departmentId = departmentName;
this.idToProduct = new HashMap<>();
}
public void mergeProduct(Product prod) {
idToProduct.merge(prod.getId(), prod, Product::merge);
}
public void mergeAllProducts(Iterable<Product> products) {
for (Product prod: products) {
mergeProduct(prod);
}
}
public void addProduct(Product prod) {
idToProduct.put(prod.getId(), prod);
}
public void addAllProducts(Iterable<Product> products) {
for (Product prod: products) {
addProduct(prod);
}
}
public String getId() {
return departmentId;
}
public Map<String, Product> getIdToProduct() {
return Collections.unmodifiableMap(idToProduct);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o instanceof Department other) {
return departmentId.equals(other.departmentId);
} else return false;
}
@Override
public int hashCode() {
return Objects.hash(departmentId);
}
}
public class Product {
private final String productId;
private final Set<String> suppliers;
public Product(String id) {
this.productId = id;
this.suppliers = new HashSet<>();
}
public boolean addSupplier(String newSup) {
return suppliers.add(newSup);
}
public boolean addAllSuppliers(Collection<String> newSup) {
return suppliers.addAll(newSup);
}
public Product merge(Product other) {
if (!this.equals(other)) throw new IllegalArgumentException();
Product merged = new Product(productId);
merged.addAllSuppliers(this.suppliers);
merged.addAllSuppliers(other.suppliers);
return merged;
}
public String getId() {
return productId;
}
public Set<String> getSuppliers() {
return Collections.unmodifiableSet(suppliers);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o instanceof Product other) {
return this.productId.equals(other.productId);
} else return false;
}
@Override
public int hashCode() {
return Objects.hash(productId);
}
}
Further steps:
Upvotes: 4
Reputation: 11848
In the else
block, you call get
method of txnvalues
which a list of HashMap
s and thus it expects an integer index. I believe you assume that at this point you've got a reference to the HashMap
to which you would add the values. But you don't.
So, you need to find the index where to add the values, which means you have to look through the txnvalues
list again.
For this reason, you should use a different approach:
txnvalues.stream()
.filter(m -> m.get("tID").equals(txnID))
.findFirst()
.ifPresentOrElse(
m -> m.get("Values").add(attrData),
() -> {
HashMap data = new HashMap<>();
// Other stuff to fill the data
txnvalues.add(data);
}
);
Here .filter(m -> m.get("tID").equals(txnID))
corresponds to your .anyMatch(list -> list.containsValue(txnID))
(the parameter list
is actually instance of HashMap
).
I changed the condition: according to your data sample, you looking for Map
which has txnID
value for the "tID"
key, therefore getting the value of this key is faster than looking through all the values in the HashMap
. (It may return null
.)
So filter
will return only the entries which contain match the required value of the "tID"
key. Then .findFirst()
“returns” the reference to that HashMap
. Now .ifPresentOrElse
performs the actions you want:
m.get("Values").add(attrData)
into the list; this corresponds your one line of code in the else
block;if
block: if nothing is found, create the new instance.Upvotes: 1
Reputation: 141
I decided to parameterize all of your various iterables. Below is the parameterized code.
List<HashMap<String, List<HashMap<String, Object>>>> txnvalues = new ArrayList<HashMap<String, List<HashMap<String, Object>>>>();
for (LinkedHashMap<String, Object> linkedHashMap : resultset) {//Error here
HashMap<String, List<HashMap<String, Object>>> data = new HashMap<String, List<HashMap<String, Object>>>();
HashMap<String, Object> attrData = new HashMap<String, Object>();
List<HashMap<String, Object>> values = new ArrayList<HashMap<String, Object>>();
data.put("values", new ArrayList<>());
attrData.put("attrID", linkedHashMap.get("ID"));
attrData.put("attrVal", linkedHashMap.get("VAL"));
String txnID = linkedHashMap.get("T_ID").toString();
if (!txnvalues.stream().anyMatch(list -> list.containsValue(txnID))) {
data.put("tID", linkedHashMap.get("T_ID")); //Error here
values.add(attrData);
data.put("Values", values);
txnvalues.add(data);
} else {
txnvalues.get("Values").add(attrData); //Error here
}
}
First, you have multiple errors in your code such as trying to put a String key and Object value into data
, which is a HashMap that only takes a String key and a List(of HashMaps of Strings and Objects) value. Another such is trying to get an item from txnvalues
by a String, when txnvalues
is a List and therefore requires an integer index parameter.
Second, you have a variable here which is never defined: resultset
. We don't know what it is or how it is used, since it's never referenced elsewhere.
Third, there are many many ways to handle nested sets. This >-> List<HashMap<String, List<HashMap<String, Object>>>>
is simply horrible.
Please re-write your code in a way that is readable, parameterized, and can properly compile without errors. Just parameterizing will help you keep track of which iterables take which parameters and will help prevent the problem you had when you came here for help.
Upvotes: 4