Home > Back-end >  Java Collectors Reducing is having NullPointer when dealing with Double values
Java Collectors Reducing is having NullPointer when dealing with Double values

Time:09-09

Here a1,a2,a4,a5 are having same TransId, and CountryCode I want to aggregate them by adding CurrencyUSD, and ExccessAmount achieving a less time-consuming way as I will be dealing with extensive data. I am using grouping and reducing but having NullPointor exception.

public static void prepareList() {      
    
    Author a1 =new Author();
    Author a2 =new Author();
    Author a3 =new Author();
    Author a4 =new Author();
    Author a5 =new Author();
    Author a6 =new Author();
    Author a7 =new Author();
    
    a1.setTransId("1111111");
    a1.setCountryCode("US");
    a1.setCurrencyUSD(1000.00);
    a1.setExccessAmount(50.0);
    
    a2.setTransId("1111111");
    a2.setCountryCode("US");
    a2.setCurrencyUSD(1000.00);
    a2.setExccessAmount(50.0);
    
    a3.setTransId("1111111");
    a3.setCountryCode("IN");
    a3.setCurrencyUSD(1000.00);
    a3.setExccessAmount(1000.00);
    
    a4.setTransId("222222");
    a4.setCountryCode("US");
    a4.setCurrencyUSD(1000.00);
    a4.setExccessAmount(1000.00);
    
    a5.setTransId("222222");
    a5.setCountryCode("US");
    a5.setCurrencyUSD(1000.00);
    a5.setExccessAmount(1000.00);
    
    a6.setTransId("3333333");
    a6.setCountryCode("US");
    a6.setCurrencyUSD(1000.00);
    a6.setExccessAmount(1000.00);
    
    a7.setTransId("444444");
    a7.setCountryCode("US");
    a7.setCurrencyUSD(1000.00);
    a7.setExccessAmount(1000.00);
    
    Collection<Author> arrayListofObjects = new ArrayList<Author>();

    arrayListofObjects.add(a1);
    arrayListofObjects.add(a2);
    arrayListofObjects.add(a3);
    arrayListofObjects.add(a4);
    arrayListofObjects.add(a5);
    arrayListofObjects.add(a6);
    arrayListofObjects.add(a7);

    Map<String, Author> mapList2 = arrayListofObjects.stream().collect(Collectors.groupingBy(authorObj -> 
      authorObj.getTransId()   "_"   authorObj.getCountryCode(), Collectors.reducing(new Author(),(x,y)->{ 
      y.setCurrencyUSD(Double.sum(y.getCurrencyUSD(),x.getCurrencyUSD())); 
      y.setExccessAmount(Double.sum(y.getExccessAmount(), x.getExccessAmount())); 
      return y; 
    })));
        
}

public class Author { 
private String name; 
private String address; 
private String transId; 
private String countryCode; 
private Double exccessAmount; 
private Double currencyUSD; 

public String getTransId() { 
return transId; 
} 
public void setTransId(String transId) { 
this.transId = transId; 
} 
public String getCountryCode() { 
return countryCode; 
} 
}

Exception in thread "main" java.lang.NullPointerException at Examples.App.lambda$1(App.java:206) at java.base/java.util.stream.Collectors.lambda$reducing$42(Collectors.java:877) at java.base/java.util.stream.Collectors.lambda$groupingBy$53(Collectors.java:1136) at java.base/java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169) at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1655) at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)

CodePudding user response:

/**
    * Returns a {@code Collector} which performs a reduction of its
    * input elements under a specified {@code BinaryOperator} using the
    * provided identity.
    *
    * @apiNote
    * The {@code reducing()} collectors are most useful when used in a
    * multi-level reduction, downstream of {@code groupingBy} or
    * {@code partitioningBy}.  To perform a simple reduction on a stream,
    * use {@link Stream#reduce(Object, BinaryOperator)}} instead.
    *
    * @param <T> element type for the input and output of the reduction
    * @param identity the identity value for the reduction (also, the value
    *                 that is returned when there are no input elements)
    * @param op a {@code BinaryOperator<T>} used to reduce the input elements
    * @return a {@code Collector} which implements the reduction operation
    *
    * @see #reducing(BinaryOperator)
    * @see #reducing(Object, Function, BinaryOperator)
    */

copyed the comments on reducing method, the first param you inputed is a new Authoer(),the new Author will be used on the first time reducing. which means your lambda function (x,y)->{...} will be applyed with (x=new Author(),y=a1). the x has only null fields. so you could try to set default value ,or null check to avoid

CodePudding user response:

Object.requireNonNull

You can use Object.requireNonNull(T,String) to validate if your references are null. Provided message can help in identifying the culprit. As I've said in the comments it's hiding somewhere in your data among exccessAmount and currencyUSD values.

Map<String, Author> autorById = arrayListofObjects.stream()
    .collect(Collectors.groupingBy(
        authorObj -> authorObj.getTransId()   "_"   authorObj.getCountryCode(),
        Collectors.reducing(
            new Author("", "", 0D, 0D),
            (y, x) -> {
            y.setCurrencyUSD(Double.sum(y.getCurrencyUSD(), Objects.requireNonNull(x.getCurrencyUSD(),"currencyUSD of is Null"   x)));
            y.setExccessAmount(Double.sum(y.getExccessAmount(),  Objects.requireNonNull(x.getExccessAmount(),"exccessAmount of is Null"   x)));
            return y;
        }
    )));

Mutable reduction vs. Immutable reduction

There's another issue, obscured by the exception you're getting.

Your code is broken due to the way you're performing reduction. Collectors.reducing is meant for immutable reduction, i.e. for folding operation in which every reduction step results in creation of a new immutable object like Integer, BigDecimal, etc. But you've provided a mutable identity and performing mutation of this object while folding the stream instead of creating a new one.

As the result of that, the identity object would be created only once per thread. This single instance Author would be used for accumulation and reference to it end up in each and every value. Hence, the result would be incorrect.

To address this issue you need either:

  • generate a new Author while accumulating values with Collectors.reducing;

  • use mutable reduction.

Here's an example how to implement mutable reduction using a custom collector created via Collector.of():

Map<String, Author> autorById = arrayListofObjects.stream()
    .collect(Collectors.groupingBy(
        authorObj -> authorObj.getTransId()   "_"   authorObj.getCountryCode(),
        Collector.of(
            () -> new Author("", "", 0D, 0D),
            (Author result, Author next) -> {
                result.setCurrencyUSD(Double.sum(result.getCurrencyUSD(), Objects.requireNonNull(next.getCurrencyUSD(), "currencyUSD of is Null"   next)));
                result.setExccessAmount(Double.sum(result.getExccessAmount(), Objects.requireNonNull(next.getExccessAmount(), "exccessAmount of is Null"   next)));
            },
            (left, right) -> {
                left.setCurrencyUSD(Double.sum(left.getCurrencyUSD(), right.getCurrencyUSD()));
                left.setExccessAmount(Double.sum(left.getExccessAmount(), right.getExccessAmount()));
                return left;
            })
    ));
  •  Tags:  
  • java
  • Related