[SOLVED] Unexpected behaviour of Apache Commons CollectionUtils addAll(Collection collection, Object[] elements)

Issue

This Content is from Stack Overflow. Question asked by Dorian Feyerer

Brief description:

Basically, inside my code i want to add a List <String> to every <key> inside a ListValuedMap <String, List<String>> as a <value>. I did some testing on the created ListValuedMap spreadNormal with

System.out.println(spreadNormal.keySet());
System.out.println(spreadNormal.values());

and it showed me, that the keys are inside the map (unsorted), but the corresponding values are empty.
I am deleting the inserted List <String> after using Collections.addAll(String, List<String>) with list.clear() after each loop.

I would have expected, that indeed a copy of these values stay in my ListValuedMap but my results are:

[Agios Pharmaceuticals Inc., Vicor Corp., EDP Renov´┐Żveis S.A., Envista Holdings Corp., JENOPTIK AG,...]
[[], [], [], [], [], ...]

Can you provide some explanations on that? Is the default behaviour of the Collections.addAll method to copy the reference to an object, instead of the object itself?

The corresponding code section is highlighted with // ++++++++++

Full code example (Eclipse):

import java.io.*;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;
import java.util.*;

import org.apache.commons.collections4.ListValuedMap;
import org.apache.commons.collections4.MultiSet;
import org.apache.commons.collections4.multimap.ArrayListValuedHashMap;


public class Webdata {
    
    public static void main(String[] args) throws IOException
        {
            long start = System.currentTimeMillis();
            
            parseData();
            
            System.out.println(secondsElapsed(start)+" seconds processing time");
            
        };
            
    private static void parseData() throws IOException
        {
            
            List <String> subdirectories = new ArrayList<>();
            String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
            
            String errorMessage1 = "String formatting problem";
                String errorMessage2 = "String object non existent";
                
                for (int i=0; i< chars.length(); i++)
                {
                    subdirectories.add("https://www.tradegate.de/indizes.php?buchstabe="+chars.charAt(i));
                }
                
                    
                List <String> stockMetadata = new ArrayList<>();
                
                ListValuedMap <String, List<String>> nonError = new ArrayListValuedHashMap<>();
                ListValuedMap <String, List<String>> numFormatError = new ArrayListValuedHashMap<>();
                ListValuedMap <String, List<String>> nullPointerError = new ArrayListValuedHashMap<>();
                
                ListValuedMap <String, List<String>> spreadTooHigh = new ArrayListValuedHashMap<>();
                ListValuedMap <String, List<String>> spreadNormal = new ArrayListValuedHashMap<>();
                
                int cap1 = 44;
                int cap2 = 56;
        
                for (int suffix = 0; suffix <chars.length(); suffix++) 
                {   
                Document doc = Jsoup.connect(subdirectories.get(suffix).toString()).get();
                Elements htmlTableRows = doc.getElementById("kursliste_abc").select("tr");
                
                htmlTableRows.forEach( tr-> 
                        {
                        String stockName    = tr.child(0).text();
                        String bid_price    = tr.child(1).text();
                        String ask_price    = tr.child(2).text();
                        String isin         = tr.child(0).selectFirst("a").absUrl("href").substring(cap1,cap2);
                        
//     +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

                        try 
                        {
                            if (calcSpread(bid_price, ask_price) < 5) 
                            {
                                Collections.addAll(stockMetadata, isin, bid_price, ask_price, calcSpread(bid_price, ask_price).toString());
                                spreadNormal.put(stockName,stockMetadata);  
        
                            }
                            
                            else if (calcSpread(bid_price, ask_price) > 5)
                            {
                                Collections.addAll(stockMetadata, isin, bid_price, ask_price, calcSpread(bid_price, ask_price).toString());
                                spreadTooHigh.put(stockName,stockMetadata); 
                                
                            }
                                stockMetadata.clear();
                                
                        }
                        catch (NumberFormatException e) 
                        {
                            Collections.addAll(stockMetadata, e.getMessage());
                            numFormatError.put(stockName, stockMetadata);
                            stockMetadata.clear();
                            
                        }
                        
                        catch (NullPointerException Ne) 
                        {
                            Collections.addAll(stockMetadata, Ne.getMessage());
                            nullPointerError.put(stockName, stockMetadata);
                            stockMetadata.clear();
                            
                        }   //end of try-catch

//     +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
                        
                 });        //end of for-each loop htmlTableRows
            }               //end of JSOUP method
            
            
            System.out.println(spreadNormal.keySet());
            System.out.println(spreadNormal.values());
            
    }                   //end of parseData()
    
    
    public static Float calcSpread (String arg1, String arg2) 
    {
        try 
        {
            Float bid = Float.parseFloat(arg1.replace("," , "."));
            Float ask = Float.parseFloat(arg2.replace("," , "."));
            Float spread = ((ask-bid)/ask)*100;
            
            return spread;
        }
        catch (NumberFormatException e)
        {
            return null;
        }
    }
    
    public static Long secondsElapsed(Long start) {
        
        Long startTime = start;
        Long endTime = System.currentTimeMillis();
        Long timeDifference = endTime - startTime;
        
        return timeDifference / 1000;
    }
        
    
}  //end of class

pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>TEST</groupId>
  <artifactId>TEST</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.7</maven.compiler.source>
    <maven.compiler.target>1.7</maven.compiler.target>
  </properties>
  
<dependencies>
  <dependency>
    <groupId>org.jsoup</groupId>
    <artifactId>jsoup</artifactId>
    <version>1.11.3</version>
  </dependency>
  
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-collections4</artifactId>
    <version>4.4</version>
</dependency>
  
  <dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.0</version>
</dependency>
</dependencies>

  <build>
    <sourceDirectory>src</sourceDirectory>
    <plugins>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8.1</version>
        <configuration>
          <release>18</release>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>



Solution

There is nothing wrong with Collections.addAll.

I believe you expect all of your maps to be ListValuedMap<String, String> which is basically a Map<String, List<String>>. As such, your ListValuedMap<String, List<String>> is a Map<String, List<List<String>>>.

Just update each of your maps to be like below so each key is mapped to a List<String>:

ListValuedMap<String, String> nonError = new ArrayListValuedHashMap<>();
ListValuedMap<String, String> numFormatError = new ArrayListValuedHashMap<>();
ListValuedMap<String, String> nullPointerError = new ArrayListValuedHashMap<>();

ListValuedMap<String, String> spreadTooHigh = new ArrayListValuedHashMap<>();
ListValuedMap<String, String> spreadNormal = new ArrayListValuedHashMap<>();

And then, instead of using ListValuedMap.put(K key, V value) you have to use ListValuedMap.putAll(K key, Iterable<? extends V> values) like this:

spreadNormal.putAll(stockName, stockMetadata);
spreadTooHigh.putAll(stockName, stockMetadata);
numFormatError.putAll(stockName, stockMetadata);
nullPointerError.putAll(stockName, stockMetadata);

The putAll method will iterate over the stockMetadata iterable and add the elements one by one into the map‘s underlying list.


This Question was asked in StackOverflow by Dorian Feyerer and Answered by Dmitry Khamitov It is licensed under the terms of CC BY-SA 2.5. - CC BY-SA 3.0. - CC BY-SA 4.0.

people found this article helpful. What about you?