Java8Streams:如何调用Collection.stream()方法一次并检索具有不同字段的多个聚合值的数组

Java8Streams:如何调用Collection.stream()方法一次并检索具有不同字段的多个聚合值的数组,java,java-8,java-stream,Java,Java 8,Java Stream,我从Java8中的流API开始 以下是我使用的Person对象: public class Person { private String firstName; private String lastName; private int age; private double height; private double weight; public Person(String firstName, String lastName, int ag

我从Java8中的流API开始

以下是我使用的Person对象:

public class Person {

    private String firstName;
    private String lastName;
    private int age;
    private double height;
    private double weight;

    public Person(String firstName, String lastName, int age, double height, double weight) {
      this.firstName = firstName;
      this.lastName = lastName;
      this.age = age;
      this.height = height;
      this.weight = weight;
    }

    public String getFirstName() {
      return firstName;
    }
    public String getLastName() {
      return lastName;
    }
    public int getAge() {
      return age;
    }
    public double getHeight() {
      return height;
    }
    public double getWeight() {
      return weight;
    }

  }
下面是我的代码,它初始化了一个对象列表,并通过一个特定的名字、最大年龄和最小身高、平均体重来获得对象的数量,最后创建一个包含以下值的对象数组:

List<Person> personsList = new ArrayList<Person>();

personsList.add(new Person("John", "Doe", 25, 1.80, 80));
personsList.add(new Person("Jane", "Doe", 30, 1.69, 60));
personsList.add(new Person("John", "Smith", 35, 174, 70));

long count = personsList.stream().filter(p -> p.getFirstName().equals("John")).count();
int maxAge = personsList.stream().mapToInt(Person::getAge).max().getAsInt();
double minHeight = personsList.stream().mapToDouble(Person::getHeight).min().getAsDouble();
double avgWeight = personsList.stream().mapToDouble(Person::getWeight).average().getAsDouble();

Object[] result = new Object[] { count, maxAge, minHeight, avgWeight };
System.out.println(Arrays.toString(result));
我以前问过非常类似的问题:但这次我不能使用
summaryStatistics()
方法,因为我使用不同的字段(年龄、身高、体重)来检索聚合值


编辑2016-01-07

我测试了
TriCore
Tagir Valeev
的解决方案,并计算了每个解决方案的运行时间

看来
TriCore
解决方案比
Tagir Valeev
更有效

与我的解决方案(使用多个流)相比,Tagir Valeev的解决方案似乎节省不了多少时间

这是我的测试课:

public class StreamTest {

  public static class Person {

    private String firstName;
    private String lastName;
    private int age;
    private double height;
    private double weight;

    public Person(String firstName, String lastName, int age, double height, double weight) {
      this.firstName = firstName;
      this.lastName = lastName;
      this.age = age;
      this.height = height;
      this.weight = weight;
    }

    public String getFirstName() {
      return firstName;
    }

    public String getLastName() {
      return lastName;
    }

    public int getAge() {
      return age;
    }

    public double getHeight() {
      return height;
    }

    public double getWeight() {
      return weight;
    }

  }

  public static abstract class Process {

    public void run() {
      StopWatch timer = new StopWatch();
      timer.start();
      doRun();
      timer.stop();
      System.out.println(timer.getTime());
    }

    protected abstract void doRun();

  }

  public static void main(String[] args) {
    List<Person> personsList = new ArrayList<Person>();

    for (int i = 0; i < 1000000; i++) {
      int age = random(15, 60);
      double height = random(1.50, 2.00);
      double weight = random(50.0, 100.0);
      personsList.add(new Person(randomString(10, Mode.ALPHA), randomString(10, Mode.ALPHA), age, height, weight));
    }

    personsList.add(new Person("John", "Doe", 25, 1.80, 80));
    personsList.add(new Person("Jane", "Doe", 30, 1.69, 60));
    personsList.add(new Person("John", "Smith", 35, 174, 70));
    personsList.add(new Person("John", "T", 45, 179, 99));

    // Query with mutiple Streams
    new Process() {
      protected void doRun() {
        queryJava8(personsList);
      }
    }.run();

    // Query with 'TriCore' method
    new Process() {
      protected void doRun() {
        queryJava8_1(personsList);
      }
    }.run();

    // Query with 'Tagir Valeev' method
    new Process() {
      protected void doRun() {
        queryJava8_2(personsList);
      }
    }.run();
  }

  // --------------------
  // JAVA 8
  // --------------------

  private static void queryJava8(List<Person> personsList) {
    long count = personsList.stream().filter(p -> p.getFirstName().equals("John")).count();
    int maxAge = personsList.stream().mapToInt(Person::getAge).max().getAsInt();
    double minHeight = personsList.stream().mapToDouble(Person::getHeight).min().getAsDouble();
    double avgWeight = personsList.stream().mapToDouble(Person::getWeight).average().getAsDouble();

    Object[] result = new Object[] { count, maxAge, minHeight, avgWeight };
    System.out.println("Java8: " + Arrays.toString(result));

  }

  // --------------------
  // JAVA 8_1 - TriCore
  // --------------------

  private static void queryJava8_1(List<Person> personsList) {
    Object[] objects = personsList.stream().collect(Collector.of(() -> new PersonStatistics(p -> p.getFirstName().equals("John")),
        PersonStatistics::accept, PersonStatistics::combine, PersonStatistics::toStatArray));
    System.out.println("Java8_1: " + Arrays.toString(objects));
  }

  public static class PersonStatistics {
    private long firstNameCounter;
    private int maxAge = Integer.MIN_VALUE;
    private double minHeight = Double.MAX_VALUE;
    private double totalWeight;
    private long total;
    private final Predicate<Person> firstNameFilter;

    public PersonStatistics(Predicate<Person> firstNameFilter) {
      Objects.requireNonNull(firstNameFilter);
      this.firstNameFilter = firstNameFilter;
    }

    public void accept(Person p) {
      if (this.firstNameFilter.test(p)) {
        firstNameCounter++;
      }

      this.maxAge = Math.max(p.getAge(), maxAge);
      this.minHeight = Math.min(p.getHeight(), minHeight);
      this.totalWeight += p.getWeight();
      this.total++;
    }

    public PersonStatistics combine(PersonStatistics personStatistics) {
      this.firstNameCounter += personStatistics.firstNameCounter;
      this.maxAge = Math.max(personStatistics.maxAge, maxAge);
      this.minHeight = Math.min(personStatistics.minHeight, minHeight);
      this.totalWeight += personStatistics.totalWeight;
      this.total += personStatistics.total;

      return this;
    }

    public Object[] toStatArray() {
      return new Object[] { firstNameCounter, maxAge, minHeight, total == 0 ? 0 : totalWeight / total };
    }
  }

  // --------------------
  // JAVA 8_2 - Tagir Valeev
  // --------------------

  private static void queryJava8_2(List<Person> personsList) {
    // @formatter:off
    Collector<Person, ?, Object[]> collector = multiCollector(
            filtering(p -> p.getFirstName().equals("John"), Collectors.counting()),
            Collectors.collectingAndThen(Collectors.mapping(Person::getAge, Collectors.maxBy(Comparator.naturalOrder())), Optional::get),
            Collectors.collectingAndThen(Collectors.mapping(Person::getHeight, Collectors.minBy(Comparator.naturalOrder())), Optional::get),
            Collectors.averagingDouble(Person::getWeight)
    );
    // @formatter:on

    Object[] result = personsList.stream().collect(collector);
    System.out.println("Java8_2: " + Arrays.toString(result));
  }

  /**
   * Returns a collector which combines the results of supplied collectors
   * into the Object[] array.
   */
  @SafeVarargs
  public static <T> Collector<T, ?, Object[]> multiCollector(Collector<T, ?, ?>... collectors) {
    @SuppressWarnings("unchecked")
    Collector<T, Object, Object>[] cs = (Collector<T, Object, Object>[]) collectors;
    // @formatter:off
      return Collector.<T, Object[], Object[]> of(
          () -> Stream.of(cs).map(c -> c.supplier().get()).toArray(),
          (acc, t) -> IntStream.range(0, acc.length).forEach(
              idx -> cs[idx].accumulator().accept(acc[idx], t)),
          (acc1, acc2) -> IntStream.range(0, acc1.length)
              .mapToObj(idx -> cs[idx].combiner().apply(acc1[idx], acc2[idx])).toArray(),
          acc -> IntStream.range(0, acc.length)
              .mapToObj(idx -> cs[idx].finisher().apply(acc[idx])).toArray());
     // @formatter:on
  }

  /**
   * filtering() collector (which will be added in JDK-9, see JDK-8144675)
   */
  public static <T, A, R> Collector<T, A, R> filtering(Predicate<? super T> filter, Collector<T, A, R> downstream) {
    BiConsumer<A, T> accumulator = downstream.accumulator();
    Set<Characteristics> characteristics = downstream.characteristics();
    return Collector.of(downstream.supplier(), (acc, t) -> {
      if (filter.test(t))
        accumulator.accept(acc, t);
    } , downstream.combiner(), downstream.finisher(), characteristics.toArray(new Collector.Characteristics[characteristics.size()]));
  }

  // --------------------
  // HELPER METHODS
  // --------------------

  public static enum Mode {
    ALPHA,
    ALPHANUMERIC,
    NUMERIC
  }

  private static String randomString(int length, Mode mode) {
    StringBuffer buffer = new StringBuffer();
    String characters = "";

    switch (mode) {
      case ALPHA:
        characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
        break;

      case ALPHANUMERIC:
        characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
        break;

      case NUMERIC:
        characters = "1234567890";
        break;
    }

    int charactersLength = characters.length();

    for (int i = 0; i < length; i++) {
      double index = Math.random() * charactersLength;
      buffer.append(characters.charAt((int) index));
    }
    return buffer.toString();
  }

  private static int random(int min, int max) {
    Random rand = new Random();
    return rand.nextInt((max - min) + 1) + min;
  }

  private static double random(double min, double max) {
    return min + Math.random() * (max - min);
  }

}
公共类StreamTest{
公共静态类人员{
私有字符串名;
私有字符串lastName;
私人互联网;
私人双高;
私人双倍重量;
公众人物(字符串名、字符串名、整数、双高、双重){
this.firstName=firstName;
this.lastName=lastName;
这个。年龄=年龄;
高度=高度;
重量=重量;
}
公共字符串getFirstName(){
返回名字;
}
公共字符串getLastName(){
返回姓氏;
}
公共整数getAge(){
回归年龄;
}
公众双倍身高(){
返回高度;
}
公共双getWeight(){
返回重量;
}
}
公共静态抽象类进程{
公开募捐{
秒表计时器=新秒表();
timer.start();
多伦();
timer.stop();
System.out.println(timer.getTime());
}
受保护的抽象void doRun();
}
公共静态void main(字符串[]args){
List PersonList=新建ArrayList();
对于(int i=0;i<1000000;i++){
智力年龄=随机(15,60);
双倍高度=随机(1.50,2.00);
双倍重量=随机(50.0,100.0);
添加(新人物(随机字符串(10,Mode.ALPHA),随机字符串(10,Mode.ALPHA),年龄,身高,体重);
}
添加(新的人(“约翰”,“多伊”,25,1.80,80));
添加(新的人(“简”,“多伊”,30,1.69,60));
添加(新人物(“约翰”,“史密斯”,35,174,70));
添加(新人物(“约翰”、“T”、45、179、99));
//多流查询
新工艺(){
受保护的void doRun(){
queryJava8(个人列表);
}
}.run();
//使用“TriCore”方法进行查询
新工艺(){
受保护的void doRun(){
查询Java8_1(人员列表);
}
}.run();
//使用“Tagir Valeev”方法进行查询
新工艺(){
受保护的void doRun(){
查询Java8_2(人员列表);
}
}.run();
}
// --------------------
//爪哇8
// --------------------
私有静态void queryJava8(List personsList){
long count=personsList.stream().filter(p->p.getFirstName().equals(“John”).count();
int maxAge=personsList.stream().mapToInt(Person::getAge.max().getAsInt();
double minHeight=personsList.stream().mapToDouble(Person::getHeight.min().getAsDouble();
double avgWeight=personsList.stream().mapToDouble(Person::getWeight.average().getAsDouble();
对象[]结果=新对象[]{count,maxAge,minHeight,avgWeight};
System.out.println(“Java8:+Arrays.toString(result));
}
// --------------------
//JAVA 8_1-TriCore
// --------------------
私有静态无效查询java8_1(列表人员列表){
Object[]objects=personsList.stream().collect(Collector.of(()->newpersonstatistics(p->p.getFirstName().equals(“John”),
PersonStatistics::accept、PersonStatistics::combine、PersonStatistics::toStatArray);
System.out.println(“Java8_1:+array.toString(objects));
}
公共静态类人员统计{
私人长名计数器;
private int maxAge=Integer.MIN_值;
专用双最小高度=双最大值;
私人双总重量;
私人长总;
私有最终谓词firstNameFilter;
公共人员统计(谓词firstNameFilter){
Objects.requirennull(firstNameFilter);
this.firstNameFilter=firstNameFilter;
}
公共无效接受(p人){
if(this.firstNameFilter.test(p)){
firstnameconter++;
}
this.maxAge=Math.max(p.getAge(),maxAge);
this.minHeight=Math.min(p.getHeight(),minHeight);
this.totalWeight+=p.getWeight();
这个.total++;
}
公共人员统计联合会(人员统计){
this.firstNameCounter+=personStatistics.firstNameCounter;
this.maxAge=Math.max(personStatistics.maxAge,maxAge);
this.minHeight=Math.min(personStatistics.minHeight,minHeight);
this.totalWeight+=personStatistics.totalWeight;
this.total+=personStatistics.total;
归还这个;
}
公共对象[]toStatArray(){
返回新对象[]{firstnameconter,maxAge,minHeight,total==0?0:totalWeight/total};
}
}
// --------------------
//爪哇8_2-Tagir Valeev
// --------------------
私有静态无效查询Java8_2(列表人员列表){
//@formatter:off
收集器=多收集器(
过滤(p->p.getFirstName().equals(“John”)、收集器.counting()),
Collectors.collectionAndThen(Collectors.mapping(Person::getAge,Collectors.maxBy(Comparator.naturalOrder())),可选::get),
Collectors.collectionandthen(Collectors.mapping)(Person::getHeight,Collectors.minBy(Co
public class StreamTest {

  public static class Person {

    private String firstName;
    private String lastName;
    private int age;
    private double height;
    private double weight;

    public Person(String firstName, String lastName, int age, double height, double weight) {
      this.firstName = firstName;
      this.lastName = lastName;
      this.age = age;
      this.height = height;
      this.weight = weight;
    }

    public String getFirstName() {
      return firstName;
    }

    public String getLastName() {
      return lastName;
    }

    public int getAge() {
      return age;
    }

    public double getHeight() {
      return height;
    }

    public double getWeight() {
      return weight;
    }

  }

  public static abstract class Process {

    public void run() {
      StopWatch timer = new StopWatch();
      timer.start();
      doRun();
      timer.stop();
      System.out.println(timer.getTime());
    }

    protected abstract void doRun();

  }

  public static void main(String[] args) {
    List<Person> personsList = new ArrayList<Person>();

    for (int i = 0; i < 1000000; i++) {
      int age = random(15, 60);
      double height = random(1.50, 2.00);
      double weight = random(50.0, 100.0);
      personsList.add(new Person(randomString(10, Mode.ALPHA), randomString(10, Mode.ALPHA), age, height, weight));
    }

    personsList.add(new Person("John", "Doe", 25, 1.80, 80));
    personsList.add(new Person("Jane", "Doe", 30, 1.69, 60));
    personsList.add(new Person("John", "Smith", 35, 174, 70));
    personsList.add(new Person("John", "T", 45, 179, 99));

    // Query with mutiple Streams
    new Process() {
      protected void doRun() {
        queryJava8(personsList);
      }
    }.run();

    // Query with 'TriCore' method
    new Process() {
      protected void doRun() {
        queryJava8_1(personsList);
      }
    }.run();

    // Query with 'Tagir Valeev' method
    new Process() {
      protected void doRun() {
        queryJava8_2(personsList);
      }
    }.run();
  }

  // --------------------
  // JAVA 8
  // --------------------

  private static void queryJava8(List<Person> personsList) {
    long count = personsList.stream().filter(p -> p.getFirstName().equals("John")).count();
    int maxAge = personsList.stream().mapToInt(Person::getAge).max().getAsInt();
    double minHeight = personsList.stream().mapToDouble(Person::getHeight).min().getAsDouble();
    double avgWeight = personsList.stream().mapToDouble(Person::getWeight).average().getAsDouble();

    Object[] result = new Object[] { count, maxAge, minHeight, avgWeight };
    System.out.println("Java8: " + Arrays.toString(result));

  }

  // --------------------
  // JAVA 8_1 - TriCore
  // --------------------

  private static void queryJava8_1(List<Person> personsList) {
    Object[] objects = personsList.stream().collect(Collector.of(() -> new PersonStatistics(p -> p.getFirstName().equals("John")),
        PersonStatistics::accept, PersonStatistics::combine, PersonStatistics::toStatArray));
    System.out.println("Java8_1: " + Arrays.toString(objects));
  }

  public static class PersonStatistics {
    private long firstNameCounter;
    private int maxAge = Integer.MIN_VALUE;
    private double minHeight = Double.MAX_VALUE;
    private double totalWeight;
    private long total;
    private final Predicate<Person> firstNameFilter;

    public PersonStatistics(Predicate<Person> firstNameFilter) {
      Objects.requireNonNull(firstNameFilter);
      this.firstNameFilter = firstNameFilter;
    }

    public void accept(Person p) {
      if (this.firstNameFilter.test(p)) {
        firstNameCounter++;
      }

      this.maxAge = Math.max(p.getAge(), maxAge);
      this.minHeight = Math.min(p.getHeight(), minHeight);
      this.totalWeight += p.getWeight();
      this.total++;
    }

    public PersonStatistics combine(PersonStatistics personStatistics) {
      this.firstNameCounter += personStatistics.firstNameCounter;
      this.maxAge = Math.max(personStatistics.maxAge, maxAge);
      this.minHeight = Math.min(personStatistics.minHeight, minHeight);
      this.totalWeight += personStatistics.totalWeight;
      this.total += personStatistics.total;

      return this;
    }

    public Object[] toStatArray() {
      return new Object[] { firstNameCounter, maxAge, minHeight, total == 0 ? 0 : totalWeight / total };
    }
  }

  // --------------------
  // JAVA 8_2 - Tagir Valeev
  // --------------------

  private static void queryJava8_2(List<Person> personsList) {
    // @formatter:off
    Collector<Person, ?, Object[]> collector = multiCollector(
            filtering(p -> p.getFirstName().equals("John"), Collectors.counting()),
            Collectors.collectingAndThen(Collectors.mapping(Person::getAge, Collectors.maxBy(Comparator.naturalOrder())), Optional::get),
            Collectors.collectingAndThen(Collectors.mapping(Person::getHeight, Collectors.minBy(Comparator.naturalOrder())), Optional::get),
            Collectors.averagingDouble(Person::getWeight)
    );
    // @formatter:on

    Object[] result = personsList.stream().collect(collector);
    System.out.println("Java8_2: " + Arrays.toString(result));
  }

  /**
   * Returns a collector which combines the results of supplied collectors
   * into the Object[] array.
   */
  @SafeVarargs
  public static <T> Collector<T, ?, Object[]> multiCollector(Collector<T, ?, ?>... collectors) {
    @SuppressWarnings("unchecked")
    Collector<T, Object, Object>[] cs = (Collector<T, Object, Object>[]) collectors;
    // @formatter:off
      return Collector.<T, Object[], Object[]> of(
          () -> Stream.of(cs).map(c -> c.supplier().get()).toArray(),
          (acc, t) -> IntStream.range(0, acc.length).forEach(
              idx -> cs[idx].accumulator().accept(acc[idx], t)),
          (acc1, acc2) -> IntStream.range(0, acc1.length)
              .mapToObj(idx -> cs[idx].combiner().apply(acc1[idx], acc2[idx])).toArray(),
          acc -> IntStream.range(0, acc.length)
              .mapToObj(idx -> cs[idx].finisher().apply(acc[idx])).toArray());
     // @formatter:on
  }

  /**
   * filtering() collector (which will be added in JDK-9, see JDK-8144675)
   */
  public static <T, A, R> Collector<T, A, R> filtering(Predicate<? super T> filter, Collector<T, A, R> downstream) {
    BiConsumer<A, T> accumulator = downstream.accumulator();
    Set<Characteristics> characteristics = downstream.characteristics();
    return Collector.of(downstream.supplier(), (acc, t) -> {
      if (filter.test(t))
        accumulator.accept(acc, t);
    } , downstream.combiner(), downstream.finisher(), characteristics.toArray(new Collector.Characteristics[characteristics.size()]));
  }

  // --------------------
  // HELPER METHODS
  // --------------------

  public static enum Mode {
    ALPHA,
    ALPHANUMERIC,
    NUMERIC
  }

  private static String randomString(int length, Mode mode) {
    StringBuffer buffer = new StringBuffer();
    String characters = "";

    switch (mode) {
      case ALPHA:
        characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
        break;

      case ALPHANUMERIC:
        characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
        break;

      case NUMERIC:
        characters = "1234567890";
        break;
    }

    int charactersLength = characters.length();

    for (int i = 0; i < length; i++) {
      double index = Math.random() * charactersLength;
      buffer.append(characters.charAt((int) index));
    }
    return buffer.toString();
  }

  private static int random(int min, int max) {
    Random rand = new Random();
    return rand.nextInt((max - min) + 1) + min;
  }

  private static double random(double min, double max) {
    return min + Math.random() * (max - min);
  }

}
public static <T, A, R> Collector<T, A, R> filter(
    Predicate<? super T> predicate, Collector<T, A, R> downstream) {
    return Collector.of(
        downstream.supplier(),
        (c, t) -> {
            if (predicate.test(t))
                downstream.accumulator().accept(c, t);
        }, 
        downstream.combiner(),
        downstream.finisher()
    );
} 
static <T, A1, A2, D1, D2> Collector<T, Tuple2<A1, A2>, Tuple2<D1, D2>> collectors(
    Collector<T, A1, D1> collector1
  , Collector<T, A2, D2> collector2
) {
    return Collector.<T, Tuple2<A1, A2>, Tuple2<D1, D2>>of(
        () -> tuple(
            collector1.supplier().get()
          , collector2.supplier().get()
        ),
        (a, t) -> {
            collector1.accumulator().accept(a.v1, t);
            collector2.accumulator().accept(a.v2, t);
        },
        (a1, a2) -> tuple(
            collector1.combiner().apply(a1.v1, a2.v1)
          , collector2.combiner().apply(a1.v2, a2.v2)
        ),
        a -> tuple(
            collector1.finisher().apply(a.v1)
          , collector2.finisher().apply(a.v2)
        )
    );
}
public class PersonStatistics {
    private long firstNameCounter;
    private int maxAge = Integer.MIN_VALUE;
    private double minHeight = Double.MAX_VALUE;
    private double totalWeight;
    private long total;
    private final Predicate<Person> firstNameFilter;

    public PersonStatistics(Predicate<Person> firstNameFilter) {
        Objects.requireNonNull(firstNameFilter);
        this.firstNameFilter = firstNameFilter;
    }

    public void accept(Person p) {
        if (this.firstNameFilter.test(p)) {
            firstNameCounter++;
        }

        this.maxAge = Math.max(p.getAge(), maxAge);
        this.minHeight = Math.min(p.getHeight(), minHeight);
        this.totalWeight += p.getWeight();
        this.total++;
    }

    public PersonStatistics combine(PersonStatistics personStatistics) {
        this.firstNameCounter += personStatistics.firstNameCounter;
        this.maxAge = Math.max(personStatistics.maxAge, maxAge);
        this.minHeight = Math.min(personStatistics.minHeight, minHeight);
        this.totalWeight += personStatistics.totalWeight;
        this.total += personStatistics.total;

        return this;
    }

    public Object[] toStatArray() {
        return new Object[]{firstNameCounter, maxAge, minHeight, total == 0 ? 0 : totalWeight / total};
    }
}
public class PersonMain {
    public static void main(String[] args) {
        List<Person> personsList = new ArrayList<>();

        personsList.add(new Person("John", "Doe", 25, 180, 80));
        personsList.add(new Person("Jane", "Doe", 30, 169, 60));
        personsList.add(new Person("John", "Smith", 35, 174, 70));
        personsList.add(new Person("John", "T", 45, 179, 99));

        Object[] objects = personsList.stream().collect(Collector.of(
                () -> new PersonStatistics(p -> p.getFirstName().equals("John")),
                PersonStatistics::accept,
                PersonStatistics::combine,
                PersonStatistics::toStatArray));
        System.out.println(Arrays.toString(objects));
    }
}
/**
 * Returns a collector which combines the results of supplied collectors
 * into the Object[] array.
 */
@SafeVarargs
public static <T> Collector<T, ?, Object[]> multiCollector(
        Collector<T, ?, ?>... collectors) {
    @SuppressWarnings("unchecked")
    Collector<T, Object, Object>[] cs = (Collector<T, Object, Object>[]) collectors;
    return Collector.<T, Object[], Object[]> of(
        () -> Stream.of(cs).map(c -> c.supplier().get()).toArray(),
        (acc, t) -> IntStream.range(0, acc.length).forEach(
            idx -> cs[idx].accumulator().accept(acc[idx], t)),
        (acc1, acc2) -> IntStream.range(0, acc1.length)
            .mapToObj(idx -> cs[idx].combiner().apply(acc1[idx], acc2[idx])).toArray(),
        acc -> IntStream.range(0, acc.length)
            .mapToObj(idx -> cs[idx].finisher().apply(acc[idx])).toArray());
}
public static <T, A, R> Collector<T, A, R> filtering(
        Predicate<? super T> filter, Collector<T, A, R> downstream) {
    BiConsumer<A, T> accumulator = downstream.accumulator();
    Set<Characteristics> characteristics = downstream.characteristics();
    return Collector.of(downstream.supplier(), (acc, t) -> {
        if(filter.test(t)) accumulator.accept(acc, t);
    }, downstream.combiner(), downstream.finisher(), 
        characteristics.toArray(new Collector.Characteristics[characteristics.size()]));
}
Collector<Person, ?, Object[]> collector = 
    multiCollector(
        filtering(p -> p.getFirstName().equals("John"), counting()),
        collectingAndThen(mapping(Person::getAge, 
            maxBy(Comparator.naturalOrder())), Optional::get),
        collectingAndThen(mapping(Person::getHeight, 
            minBy(Comparator.naturalOrder())), Optional::get),
        averagingDouble(Person::getWeight));

Object[] result = personsList.stream().collect(collector);
System.out.println(Arrays.toString(result));