Site Search:

Save results to a collection using the collect method and group/partition data using the Collectors class

Back> 

This topic is about collect() terminal operation and the predefined Collectors.

group/partition
group/partition


The collect() method is an advanced reduction, it is more efficient than a regular reduction. While regular reduce() combines two values and get a new accumulated object each fold,  collect() use the same mutable object for accumulating.

The method signature are as follows:


  1. <R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner)
  2. <R, A> R collect(Collector<? super T, A, R> collector)
The first signature is general purpose. The first parameter is a Supplier that creates the mutable object that will store the accumulating results. The second parameter is a BiConsumer, which is used to add one more element to that mutable accumulating object. The third parameter is another BiConsume. It is responsible for taking two mutable accumulating objects and merging them into one. The is useful when we collect with parallel stream. The following example collect a stream of String into a StringBuilder and ArrayList<String> respectively.

StringBuilder greeting = Stream.of("hello", "world", "!").collect(StringBuilder::new, StringBuilder:append, StringBuilder::append);

ArrayList<String> greeting2 = Stream.of("hello", "world", "!").collect(ArrayList::new, ArrayList::add, ArrayList::addAll);



The second signature accepts predefined collectors which implement Collector interface.
Unfortunately, we need to remember almost all of the predefined Collectors for OCPJP, so start to read the javadoc, then take a look at the following long list of examples:

Collectors that reduce the Stream into a number or String

averagingxxx, counting, joining, maxBy, minBy, reducing, sumarizingxxx, summingxxx.

