2017-12-05 7 views
2

Java 8ストリームAPIを初めて使用しましたが、次の問題に使用する予定です。Java 8ストリーム:他のコレクタに基づくコレクタの定義

public class InputRecord { 
    private String name; 
    private Integer fieldA; 
    private BigDecimal fieldB; 

    public String getName() { 
     return name; 
    } 

    public void setName(String name) { 
     this.name = name; 
    } 

    public Integer getFieldA() { 
     return fieldA; 
    } 

    public void setFieldA(Integer fieldA) { 
     this.fieldA = fieldA; 
    } 

    public BigDecimal getFieldB() { 
     return fieldB; 
    } 

    public void setFieldB(BigDecimal fieldB) { 
     this.fieldB = fieldB; 
    } 
} 

ニーズに上記のもの4つのレコードを:

name | fieldA | fieldB 
---------------------- 
A | 100 | 1.1 
A | 150 | 2.0 
B | 200 | 1.5 
A | 120 | 1.3 

InputRecordのようになります。私はPOJOはnamefieldAを含むInputRecordと呼ばれていて、次の各行レコードを表すことができfieldB性質が言います名前でグループ化された2つのレコードに結合することができます。

  1. プロパティfieldAは、
  2. 合わせレコードfieldAfieldB両方の累積和を乗算した結果であるfieldCプロパティを含むが加算されるプロパティfieldB
  3. が加算されます。

したがって、上記の結果であろう:

name | sumOfFieldA | sumOfFieldB | fieldC (sumOfFieldA*sumOfFieldB) 
------------------------------------------------------------------- 
A | 370   | 4.4   | 1628 
B | 200   | 1.5   | 300 

は異なるPOJOはOutputRecordを組み合わせたレコードの各行レコードを表すことになると呼ばれる。

public class OutputRecord { 
    private String name; 
    private Integer sumOfFieldA; 
    private BigDecimal sumOfFieldB; 
    private BigDecimal fieldC; 

    public String getName() { 
     return name; 
    } 

    public void setName(String name) { 
     this.name = name; 
    } 

    public Integer getSumOfFieldA() { 
     return sumOfFieldA; 
    } 

    public void setSumOfFieldA(Integer sumOfFieldA) { 
     this.sumOfFieldA = sumOfFieldA; 
    } 

    public BigDecimal getSumOfFieldB() { 
     return sumOfFieldB; 
    } 

    public void setSumOfFieldB(BigDecimal sumOfFieldB) { 
     this.sumOfFieldB = sumOfFieldB; 
    } 

    public BigDecimal getFieldC() { 
     return fieldC; 
    } 

    public void setFieldC(BigDecimal fieldC) { 
     this.fieldC = fieldC; 
    } 
} 

いくつかの良いアプローチ/ソリューションは何InputRecordsのリストをOutputRecordsのリストに変換するには?

次のリンクは役立つだろうが、私は fieldCのための新しいコレクタを形成するために一緒に fieldAfieldBのためのコレクターを置くしようとして捕まってしまった場合、私は見ていた

Java 8 Stream: groupingBy with multiple Collectors

Collector<InputRecord, ?, Integer> fieldACollector = Collectors.summingInt(InputRecord::getFieldA); 
Collector<InputRecord, ?, BigDecimal> fieldBCollector = Collectors.reducing(BigDecimal.ZERO, InputRecord::getFieldB, BigDecimal::add); 

List<Collector<InputRecord, ?, ?>> collectors = Arrays.asList(fieldACollector, fieldBCollector); // need a fieldCCollector object in the list 

collectorsオブジェクトですその後、 complexCollectorオブジェクトを作成するために使用することができます(上記のリンクのTagir Valeevによる承認済みの回答に従って)。

+0

なぜあなたは飛行機でそれを計算することができるときに 'fieldC'を必要としますか? – shmosel

+0

POJOに関する詳細情報を含めることができますか?それは変更可能ですか?それはゲッターを持っていますか?コンストラクタはどのように見えますか? – shmosel

+0

ありがとう私は詳細を追加しました。 POJOを変更することができます – Mark

答えて

3

私にとっては、最もクリーンな方法はカスタムコレクタを構築することです。そこ複数行のコードはここにあるが、あなたは法の下でそれを隠すことができますので、あなたの究極の操作は次のようになります。

