Categorizing Methods in Java Types using Eclipse Collections

Donald Raab
7 min readOct 22, 2024

--

Consistent categorization of methods can lead to comprehension benefits.

Nine method categories for methods on RichIterable interface

Seven Plus Two For The Win

I’ve written a couple of blogs about grouping the methods in the Eclipse Collections RichIterable interface by Method Category. I have updated the RichIterable interface source using Custom Code Folding Regions in IntelliJ.

RichIterable in IntelliJ using Custom Code Folding Regions

There are nine categories of methods in RichIterable. If we look at the RichIterable type in the IntelliJ hierarchy view, we will see there are many interfaces that are subtypes of RichIterable.

Interface Hierarchy for RichIterable

The nine categories that have been organized for RichIterable, could be used to group the methods in all of these types as well. Each of these interfaces inherit all of the methods of RichIterable, and may selectively override methods with covariant return types.

Categorizing all of these types would be a lot of work. So I started thinking about how I could do this using Java. I wrote a little code to determine the method categories for most Collection types in Java. I categorized methods in Collection related classes in the JDK and Eclipse Collections.

The following is the method named countMethodsInClassesByCategory I wrote that does much of the work. It creates and then flips a BagMultimap. Everyone should do this at least once in their life.

public static void countMethodsInClassesByCategory(String title, MutableList<Class<?>> types)
{
ClassComparer classComparer = new ClassComparer(true, false, false);

BagMultimap<String, Class<?>> classCountsByCategory =
types.groupByEach(
clazz -> classComparer.getMethodNames(clazz)
.collect(ClassComparerAsciiDoc::categoryForMethodNamed),
Multimaps.mutable.bag.empty());

outputMethodCountsPerTypePerCategory(title, classCountsByCategory);

System.out.println();

BagMultimap<Class<?>, String> categoryCountsByClass =
classCountsByCategory.flip();

outputMethodCategoryCountsByType(title, categoryCountsByClass);
}

private static String categoryForMethodNamed(String method)
{
return PREFIXES.detectIfNone(
pair -> pair.getTwo().anySatisfy(method::startsWith),
() -> Tuples.pair("Uncategorized", Sets.mutable.empty())).getOne();
}

private static void outputMethodCategoryCountsByType(String title, BagMultimap<Class<?>, String> categoryCountsByClass)
{
System.out.println("." + title + " Method Category Counts by Type");
System.out.println("[%autowidth.stretch]");
System.out.println("|===");
System.out.println("|*Class* |*Method Counts by Category*");

MutableList<String> result2 = Lists.mutable.empty();
categoryCountsByClass.forEachKeyMultiValues((k, v) ->
result2.add("|*" + k.getSimpleName() + "* |"
+ v.toSortedBag().toStringOfItemToCount()));
result2.sortThis();
result2.forEach(System.out::println);

System.out.println("|===");
}

private static void outputMethodCountsPerTypePerCategory(String title, BagMultimap<String, Class<?>> classCountsByCategory)
{
System.out.println("." + title + " Method Counts per Type by Category");
System.out.println("[%autowidth.stretch]");
System.out.println("|===");
System.out.println("|*Category* | *Method Counts per Type*");

MutableList<String> result1 = Lists.mutable.empty();
classCountsByCategory.forEachKeyMultiValues((k, v) ->
result1.add("|*" + k + "* | "
+ v.collect(Class::getSimpleName).toSortedBag().toStringOfItemToCount()));
result1.sortThis();
result1.forEach(System.out::println);
System.out.println("|===");
}

This method takes a title and a List of Class to categorize. It creates two BagMultimap instances. The first instance counts the methods for each type by method category. By flipping this BagMultimap using the flip method, we get the counts for each Method Category, by Class.

The type ClassComparer is a class in the Eclipse Collections test-utils jar. I use it to get the method names for a class. In this particular case, the first of the three magic booleans I pass in (true, false, false), keeps the parameter types as part of the method name, but ignores the return type and packages. I described this class a while ago in the following blog.

The trick is associating method prefixes with some category. This is much faster than categorizing every method manually, which would be a lot of work. The following is the method I use to place methods in categories using their prefixes. The same code is used for JDK and Eclipse Collections types. Sometimes the prefixes match up (e.g., any, all, none, flat, group) and sometimes they don’t (e.g. select, reject, filter, collect, map). The collect method on Stream will get categorized into the transforming category. I would probably have put it in the aggregating category if I was categorizing individual methods. I still wish it would have been named something other than collect, but naming is hard, and this is what we have. :)

private static final MutableList<Pair<String, MutableSet<String>>> PREFIXES =
prefixesByCategory();