OCPJP>cat CommonCollectorsDemo.java 
import java.util.ArrayList;
import java.util.DoubleSummaryStatistics;
import java.util.IntSummaryStatistics;
import java.util.LongSummaryStatistics;
import java.util.List;
import java.util.Optional;
import java.util.stream.*;
public class CommonCollectorsDemo {
    public static void main(String[] args) {
        System.out.println("static <T> Collector<T, ?, Double> averagingDouble(ToDoubleFunction<? super T> mapper) ");
        Stream<String> stream = Stream.of("cat", "Apple", "John", "something");
        double average = stream.collect(Collectors.averagingDouble(String::length));
        System.out.println("Collectors.averagingDouble: " + average);
        
        System.out.println("static <T> Collector<T, ?, Double> averagingInt(ToIntFunction<? super T> mapper) ");
        average = Stream.of("cat", "Apple", "John", "something").collect(Collectors.averagingInt(String::length));
        System.out.println("Collectors.averagingInt: " + average);
        
        System.out.println("static <T> Collector<T, ?, Long> averagingLong(ToLongFunction<? super T> mapper) ");
        average = Stream.of("cat", "Apple", "John", "something").collect(Collectors.averagingLong(String::length));
        System.out.println("Collectors.averagingLong: " + average);
        
        List<String> list = new ArrayList<>();
        list.add("car"); 
        list.add("ship");
        list.add("airplane");
        System.out.println("static <T> Collector<T, ?, Long> counting() ");
        Long counting = list.stream().collect(Collectors.counting());
        System.out.println("Collectors.counting: " + counting);
        
        System.out.println("static Collecor<CharSequence, ?, String> joining() ");
        String joining = list.stream().collect(Collectors.joining());
        System.out.println("Collectors.joining: " + joining);
        
        System.out.println("static Collector<CharSequence, ?, String> joining(CharSequence delimiter) ");
        joining = list.stream().collect(Collectors.joining(", "));
        System.out.println("Collectors.joining(\", \"): " + joining);
        
        System.out.println("static <T> Collector<T, ?, Optional<T>> maxBy(Comparator<? super T> comparator) ");
        Optional<String> maxBy = list.stream().collect(Collectors.maxBy(String::compareToIgnoreCase));
        maxBy.ifPresent(i -> System.out.println("Collectors.maxBy: " + maxBy.get()));
        
        System.out.println("static <T> Collector<T, ?, Optional<T>> minBy(Comparator<? super T> comparator) ");
        Optional<String> minBy = list.stream().collect(Collectors.minBy(String::compareToIgnoreCase));
        minBy.ifPresent(i -> System.out.println("Collectors.minBy: " + minBy.get()));
        
        System.out.println("static <T> Collector<T, ?, Optional<T>> reducing(BinaryOperator<T> op) ");
        Optional<String> reducing = list.stream().collect(Collectors.reducing(String::concat));
        reducing.ifPresent(i -> System.out.println("Collectors.reducing: " + reducing.get()));
        
        System.out.println("static <T> Collector<T, ?, DoubleSummaryStatistics> summarizingDouble(ToDoubleFunction<? super T> mapper) ");
        DoubleSummaryStatistics summarizingDouble = list.stream().collect(Collectors.summarizingDouble(String::length));
        System.out.println("Collectors.summarizingDouble: getAverage = " + summarizingDouble.getAverage() 
                + " getCount=" + summarizingDouble.getCount() 
                + " getMax=" + summarizingDouble.getMax() 
                + " getMin=" + summarizingDouble.getMin() 
                + " getSum=" + summarizingDouble.getSum());
        
        System.out.println("static <T> Collector<T, ?, IntSummaryStatistics> summarizingInt(ToIntFunction<? super T> mapper) ");
        IntSummaryStatistics summarizingInt = list.stream().collect(Collectors.summarizingInt(String::length));
        System.out.println("Collectors.summarizingInt: getAverage = " + summarizingInt.getAverage() 
                + " getCount = " + summarizingInt.getCount() 
                + " getMax = " + summarizingInt.getMax()
                + " getMin = " + summarizingInt.getMin()
                + " getSum = " + summarizingInt.getSum());
        
        System.out.println("static <T> Collector<T, ?, LongSummaryStaticstics> summarizingLong(ToLongFunction<? super T> mapper) ");
        LongSummaryStatistics summarizingLong = list.stream().collect(Collectors.summarizingLong(String::length));
        System.out.println("Collectors.summarizingLong: getAverage = " + summarizingLong.getAverage()
                + " getCount = " + summarizingLong.getAverage()
                + " getMax = " + summarizingLong.getMax()
                + " getMin = " + summarizingLong.getMin()
                + " getSum = " + summarizingLong.getSum());
        
        System.out.println("static <T> Collector<T, ?, Double> summingDouble(ToDoubleFunction<? super T> mapper) ");
        double summingDouble = list.stream().collect(Collectors.summingDouble(String::length));
        System.out.println("Collectors.summingDouble: " + summingDouble);
        
        System.out.println("static <T> Collector<T, ?, Integer> summingInt(ToIntFunction<? super T> mapper) ");
        int summingInt = list.stream().collect(Collectors.summingInt(String::length));
        System.out.println("Collectors.summingInt: " + summingInt);
        
        System.out.println("static <T> Collector<T, ?, Long> summingLong(ToLongFunction<? super T> mapper) ");
        long summingLong = list.stream().collect(Collectors.summingLong(String::length));
        System.out.println("Collectors.summingLong: " + summingLong);
        
    }
}
OCPJP>
OCPJP>
OCPJP>javac CommonCollectorsDemo.java 
OCPJP>java CommonCollectorsDemo
static <T> Collector<T, ?, Double> averagingDouble(ToDoubleFunction<? super T> mapper) 
Collectors.averagingDouble: 5.25
static <T> Collector<T, ?, Double> averagingInt(ToIntFunction<? super T> mapper) 
Collectors.averagingInt: 5.25
static <T> Collector<T, ?, Long> averagingLong(ToLongFunction<? super T> mapper) 
Collectors.averagingLong: 5.25
static <T> Collector<T, ?, Long> counting() 
Collectors.counting: 3
static Collecor<CharSequence, ?, String> joining() 
Collectors.joining: carshipairplane
static Collector<CharSequence, ?, String> joining(CharSequence delimiter) 
Collectors.joining(", "): car, ship, airplane
static <T> Collector<T, ?, Optional<T>> maxBy(Comparator<? super T> comparator) 
Collectors.maxBy: ship
static <T> Collector<T, ?, Optional<T>> minBy(Comparator<? super T> comparator) 
Collectors.minBy: airplane
static <T> Collector<T, ?, Optional<T>> reducing(BinaryOperator<T> op) 
Collectors.reducing: carshipairplane
static <T> Collector<T, ?, DoubleSummaryStatistics> summarizingDouble(ToDoubleFunction<? super T> mapper) 
Collectors.summarizingDouble: getAverage = 5.0 getCount=3 getMax=8.0 getMin=3.0 getSum=15.0
static <T> Collector<T, ?, IntSummaryStatistics> summarizingInt(ToIntFunction<? super T> mapper) 
Collectors.summarizingInt: getAverage = 5.0 getCount = 3 getMax = 8 getMin = 3 getSum = 15
static <T> Collector<T, ?, LongSummaryStaticstics> summarizingLong(ToLongFunction<? super T> mapper) 
Collectors.summarizingLong: getAverage = 5.0 getCount = 5.0 getMax = 8 getMin = 3 getSum = 15
static <T> Collector<T, ?, Double> summingDouble(ToDoubleFunction<? super T> mapper) 
Collectors.summingDouble: 15.0
static <T> Collector<T, ?, Integer> summingInt(ToIntFunction<? super T> mapper) 
Collectors.summingInt: 15
static <T> Collector<T, ?, Long> summingLong(ToLongFunction<? super T> mapper) 
Collectors.summingLong: 15
OCPJP>

