我将从Java 8中的Stream 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; } }
这是我的代码,它初始化一个对象列表Person,它获取由特定名字过滤的对象数,最大年龄和最小高度,平均权重,最后创建一个包含这些值的对象数组:
ListpersonsList = new ArrayList (); 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));
是否可以单独调用该stream()
方法并直接返回对象数组?
Object[] result = personsList.stream()...count()...max()...min()...average()
我之前问过非常类似的问题:Java 8 Streams:如何调用一次Collection.stream()方法并检索几个聚合值的数组,但这次我不能使用该summaryStatistics()
方法,因为我使用不同的字段(年龄,身高,体重)检索聚合值.
编辑2016-01-07
我测试了TriCore
和的解决方案Tagir Valeev
,并计算了每个解决方案的运行时间.
似乎TriCore
解决方案比效率更高Tagir Valeev
.
Tagir Valeev
与我的解决方案(使用多个Streams)相比,解决方案似乎没有节省太多时间.
这是我的测试类:
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) { ListpersonsList = new ArrayList (); 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 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 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 firstNameFilter; public PersonStatistics(Predicate 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 personsList) { // @formatter:off Collector 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 Collector multiCollector(Collector ... collectors) { @SuppressWarnings("unchecked") Collector [] cs = (Collector []) collectors; // @formatter:off return Collector. 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 Collector filtering(Predicate super T> filter, Collector downstream) { BiConsumer accumulator = downstream.accumulator(); Set 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); } }
Lukas Eder.. 9
使用标准JDK 8 API解决这个问题有点棘手,它没有提供很多组合Collector
类型的方法.如果您愿意使用像jOOλ这样的第三方库,您可以写:
Tuple4, Optional, Optional> result =
Seq.seq(personsList)
.collect(
filter(p -> p.getFirstName().equals("John"), count()),
max(Person::getAge),
min(Person::getHeight),
avg(Person::getWeight)
);
System.out.println(result);
以上产量:
(2, Optional[35], Optional[1.8], Optional[75.0])
注意,它使用的是新Agg.filter()
方法,类似于JDK 9 Collectors.filtering()
方法,其工作方式如下:
public staticCollector filter( Predicate super T> predicate, Collector downstream) { return Collector.of( downstream.supplier(), (c, t) -> { if (predicate.test(t)) downstream.accumulator().accept(c, t); }, downstream.combiner(), downstream.finisher() ); }
collect(collector1, collector2, ...)
工作怎么样?如果您不想使用上述第三方库,则可以编写自己的Collector
组合实用程序.将两个收集器组合到Tuple2
收集器中的示例:
staticCollector , Tuple2 > collectors( Collector collector1 , Collector collector2 ) { return Collector. , Tuple2 >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) ) ); }
免责声明:我为jOOλ背后的公司工作.
使用标准JDK 8 API解决这个问题有点棘手,它没有提供很多组合Collector
类型的方法.如果您愿意使用像jOOλ这样的第三方库,您可以写:
Tuple4, Optional, Optional> result =
Seq.seq(personsList)
.collect(
filter(p -> p.getFirstName().equals("John"), count()),
max(Person::getAge),
min(Person::getHeight),
avg(Person::getWeight)
);
System.out.println(result);
以上产量:
(2, Optional[35], Optional[1.8], Optional[75.0])
注意,它使用的是新Agg.filter()
方法,类似于JDK 9 Collectors.filtering()
方法,其工作方式如下:
public staticCollector filter( Predicate super T> predicate, Collector downstream) { return Collector.of( downstream.supplier(), (c, t) -> { if (predicate.test(t)) downstream.accumulator().accept(c, t); }, downstream.combiner(), downstream.finisher() ); }
collect(collector1, collector2, ...)
工作怎么样?如果您不想使用上述第三方库,则可以编写自己的Collector
组合实用程序.将两个收集器组合到Tuple2
收集器中的示例:
staticCollector , Tuple2 > collectors( Collector collector1 , Collector collector2 ) { return Collector. , Tuple2 >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) ) ); }
免责声明:我为jOOλ背后的公司工作.
这是收藏家
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 PredicatefirstNameFilter; public PersonStatistics(Predicate 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) { ListpersonsList = 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)); } }
如果没有第三方库,您可以创建一个通用收集器,它将任意数量的指定收集器的结果组合到单个Object[]
数组中:
/** * Returns a collector which combines the results of supplied collectors * into the Object[] array. */ @SafeVarargs public staticCollector multiCollector( Collector ... collectors) { @SuppressWarnings("unchecked") Collector [] cs = (Collector []) collectors; return Collector. 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()); }
对于您的具体问题,您还需要一个filtering()
收集器(将在JDK-9中添加,请参阅JDK-8144675):
public staticCollector filtering( Predicate super T> filter, Collector downstream) { BiConsumer accumulator = downstream.accumulator(); Set 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()])); }
现在您可以构建一个可以生成最终结果的收集器:
Collectorcollector = 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));