public static MutableList<Pair<String, MutableSet<String>>> prefixesByCategory()
{
MutableList<Pair<String, MutableSet<String>>> list = Lists.mutable.empty();
list.add(Tuples.pair(
"Iterating",
Sets.mutable.with(
"each", "forEach", "tap", "asLazy", "iterator", "spliterator", "tee", "drop",
"take", "generate", "iterate", "peek", "reverse", "listIterator", "parallel",
"sequential", "asParallel", "asReversed", "asUnmodifiable", "asSynchronized",
"stream", "parallelStream", "primitiveParallelStream", "primitiveStream",
"intIterator")));
list.add(Tuples.pair("Counting", Sets.mutable.with("count", "size")));
list.add(Tuples.pair(
"Testing",
Sets.mutable.with("any",
"all", "none", "contains", "is", "not", "corresponds", "equals", "if",
"comparator", "compareTo")));
list.add(Tuples.pair(
"Finding",
Sets.mutable.with("detect",
"get", "min", "max", "find", "indexOf", "lastIndexOf", "top", "bottom",
"binarySearch", "occurrences")));
list.add(Tuples.pair("Grouping", Sets.mutable.with("chunk", "group", "zip", "partitioning")));
list.add(Tuples.pair(
"Filtering",
Sets.mutable.with("select", "reject", "partition", "filter", "distinct", "limit",
"skip", "subList")));
list.add(Tuples.pair("Transforming", Sets.mutable.with("collect", "flat", "zip", "map")));
list.add(Tuples.pair(
"Aggregating",
Sets.mutable.with("aggregate",
"inject", "reduce", "reducing", "sum", "averaging", "average", "median",
"dotProduct", "hashCode")));
list.add(Tuples.pair(
"Converting",
Sets.mutable.with("into",
"to", "append", "make", "joining", "flip", "key", "values", "entry",
"clone", "freeze")));
list.add(Tuples.pair("Sorting", Sets.mutable.with("sort")));
list.add(Tuples.pair("Casting", Sets.mutable.with("cast")));
list.add(Tuples.pair(
"Mutating",
Sets.mutable.with("add",
"remove", "retain", "replace", "clear", "put", "shuffle", "set", "merge",
"compute", "with", "update", "swap")));
list.add(Tuples.pair(
"Creating",
Sets.mutable.with("of", "concat", "empty", "unordered", "builder", "newWith", "newEmpty")));
list.add(Tuples.pair("Closing", Sets.mutable.with("close", "onClose")));
list.add(Tuples.pair(
"Set Math",
Sets.mutable.with("union", "difference", "intersect", "symmetric", "cartesian", "powerSet")));
return list;
}

The following code shows how to pass a MutableList of Class instances to this method to create both BagMultimap instances and output them in a simple Asciidoc table.

public static void viewECReadableMethodsInClassesByCategory()
{
countMethodsInClassesByCategory("Eclipse Collections Readable",
Lists.mutable.with(
RichIterable.class,
LazyIterable.class,
Bag.class,
ListIterable.class,
SetIterable.class,
StackIterable.class,
MapIterable.class,
SortedSetIterable.class,
SortedBag.class,
SortedMapIterable.class));
}

The following is the output of this method.

This data can be easily turned into charts, like a pie chart for each type. Here’s the Bag result as a pie chart.

Counting Bag Methods by Method Categories

Once we step past readable interfaces into mutable and immutable interfaces, the number of categories has to necessarily increase. I add a method named Mutating to capture methods like add and remove.

Here are some of the mutable collection types for Eclipse Collections.

If we look at the MutableBag type as a pie chart, it will appear as follows.

Notice how most of the operations on MutableBag are read operations, and there are only 18 write (mutating) operations. Collections can be much more useful than just data collectors and containers. I’m writing a book somewhat related to this very topic right now. Stay tuned!

Categorizing Methods in the JDK Collection Types

We could use the same categories to group methods in JDK types as well.

Let’s see what counting methods by method category for Stream would look like.

Counting Stream Methods by Method Categories

Now let’s see what will happen if we do the same for the Collectors class.

Counting Collectors Methods by Method Categories

The following is the output I used for creating the pie charts for Stream and Collectors above. I used the same category naming I used for RichIterable, and added some new categories like Creating, Closing, and Sorting. Here’s a bunch of the JDK Collection related interfaces classes with methods counted by Method Category.

Let’s add a pie chart for the List interface so we can compare it to Stream.

Counting List Methods by Method Categories

Stream has all read-only methods, and List has mostly creating and mutating operations. This is an odd separation of data and behavior. It’s not what you would expect in an object-oriented library.

Additional Perspectives

Each of the collection types in Eclipse Collections and the JDK has a number of eager, lazy and lambda enabled methods.

  • An eager method is a method that evaluates immediately and returns a result.
  • A lazy method returns a result which may cause evaluation to occur at a later time, once a terminal (aka eager) method is called.
  • A lambda enabled method is a method which takes at least one Functional Interface as a parameter type. A Functional Interface is an interface with a single abstract method that can be represented with a lambda (e.g., Function, Predicate, Consumer). Lambda-enabled methods can be either eager or lazy.

The following table shows a selection of types in Eclipse Collections with the counts of eager, lazy, and lambda enabled methods.

Eager, Lazy, and Lambda Method Counts across Eclipse Collections types

It’s possible that eager and lazy could be method categories, and so could lambda-enabled. We might want to intersect these method categories with other method categories, so we could see for instance where there are lambda-enabled filtering methods, or eager filtering methods, or lazy transforming methods, etc.

I won’t be drawing any more pie charts for these. :)

Final Thoughts

There are a lot of opportunities for increasing the comprehension of our rich class libraries, if we can find useful ways of categorizing the methods in our classes. When we are consistent in our categorization, it can make it easier to find and compare things between classes. This can also lead to us evaluating and evolving our class designs in more useful and intentional manners.

Feel free to use the code in this blog, and categorize things in ways that they make sense to you. Just because I found the categories I used here helpful for understanding Eclipse Collections types, doesn’t mean everyone will find them useful. After all, naming is hard, and method categories are very much a difficult naming exercise.

I hope you found this useful. Thanks for reading!

I am the creator of and committer for the Eclipse Collections OSS project, which is managed at the Eclipse Foundation. Eclipse Collections is open for contributions. I am writing a book this year about Eclipse Collections. Stay tuned!

--

--

Donald Raab
Donald Raab

Written by Donald Raab

Java Champion. Creator of the Eclipse Collections OSS Java library (https://github.com/eclipse/eclipse-collections). Inspired by Smalltalk. Opinions are my own.

Responses (1)