Collectors that reduce the Stream into a Collection


toCollection, toSet, toList


OCPJP>cat ToCollectionDemo.java 
import java.util.stream.*;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class ToCollectionDemo {
    public static void main(String...args) {
        Stream<String> stream = Arrays.asList("dog", "fish", "cat", "bird").stream();
        System.out.println("static <T, C extends Collection<T>> collector<T, ? , C> toCollection(Supplier<C> collectionFactory) ");
        Set<String> set = stream.collect(Collectors.toCollection(HashSet::new));
        System.out.println("Collectors.toCollection: " + set);
        
        stream = set.stream();
        System.out.println("static <T> collector<T, ? List<T>> toList() ");
        List<String> list = stream.collect(Collectors.toList());
        System.out.println("Collectors.toList: " + list);
        
        stream = list.stream();
        System.out.println("static <T> collector<T, ?, Set<T>> toSet() ");
        set = stream.collect(Collectors.toSet());
        System.out.println("Collectors.toSet: " + set);
    }
}

OCPJP>
OCPJP>
OCPJP>javac ToCollectionDemo.java 
OCPJP>java ToCollectionDemo
static <T, C extends Collection<T>> collector<T, ? , C> toCollection(Supplier<C> collectionFactory) 
Collectors.toCollection: [fish, cat, bird, dog]
static <T> collector<T, ? List<T>> toList() 
Collectors.toList: [fish, cat, bird, dog]
static <T> collector<T, ?, Set<T>> toSet() 
Collectors.toSet: [fish, cat, bird, dog]
OCPJP>

Collectors that reduce the Stream into a Map


toMap, groupBy, partitionBy, mapping

toMap() have 3 signatures, example usages are:

  • two parameter version: first lambda is key mapper, second lambda is value mapper.

Map<String, String> wordToFirstSubString = Stream.of("hello", "world", "!").collect(Collectors.toMap(k -> k, v -> v.substring(0, 1))); //{hello=h, world=w, !=!}

  • three parameter version: the third parameter is a merger lambda used when a key have two values mapped to it.

Map<Integer, String> lengthToString = Stream.of("hello", "world", "!").collect(Collectors.toMap(String::length, v -> v, (s1, s2) -> s1 + "concat to " + s2)); //{5=hello concat to world, 1=!}

  • four parameter version: the fourth parameter is a Supplier that specify how to construct the returned Map.
HashMap<Integer, String> lengthToString = Stream.of("hello", "world", "!").collect(Collectors.toMap(String::length, v -> v, (s1, s2) -> s1 + "concat to " + s2, HashMap::new));  //{5=hello concat to world, 1=!}



The groupingBy() collector tells collect() that it should group all of the elements of the stream into lists, classifying them by the function provided.

