Code readability with FluentItarable and Java 8

Each and every developer has to think about code readability and review his habits when working with new technologies and solutions. As you probably know on 18 March 2014 Java 8 was released and changed a lot in Java world.

Just to name few main features introduced in Java 8:

  • Lambda Expressions
  • Default methods
  • Method parameter reflection.
  • etc.

Full list of features in available here.

In this article I would like to compare how usage of lambdas influence code readability on very simple examples.

With Java 7

So lets start with some POJO class.

public class Item {

	int weight;
	BigDecimal price;

	public Item(int weight, BigDecimal price) {
		this.weight = weight;
		this.price = price;
	}

	public int getWeight() {
		return weight;
	}

	public BigDecimal getPrice() {
		return price;
	}
}

Now let’s imagine that we want to write function which will return price converted to String for all items with weight more than 5.
In plain Java it can look like this:

public List getBigItemsPricesAsStrings(List items) {
	List result = new ArrayList();
	for (Item item : items) {
		if (item.getWeight() > 5) {
			result.add(item.getPrice().toString());
		}
	}
	return result;
}

It is not specially readable. We have if statement with some magic numbers and conversion from BidDecimal to String in the next line. In order to know how this method works we have to analyze it line by line. What is the first step to make it better? Refactor.

public List getBigItemsPricesAsStrings(List items) {
	List result = new ArrayList();
	for (Item item : items) {
		if (itemIsBig(item)) {
			convertPrice(result, item);
		}
	}
	return result;
}

private void convertPrice(List result, Item item) {
	result.add(item.getPrice().toString());
}

private boolean itemIsBig(Item item) {
	return item.getWeight() > 5;
}

Now it is better, but still in one method we mix different things – we loop through items, we check condition and convert something. So now lets change whole idea and use FluentIterable.

import static com.google.common.collect.FluentIterable.from;

public List getBigItemsPricesAsStrings(List items) {
	return from(items).filter(onlyBig()).transform(priceToString()).toList();
}

private Function priceToString() {
	return new Function() {

		@Override
		public String apply(Item item) {
			return item.getPrice().toString();
		}

	};
}

private Predicate onlyBig() {
	return new Predicate() {

		@Override
		public boolean apply(Item item) {
			return item.getWeight() > 5;
		}

	};
}

That was usage of FluentItarable in Java 7 so without lambdas. The desired method is now one line and is quite clear. From this code we can easily tell what it does. In order to use FluentIterable we have to implement Predicate and Function interfaces. I did it by adding methods to class which return desired types with implementation. Those elements can be extracted to separate compilation units or to class like: ItemPredicates or ItemFunctions if we have more of them.

With Java 8

So now we have Java 8 and lambdas. Lets see what we can do with this example.

import static com.google.common.collect.FluentIterable.from;

public List getBigItemsPricesAsStrings(List items) {
	return from(items).filter(item -> item.getWeight() > 5)
		.transform(item -> item.getPrice().toString()).toList();
}

I would like to stop here for a moment. In previous example we have splitted levels of abstraction and details were hidden in Predicate and Function implementations. Now with direct usage of lambdas we mix those levels again. We are getting one line but it starts to look a little bit ugly. In comparison to previous implementation we again have to think and analyze what this code does. We don’t see it immediately.

If we now write next POJO class

public class AnotherItem {

	int weight;
	int lenght;
	int height;
	int width;
	BigDecimal price;

	public AnotherItem(int weight, int lenght, int height, int width, BigDecimal price) {
		this.weight = weight;
		this.lenght = lenght;
		this.height = height;
		this.width = width;
		this.price = price;
	}

	public int getWeight() {
		return weight;
	}

	public int getLenght() {
		return lenght;
	}

	public int getHeight() {
		return height;
	}

	public int getWidth() {
		return width;
	}

	public BigDecimal getPrice() {
		return price;
	}
}

And would like to use it with similiar method, we would get:

import static com.google.common.collect.FluentIterable.from;

public List getBigAnotherItemsPricesAsStrings(List items) {
	return from(items).filter(item -> item.getWeight() > 5 && item.getLenght() > 10
			&& item.getHeight() > 20 && item.getWidth() > 5).transform(item -> item.getPrice().toString()).toList();
}

As you can see it is getting worse and worse. So how to overcome this problem? Lets Extract Method

import static com.google.common.collect.FluentIterable.from;

public List getBigAnotherItemsPricesAsStrings(List items) {
	return from(items).filter(item -> isItemBig(item)).transform(item -> convertPrice(item)).toList();
}

private String convertPrice(AnotherItem item) {
	return item.getPrice().toString();
}

private boolean isItemBig(AnotherItem item) {
	return item.getWeight() > 5 && item.getLenght() > 10
			&& item.getHeight() > 20 && item.getWidth() > 5;
}

So what did we get in the end? Instead of simple method call from Java 7 (methods create Predicate and Function) we have lambdas with methods containing logic. It is quite readable and doesn’t contain boilerplate code. The difference is basically only syntactic sugar.

Summary

With Java 8 we can use lambdas and stop writing boilerplate code. It can be very useful, but we have to think when writing and we have to find good balance between code readability and minimalism. Usually we tend to write less but with writing less can lose a lot. We can lose on code readability and get hit by future maintenance. Also we start to mix levels of abstraction and details of implementation with overall logic.

Moreover, with bad usage of lambdas we get hard to understand code. This code can no longer work as its own documentation.

So as always, when new things are available to us – think before use, and if you use, use it wisely.