Java 8 Streams: cómo llamar una vez al método Collection.stream () y recuperar una matriz de varios valores agregados con diferentes campos

Estoy comenzando con la API Stream en Java 8.

Aquí está mi objeto Persona que uso:

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;
    }

  }

Aquí está mi código que inicializa una lista de objetos Persona y que obtiene el número de objetos filtrados por un nombre específico, la edad máxima y la altura mínima, el promedio de peso, y finalmente crea una matriz de objetos que contiene estos valores:

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));

¿Es posible hacer una sola llamada alstream() método y para devolver la matriz de objetos directamente?

Object[] result = personsList.stream()...count()...max()...min()...average()

Hice una pregunta muy similar anteriormente:Java 8 Streams: cómo llamar una vez al método Collection.stream () y recuperar una matriz de varios valores agregados pero esta vez no puedo usar elsummaryStatistics() método porque uso diferentes campos (edad, altura, peso) para recuperar los valores agregados.

EDITAR 2016-01-07

Probé las soluciones deTriCore yTagir Valeev, y calculé el tiempo de ejecución de cada solución.

Parece que elTriCore la solución es más eficiente queTagir Valeev.

Tagir ValeevLa solución de parece no ahorrar mucho tiempo en comparación con mi solución (usando múltiples Streams).

Aquí está mi clase de prueba:

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);
  }

}

Respuestas a la pregunta(3)

Su respuesta a la pregunta