Collection<OutputRecord> output = List.of(first, second, thrid, fourth) 
      .stream() 
      .parallel() 
      .collect(toOutputRecords()); 

が実際toOutputRecordsながら次のようになります。

private static Collector<InputRecord, ?, Collection<OutputRecord>> toOutputRecords() { 
    class Acc { 

     Map<String, OutputRecord> map = new HashMap<>(); 

     void add(InputRecord elem) { 
      String value = elem.getName(); 
      // constructor without fieldC since you compute it at the end 
      OutputRecord record = new OutputRecord(value, elem.getFieldA(), elem.getFieldB()); 
      mergeIntoMap(map, value, record); 
     } 

     Acc merge(Acc right) { 
      Map<String, OutputRecord> leftMap = map; 
      Map<String, OutputRecord> rightMap = right.map; 

      for (Entry<String, OutputRecord> entry : rightMap.entrySet()) { 
       mergeIntoMap(leftMap, entry.getKey(), entry.getValue()); 
      } 
      return this; 
     } 

     private void mergeIntoMap(Map<String, OutputRecord> map, String value, OutputRecord record) { 

      map.merge(value, record, (left, right) -> { 
       left.setSumOfFieldA(left.getSumOfFieldA() + right.getSumOfFieldA()); 
       left.setSumOfFieldB(left.getSumOfFieldB().add(right.getSumOfFieldB())); 

       return left; 
      }); 
     } 

     public Collection<OutputRecord> finisher() { 
      for (Entry<String, OutputRecord> e : map.entrySet()) { 
       OutputRecord output = e.getValue(); 
       output.setFieldC(output.getSumOfFieldB().multiply(BigDecimal.valueOf(output.getSumOfFieldA()))); 
      } 
      return map.values(); 
     } 

    } 
    return Collector.of(Acc::new, Acc::add, Acc::merge, Acc::finisher); 
} 
0

InputRecordsのリストからOutputRecordsのリストを生成するために、結合および集計関数を使用することができます。

+1

OPは 'Map'を望んでいません。少なくとも' result.values() 'を返すことができます。 https://stackoverflow.com/a/47649011/1059372 – Eugene

1

あなたが使用することができますStream.reduce(..)を使用して、2つのレコードを1つのレコードに変換します。これは、JVMによってガベージコレクションされる必要がある一時的なオブジェクトの束を作成します。

Collection<InputRecord> input = Arrays.asList(
     new InputRecord("A", 100, new BigDecimal(1.1)), 
     new InputRecord("A", 150, new BigDecimal(2.0)), 
     new InputRecord("B", 200, new BigDecimal(1.5)), 
     new InputRecord("A", 120, new BigDecimal(1.3))); 

Collection<OutputRecord> output = input.stream() 
     // group records for particular Name into a List 
     .collect(Collectors.groupingBy(InputRecord::getName)) 
     .values().stream() 
     // Reduce every List to a single records, performing summing 
     .map(records -> records.stream() 
       .reduce((a, b) -> 
         new InputRecord(a.getName(), 
           a.getFieldA() + b.getFieldA(), 
           a.getFieldB().add(b.getFieldB())))) 
     .filter(Optional::isPresent) 
     .map(Optional::get) 
     // Finally transform the InputRecord to OutputRecord 
     .map(record -> new OutputRecord(record.getName(), 
       record.getFieldA(), 
       record.getFieldB(), 
       record.getFieldB().multiply(new BigDecimal(record.getFieldA())))) 
     .collect(Collectors.toList()); 
+0

おそらく 'filter(Optional :: isPresent)の代わりに' InputRecord(0、BigDecimla.ZERO) 'というアイデンティティを使うことができます.map(オプション) :: get) 'では、オブジェクトの作成方法が少なくなるので、変更可能なコレクタを使用することをお勧めします。これをさらに良くするために、さらに2つのメソッドを作成することができます: 'InputRecord join(InputRecord left、InputRecord right)...'と 'OutputRecord transform(InputRecord in)'の形式でもう1つ。そのため、このパイプラインははるかに短くなり、はるかに読みやすくなります。まだプラス1 – Eugene

関連する問題