Functional tips for Java developers.

Programming, the path to freedom - OOP, the path to relativity. FP, the path to absolutism. 😆

Calofir Emil
5 min readMay 17, 2019

I’m pure and immutable, a saint, you cannot change me only my Maker can, you can try to clone me but it is in vain, my nature is persistent.

OK?! 🙄 Java is difficult, it is better to be pragmatic than purist. You can try to limit your unavoidable state and push it to the edge of your system, isolate it so you can be more functional.

And…

Check this out…

1. Data In Data Out

Try to make your code pure (no side effects, compromise on logging); target around 80% of your codebase.

Why?

Easy to test - complete control of the input and output;

Easy to read and understand. In theory, you don’t need to know the state of the program to understand what a piece of code does.

class Customer {
public boolean validateEmail(){}//access a global state
// vs
public static boolean validateEmail(String email){}
//or more general method
public static boolean valid(HasEmailAdress anything){
Email email = anything.getEmailAddress();
//validation logic
return true;
}
}

interface HasEmailAdress{
Email getEmailAddress();
}

If your functions don’t access the global state, modify the input or bother the rest of the world then Make Them Static.

If you have side effects, then MAKE THEM VISIBLE to the reader, name functions, and methods according to their side effects. A good suggestion can be found here.

verbs for naming functions that produce side effects and adjectives for the rest.

sort() vs sorted()
reverse() vs reversed()
//has verb in the name
void processOrder(final Order order) {
if (BussinessRules.canBillOrder(order) {
val taxes = fetch(order.type()); // side effects
order.bill(taxes); // side effects
val invoice = order.billed(/*returns a copy*/);
persist(invoice); //side effects
}
}
class BussinessRules {
//no side effects; no verb
public static boolean canBillOrder(final Order order) {
Predicate<Movie> hasBillableContent =
movie -> "NotFree".equals(movie.type()) && movie.year() > 1980;
List<Movie> movies = order.movies();
return movies.stream().anyMatch(hasBillableContent);
}
}

class Order {
//no side effects
public List<Movie> movies() {
return ImmutableList.copyOf(this.movies);
}

//has a verb in the name
public void bill(Financials financials) {
//change internal state of the object;
}

//no verb in name
public OrderInvoice billed() {
//return copy of the bill
}
}

2. Immutability

A large class of software bugs boils down to one section of code modifying data in another section in an unexpected way. This type of bug becomes even more heinous in the multi-core world we all live in now. By making our data immutable, we can avoid this class of bugs altogether.

The less state that can change, the less you have to think about => Think about concurrency (the excuse you give to your peers)

Using immutable data is an oft-repeated bit of advice in the Java world; it’s mentioned in Effective Java [Blo08]: “Minimize Mutability, among other places, but it is rarely followed. This is large because Java wasn’t designed with immutability in mind, so it takes a lot of programmer effort to get it.” In Java, everything is mutable by default.

How to use immutability
In classes? Use final

class Customer{
private final String name;
private final String address;
public Customer(String name, String address){ … }
public String getName(){ … }
public String getAddress(){ … }
}
or using lombok @Value on your mutable class

Collections ? just use Guava

public final Customer {
private ImmutableList<Phone> phones;
public Customer (List<Phone> phones) {
this.phones = ImmutableList.copyOf(phones);
}
}

Use Builder pattern to create immutable objects

//what to change the list??
ImmutableList.builder().addAll(phones).add(newPhone).build();

Make a defensive copy to not change the original

public final Customer = clone(mutableCustomer);

3. Verbs are people too

In Java, functions are not first-class citizens. What is not a function must be escorted by a Noun. Passing around instructions is very useful, we already do this in strategy pattern, command pattern, or callbacks by the use the Functional Interface (aka Functor, Function Object).

Functional Interface is a pattern that in the object-oriented world approximates the behavior of the functions (higher-order functions) of the functional world.

If we use functions as parameters we can get rid of the above patterns and thanks to Java 8 they are easy to use now. For example replace them with Anonymous Functions: (a , b) -> a + b

enum MultipleValueFieldValidator{
DATE(Type.DATE, DateUtils.isDate),
DEBT(Type.ODD,e -> Long.valueOf(e) < 0),
PRICE(FieldType.PRICE, e-> e.endsWith("$"));
//…
public Function<String, Boolean> getValidator(FieldType type){…}
}
Boolean isValid = MultipleValueFieldValidator
.getValidator(formField.getType())
.apply(formField.getValue())

4. Declarative style

Write your code in such a way that it describes what you want to do, and not how you want to do it. It is left up to the language compiler to figure out how.

Examples: SQL/ Excel/ Prolog.

Adopting this style of writing your code makes it more readable code and you end up with smaller, simpler pieces.

//collect main languages for customers that are programmers
customers.stream().filter(isProgrammer).map(mainLanguage).collect();

5. Strong typing

To find more errors at compile time.
Try to come up with a universal vocabulary in the context of the application that can be used by everyone (developers, business, testers).

Java is not strong typed and implementing this can be tedious.

//this is not strong typed;
public final class Customer{
public Customer(String name, String email){
this.name=name;
this.email=email;
}
}
//works but wrong parameter values
new Customer("Email", "Name")
//we can fix it using a builder.
Customer.builder()
.name("Name")
.email("Email")
.build();
//or better create additional typesclass Name{ final String name; //... }
class Email{ final String email; //...}
Customer.builder()
.name(new Name("Name"))
.email(new Email("Email"))
.build();
new Customer(new Name(“Name"),
new Email("Email"))

Maven code generation

Creating lots of small classes can be handled by the compiler. You can model all your types in a tool and export them into a JSON file and generate classes during compilation with maven code generation.

6. Lazy evaluation

I chose a lazy person to do a hard job.
Because a lazy person will find an easy way to do it. ― Bill Gates

Lazy evaluation means delaying a computation until the result is needed, and that’s to be opposed to eager evaluation. Good for saving resources, many computations are not needed if some conditions are meet or you can add logic on the fly by composing the suppliers.

We already do this, Suppliers, Factories, SQL Cursors.

Supplier<UUID> randomUUIDGenerator = UUID::randomUUID; //lazy
Supplier<Stream<UUID>> keysGenerator =
() -> Stream.generate(randomUUIDGenerator); //lazy

List<UUID> nextTenKeys =
keysGenerator.get()
.limit(10)
.collect(Collectors.toList()); //eager
List<UUID> nextFiveKeys =
keysGenerator.get()
.limit(5)
.collect(Collectors.toList()); //eager

Lazy evaluation with a Memoization pattern may be a good optimization to use.

When combining the power of the two programming paradigms: with OOP as the outer layer, managing the mutable state, and the FP in the core handling the business logic and computations using immutability and function composition, we can enhance our development experience.

--

--