Map<Integer, List<String>> wordsByLength = Stream.of("hello", "world", "!").collect(Collectors.groupingBy(String::length));  //{5=[hello, world], 1=[!]}

Besides the one parameter signature, groupingBy() has 2 common signatures:

  • group into other type other than List.

Map<Integer, Set<String>> wordsByLength = Stream.of("hello", "world", "!").collect(Collectors.groupingBy(String::length, Collectors.toSet()));

  • specify Map's implementation class.

HashMap<Integer, Set<String>> wordsByLength = Stream.of("hello", "world", "!").collect(Collectors.groupingBy(String::length, HashMap::new, Collectors.toSet()));

Partitioning is a special css of grouping -- there are only two possible groups -- true and false.

Map<Integer, List<String>> wordsByLength = Stream.of("hello", "world", "!").collect(Collectors.partitioningBy(s -> s.length() < 5));  //{false=[hello, world], true=[!]}

Finally, there is a mapping() collector that allow us to add another collector to deal with the each group collected with groupingBy().

Map<Integer, Optional<Character>> fun = Stream.of("hello", "world", "!").collect(Collectors.groupingBy(String::length, Collectors.mapping(s -> s.charAt(0), minBy(Comparator.naturalOrder()))));  // {5=[h], 1=[!]}



OCPJP>cat ToMapDemo.java 
import java.util.stream.*;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.*;
import java.util.HashSet;
public class ToMapDemo {
    public static void main(String[] args) {
        System.out.println("static <T, K, U> Collector<T, ?, Map<K, U>> "
                + "toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper) ");
        Stream<Integer> stream = Stream.of(1, 2, 3);
        Map<String, String> map = stream.collect(Collectors.toMap(i -> i + "key", i -> i + "value"));
        System.out.println("Collectors.toMap: " + map);
        
        System.out.println("static <T, K, U> Collector<T, ?, Map<K, U>> "
                + "toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, "
                + "BinaryOperator<U> mergeFunction)");
        stream = Stream.of(1, 2, 3, 3);
        //map = stream.collect(Collectors.toMap(i -> i + "key", i -> i + "value")); //java.lang.IllegalStateException: Duplicate key 3value
        map = stream.collect(Collectors.toMap(i -> i + "key", i -> i + "value", (s1, s2) -> s1 + s2));
        System.out.println("Collectors.toMap (with dup keys): " + map);
        
        System.out.println("static <T, K, U, M extends Map<K, U>> Collector<T, ?, M> "
                + "toMap(Function<? super T, ? extends K> keyMapper, "
                + "Function<? super T, ? extends U> valueMapper, "
                + "BinaryOperator<U> mergeFunction, "
                + "Supplier<M> mapSupplier)");
        map = Stream.of(1, 2, 3, 3).collect(Collectors.toMap(i -> i + "key", i -> i + "value", (s1, s2) -> s1 + s2, TreeMap::new));
        System.out.println("Collectors.toMap (with dup keys, want a specific Map): " + map);
        
        System.out.println("static <T, K, U> Collector<T, ?, ConcurrentMap<K, U>> "
                + "toConcurrentMap(Function<? super T, ? extends K> keyMapper, "
                + "Function<? super T, ? extends U> valueMapper)");
        stream = Stream.of(1, 2, 3);
        map = stream.parallel().collect(Collectors.toConcurrentMap(i -> i + "key", i -> i + "value"));
        System.out.println("Collectors.toConcurrentMap: " + map);
        
        System.out.println("static <T, K, U> Collector<T, ?, ConcurrentMap<K, U>> "
                + "toConcurrentMap(Function<? super T, ? extends K> keyMapper, "
                + "Function<? super T, ? extends U> valueMapper, "
                + "BinaryOperator<U> mergeFunction)");
        stream = Stream.of(1, 2, 3, 3);
        map = stream.parallel().collect(Collectors.toConcurrentMap(i -> i + "key", i -> i + "value", String::concat));
        System.out.println("Collectors.toConcurrentMap (with dup keys): " + map);
        
        System.out.println("static <T, K, U, M extends ConcurrentMap<K, U>> Collector<T, ?, M> "
                + "toConcurrentMap(Function<? super T, ? extends K> keyMapper, "
                + "Function<? super T, ? extends U> valueMapper, "
                + "BinaryOperator<U> mergeFunction, "
                + "Supplier<M> mapSupplier)");
        stream = Stream.of(1, 2, 3, 3);
        map = stream.parallel().collect(Collectors.toConcurrentMap(i -> i + "key", i -> i + "value", 
                String::concat, ConcurrentHashMap::new));
        System.out.println("Collectors.toConcurrentMap (dup keys, specified Map): " + map);
        
        Set<String> set = new HashSet<>();
        set.add("cat");
        set.add("dog");
        set.add("bird");
        set.add("frog");
        
        Stream<String> pets = set.stream();
        System.out.println("static <T, K> Collector<T, ?, Map<K, List<T>>> "
                + "groupingBy(Function<? super T, ? extends K> classifier)");
        Map<Integer, List<String>> map2 = pets.collect(Collectors.groupingBy(String::length));
        System.out.println("Collectors.groupingBy: " + map2);
        
        System.out.println("static <T, K, A, D> Collector<T, ?, Map<K, Map<T, D>>> "
                + "groupingBy(Function<? super T, ? extends K> classifier, "
                + "Collector<? super T, A, D> downStream)");
        Map<Integer, Long> map3 = set.stream().collect(Collectors.groupingBy(String::length, 
                Collectors.counting()));
        System.out.println("Collectors.groupingBy (Collectors for downStream): " + map3);
        Map<Integer, Map<Integer, List<String>>> map4 = set.stream().collect(Collectors.groupingBy(String::length, 
                Collectors.groupingBy(String::length)));
        System.out.println("Collectors.groupingBy (nested): " + map4);
        
        System.out.println("static <T, K, A, D, M extends Map<K, D> Collector<T, ?, M> "
                + "groupingBy(Function<? super T, ? extends K> classifier, "
                + "Supplier<M> mapFactory, Collector<? super T, A, D> downStream)");
        Map<Integer, Map<Integer, List<String>>> map5 = set.stream().collect(Collectors.groupingBy(String::length,
                TreeMap::new, Collectors.groupingBy(String::length)));
        System.out.println("Collectors.groupingBy (nested, specify map type)" 
                + (map5 instanceof TreeMap) + " " + map5);
        
        System.out.println("static <T> Collector<T, ?, Map<Boolean, List<T>>> "
                + "partitioningBy(Predicate<? super T> predicate)");
        Map<Boolean, List<String>> map6 = set.stream().collect(Collectors.partitioningBy(i -> i.length() > 3));
        System.out.println("Collectors.partitioningBy: " + map6);
        
        System.out.println("static <T, D, A> Collector<T, ?, Map<Boolean, D>> "
                + "partitioningBy(Predicate<? super T> predicate, Collector<? super T, A, D> downStream)");
        Map<Boolean, Map<Integer, List<String>>> map7 = set.stream().collect(Collectors
                .partitioningBy(i -> i.length() > 3, Collectors.groupingBy(String::length)));
        System.out.println("Collectors.partitioningBy (with downStream Collector): " + map7);
        
        Map<Integer, List<String>> map8 = set.parallelStream().collect(Collectors.groupingByConcurrent(String::length));
        System.out.println("Collectors.groupingByConcurrent: " + map8);
        
        Map<Integer, String> map9 = set.parallelStream().collect(Collectors.groupingByConcurrent(
                String::length, Collectors.joining()));
        System.out.println("Collectors.groupingByConcurrent (with downStream Collector): " + map9);
        
        Map<Integer, ConcurrentMap<Integer, List<String>>> map10 = set.parallelStream().collect(Collectors.groupingByConcurrent(
                String::length, ConcurrentSkipListMap::new, Collectors.groupingByConcurrent(String::length)));
        System.out.println("Collectors.groupingByConcurrent (with downStream Collector and specified ConcurrentMap tyep: " 
                + (map10 instanceof ConcurrentSkipListMap) + map10);
        
        System.out.println("static <T, U, A, R> Collector<T, ?, R> mapping("
                + "Function<? super T, ? super U> mapper, Collector<? super U, A, R> downStream)");
        Set<String> mappedSet = set.stream().collect(Collectors.mapping(i -> i + i, Collectors.toSet()));
        System.out.println("Collectors.mapping: " + mappedSet);
        
        Map<Integer, Set<String>> map11 = set.stream().collect(Collectors.groupingBy(String::length, 
                Collectors.mapping(i -> i + i, Collectors.toSet())));
        System.out.println("Collectors.mapping used with groupingBy: " + map11);
    }
}
OCPJP>
OCPJP>
OCPJP>javac ToMapDemo.java 
OCPJP>java ToMapDemo
static <T, K, U> Collector<T, ?, Map<K, U>> toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper) 
Collectors.toMap: {3key=3value, 2key=2value, 1key=1value}
static <T, K, U> Collector<T, ?, Map<K, U>> toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction)
Collectors.toMap (with dup keys): {3key=3value3value, 2key=2value, 1key=1value}
static <T, K, U, M extends Map<K, U>> Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction, Supplier<M> mapSupplier)
Collectors.toMap (with dup keys, want a specific Map): {1key=1value, 2key=2value, 3key=3value3value}
static <T, K, U> Collector<T, ?, ConcurrentMap<K, U>> toConcurrentMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper)
Collectors.toConcurrentMap: {3key=3value, 2key=2value, 1key=1value}
static <T, K, U> Collector<T, ?, ConcurrentMap<K, U>> toConcurrentMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction)
Collectors.toConcurrentMap (with dup keys): {3key=3value3value, 2key=2value, 1key=1value}
static <T, K, U, M extends ConcurrentMap<K, U>> Collector<T, ?, M> toConcurrentMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction, Supplier<M> mapSupplier)
Collectors.toConcurrentMap (dup keys, specified Map): {3key=3value3value, 2key=2value, 1key=1value}
static <T, K> Collector<T, ?, Map<K, List<T>>> groupingBy(Function<? super T, ? extends K> classifier)
Collectors.groupingBy: {3=[cat, dog], 4=[frog, bird]}
static <T, K, A, D> Collector<T, ?, Map<K, Map<T, D>>> groupingBy(Function<? super T, ? extends K> classifier, Collector<? super T, A, D> downStream)
Collectors.groupingBy (Collectors for downStream): {3=2, 4=2}
Collectors.groupingBy (nested): {3={3=[cat, dog]}, 4={4=[frog, bird]}}
static <T, K, A, D, M extends Map<K, D> Collector<T, ?, M> groupingBy(Function<? super T, ? extends K> classifier, Supplier<M> mapFactory, Collector<? super T, A, D> downStream)
Collectors.groupingBy (nested, specify map type)true {3={3=[cat, dog]}, 4={4=[frog, bird]}}
static <T> Collector<T, ?, Map<Boolean, List<T>>> partitioningBy(Predicate<? super T> predicate)
Collectors.partitioningBy: {false=[cat, dog], true=[frog, bird]}
static <T, D, A> Collector<T, ?, Map<Boolean, D>> partitioningBy(Predicate<? super T> predicate, Collector<? super T, A, D> downStream)
Collectors.partitioningBy (with downStream Collector): {false={3=[cat, dog]}, true={4=[frog, bird]}}
Collectors.groupingByConcurrent: {3=[dog, cat], 4=[frog, bird]}
Collectors.groupingByConcurrent (with downStream Collector): {3=dogcat, 4=frogbird}
Collectors.groupingByConcurrent (with downStream Collector and specified ConcurrentMap tyep: true{3={3=[dog, cat]}, 4={4=[frog, bird]}}
static <T, U, A, R> Collector<T, ?, R> mapping(Function<? super T, ? super U> mapper, Collector<? super U, A, R> downStream)
Collectors.mapping: [dogdog, frogfrog, birdbird, catcat]
Collectors.mapping used with groupingBy: {3=[dogdog, catcat], 4=[frogfrog, birdbird]}
OCPJP>


I print out the method signatures not because I want to drive you crazy, but because those are the definitive answer to any of your questions if your code refuse to compile.