Preface
JDK 8 was released back in 2014 as an official version. Since it’s an LTS release and is still being maintained and updated, most companies that “pin” their Java version are basically pinning to 8. But recently the long-term support release JDK 17 is also out, and later there might be more aggressive teams following up. A friend’s company has even put upgrading to JDK 17 on their evaluation roadmap—jealous, jealous. Back to JDK 8: I think the biggest highlight is that it let Java put one foot into the world of multi-paradigm programming languages.
Of course you’ll definitely say “come on, that’s nothing,” it’s all just syntactic sugar. But paired with some great libraries, that’s exactly what I want to introduce: a Java functional library. I accidentally saw it on GitHub last time, went through the docs, learned it, and then brought it into my own project. It really can make you so sweet you just shut up—writing feels like Scala. The library is called vavr, and here’s the GitHub link
So I want to introduce vavr: first, so I can summarize it for myself; second, to do a bit of evangelizing for you who also like things sweet. This post is the first part. To introduce vavr better, this one mainly covers functional programming in Java 8, and the detailed vavr introduction will be in the next part.
Functional Programming
To do functional programming, you first need functions. Before Java 8, Java really only had the concept of methods—there basically wasn’t a “function” concept. Second, functions need to be “first-class citizens.” By first-class citizen, I mean functions are on the same level as other primitive/reference types: they can be assigned, and they can be passed as parameters.
Java actually achieves this through an interface plus special lambda syntax. Here’s the lambda syntax:
// 前面是入参,然后中间使用一个 “->” 的符号连接, 后面是表达式
Type var -> 表达式;
/*
两个入参前面需要括号括起来(一个也可以括号,但是通常省略)
如果表达式很复杂,一句写不完,则需要使用{}括起来,如果有返回值还需要并且手动return
*/
(Type1 var1, Type2 var2) -> {
表达式1;
表达式2;
}
// 如果函数没有入参,则必须要括号,但是括号里面什么都不xei
() -> 表达式;
// 另外类型java会自动推断,其实入参的类型可以不写,只需要取个名字就好了
var -> 表达式;
(var1, var2) -> 表达式
Here are some real examples
// 只有一个参数的语法(括号省略,类型也省略)
str -> System.out.print(str);
str -> System.out.print(str);
// 两个参数的例子(两个参数必须要括号)
(str1, str2) -> System.out.print(str + str2);
// 没有入参,复杂表达式,还带返回值
() -> {
String someget = new String("someget");
String result = someget.concat(".cn");
return result;
};
A function written as a lambda can be assigned to an interface, and then you can pass inputs and outputs through that interface. Java itself is already huge; now it ships two major releases a year, but no matter how it changes, the syntax won’t undergo a massive upheaval. Some people say lambda is syntactic sugar for anonymous inner classes—it kind of smells like that, but calling it “syntactic sugar” isn’t accurate. After compiling a lambda, there isn’t a separate .calss bytecode file; the corresponding bytecode is generated at runtime.
p.s. One thing to note: lambda expressions in Java 8 are not full closures, because they only close over values, not variables. That is, variables used inside a lambda must be declared final or be implicitly final (meaning they’re not explicitly final, but they are never modified in the code). This also shows up in the immutable data structures in the next post, because this restriction makes your code insanely safe.
Functional Interfaces
Above I introduced how Java implements functional programming. Java defines functions through interfaces: after you define a lambda, you can assign it to an interface, which means your lambda is actually implementing one method in that interface.
You can define this interface yourself—just define one unimplemented (abstract) method. Note this is a hard requirement: the interface must have exactly one unimplemented (abstract) method. Java 8 introduced default methods in interfaces, and having default methods does not prevent it from being a functional interface. To let you check whether it meets the spec before compilation, Java 8 added the @FunctionalInterface annotation. With this annotation, the compiler knows you intend to write a functional interface, so if your interface doesn’t have an unimplemented method, it won’t compile. Functional interfaces are also called SAM interfaces, short for Single Abstract Method interfaces.
So if writing an interface with one unimplemented method can accept a lambda, and everyone’s method is unimplemented anyway, aren’t your interface and my interface the same? Yes and no. The method is indeed unimplemented, but the input and output types of each method are different. So the difference between functional interfaces is actually defined by the input and output of that single abstract method.
In theory, functional interfaces with the same input and output are the same. Java 8 has many built-in functional interfaces. Next I’ll introduce the four most common ones—you definitely use them a lot.
Function<T, R>
The official doc says it “accepts one argument and produces a result.” The name of this functional interface is slightly misleading because it’s literally called “Function.” What it actually does is accept an object (generic T) and convert it into another object (generic R). In Function<T, R>, the first type parameter is the input generic, and the second is the output generic.

As you can see, its unimplemented method is apply. If you’ve used Java Stream or Scala’s map operator, you’ll definitely be familiar with it: it maps each element in the stream into another type you want.

Consumer
The official doc says it “accepts a single input argument and returns no result.” In other words, it only accepts one generic type and returns nothing (void). You can use side effects to accompany some expected state change or event.

The unimplemented method of Consumer is accept. It takes an object and returns void. The most commonly used forEach uses this interface.

Predicate
This functional interface takes in a type, but returns a boolean value (true or false). Also, quick rant: some books translate predicate as “谓词”—that’s seriously anti-human.

Its unimplemented method is test. Smart you probably already thought of it: what you pass into filter in Java Stream is exactly this interface.

Supplier
The last commonly used one is a bit similar to Consumer, but reversed: it has no input parameters, but it has a return value.

Its unimplemented method is get. If you’ve written () -> xxxx, you should immediately react to this. For example, supplyAsync in CompletableFuture takes a supplier.

Finally, let’s summarize these four most common functional interfaces. Writing them separately above is a bit messy, so here’s a table to memorize them together:
| Interface Name | Chinese | Purpose | Abstract Method |
|---|---|---|---|
| consumer |
消费者 | T is input; accepts a type and produces side effects |
void accept(T t) |
| function <T, R> | 函数 | T is input, R is output; accepts a T and returns an R object |
R apply(T t) |
| predicate |
断言 | T is input; accepts a T and returns a boolean value |
boolean test(T t) |
| supplier |
提供者 | T is output; no input (written as () -> xxx), returns a T object |
T get() |

Afterword
I didn’t say a single word about vavr here. This is mainly a summary of Java’s functional programming interfaces. Of course I only listed four—there are actually many more, like adding a Binary prefix and it becomes another functional interface (go check the source code). If the built-in ones aren’t enough, you can totally define your own. These are just what Java ships with. For example, if you change Function’s return type to boolean, it becomes Predicate. We can absolutely create something like a Function that returns Integer and call it IntFunction or something (actually the JDK already provides it 😂).
Functional programming is the foundation, so in the next post I’ll introduce the vavr functional library that I’ve recently been obsessed with. It can make writing Java as smooth as Scala.
References
All articles in this blog, unless otherwise stated, are licensed under @Oreoft . Please indicate the source when reprinting!