A review of the functional interface introduced in Java 1.8.
environment
reference
In this code, the forEach method receives a Consumer-type functional interface. This writing is simplified, but
List<String> list = List.of("a", "b", "c");
list.forEach(System.out::println);
If you write Java 1.8 or earlier, it will be as follows.
public class PrintLine implements Consumer<Object> {
@Override
public void accept(Object t) {
System.out.println(t);
}
}
List<String> list = List.of("a", "b", "c");
PrintLine println = new PrintLine();
list.forEach(println);
Also, if you write an anonymous class, it will be as follows.
List<String> list = List.of("a", "b", "c");
list.forEach(new Consumer<Object>() {
@Override
public void accept(Object t) {
System.out.println(t);
}
});
You can simplify these writings by using lambda expressions introduced in Java 1.8.
// (1)If you change the writing style of anonymous classes to lambda expression, you can write like this.
list.forEach((String t) -> {
System.out.println(t);
});
// (2)The argument type is self-explanatory and can be omitted.
list.forEach((t) -> {
System.out.println(t);
});
// (3)If there is only one argument, the argument()Can be omitted.
list.forEach(t -> {
System.out.println(t);
});
// (4)If the code in the block is one line{}Can be omitted. You don't even need a semicolon in your code.
list.forEach(t -> System.out.println(t));
// (5)Functional interface abstract method (Consumer in this case).accept) signature and
//If the signatures of the methods to be executed match, it can be written as a method reference.
list.forEach(System.out::println);
Functional interfaces are annotated with @FunctionalInterface
,
@FunctionalInterface
public interface Consumer<T> {
//...
}
An annotation type for informational purposes used to indicate that an interface type declaration is intended to be a functional interface as defined in the Java language specification. Conceptually, a functional interface has only one abstract method.
As quoted above, the condition that satisfies the definition of a functional interface is that the interface has only one abstract method declared.
The compiler treats any interface that meets the definition of a functional interface as a functional interface, regardless of whether the FunctionalInterface annotation type exists in the interface declaration.
Also, if this condition is met, the compiler will be treated as a functional interface with or without this annotation.
This package defines a basic functional interface. Typical examples are Consumer
, Function
, Predicate
, Supplier
, and there are also ʻIntCounsumer and ʻIntFunction
which are specializations of these.
There are also functional interfaces implemented for specific purposes outside this package.
Type parameter: T-Operation input type
Abstract method
void accept(T t);
BiConsumer
There is also a functional interface called BiConsumer
that accepts two arguments, which is a specialization of Consumer
.
Type parameter: T --Type of first argument of operation U --Type of second argument of operation
Abstract method
void accept(T t, U u);
java.base : java.util.Iterator<E>
forEach
One of the most common ways to use Consumer is the iterator forEach method.
Iterable.forEach
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
sample Since Java 1.8, do not write like (1), but write like (2) or (3) with a lambda expression. If you want to use one line like (2), you don't need to enclose it in a block and you can write like (3). Furthermore, you can simplify the code by writing it as a method reference as in (4).
List<String> list = List.of("a", "b", "c");
// (1)
list.forEach(new Consumer<String>() {
@Override
public void accept(String t) {
System.out.println(t);
}
});
// (2)
list.forEach(s -> {
System.out.println(s);
});
// (3)
list.forEach(s -> System.out.println(s));
// (4)Method reference
list.forEach(System.out::println);
Type parameter: T-Function input type R-Function result type
Abstract method
R apply(T t);
BiFunction
There is also a functional interface called BiFunction
that takes two arguments, which is a specialization of Function
.
Type parameter: T --Type of first argument of function U --Type of second argument of function R-Function result type
Abstract method
R apply(T t, U u);
java.base : java.util.Map<K,V>
computeIfAbsent
Map.computeIfAbsent
default V computeIfAbsent(K key,
Function<? super K, ? extends V> mappingFunction) {
Objects.requireNonNull(mappingFunction);
V v;
if ((v = get(key)) == null) {
V newValue;
if ((newValue = mappingFunction.apply(key)) != null) {
put(key, newValue);
return newValue;
}
}
return v;
}
sample
Map<String, Integer> map = new HashMap<>();
// (1)
Integer value = map.computeIfAbsent("apple", new Function<String, Integer>(){
@Override
public Integer apply(String t) {
return t.length();
}
});
// (2)
value = map.computeIfAbsent("banana", s -> {
return s.length();
});
// (3)
value = map.computeIfAbsent("cherry", s -> s.length());
// (4)Instance method reference
value = map.computeIfAbsent("durian", String::length);
UnaryOperator<T>
A functional interface that has Function
in the super interface and specializes in Function that returns the same type as the argument type.
Type parameter: T-operand operand and result type
replaceAll
List.replaceAll
default void replaceAll(UnaryOperator<E> operator) {
Objects.requireNonNull(operator);
final ListIterator<E> li = this.listIterator();
while (li.hasNext()) {
li.set(operator.apply(li.next()));
}
}
sample
List<String> list = new ArrayList<>();
list.add("apple");
list.add("banana");
list.add("cherry");
// (1)
list.replaceAll(new UnaryOperator<String>() {
@Override
public String apply(String t) {
return t.toUpperCase();
}
});
// (2)
list.replaceAll(t -> t.toUpperCase());
// (3)Instance method reference
list.replaceAll(String::toUpperCase);
UnaryOperator.identity
The UnaryOperator interface has a static method called identity. (It is also in the Function interface.)
static <T> UnaryOperator<T> identity() {
return t -> t;
}
The code that you often see as a usage example of this method converts List to Map as shown below. This conversion sets the id of the Item class in the key of the map and the instance of the Item class in the value of the map, but the intent of the code becomes clear by using the identity method that returns the input argument.
List<Item> list = new ArrayList<>();
list.add(new Item(1L, "apple"));
list.add(new Item(2L, "banana"));
list.add(new Item(3L, "cherry"));
//Map<Long, Item> fruitIdMap = list.stream()
// .collect(Collectors.toMap(Item::getId, i -> i));
// more than better
Map<Long, Item> fruitIdMap = list.stream()
.collect(Collectors.toMap(Item::getId, UnaryOperator.identity()));
System.out.println(fruitIdMap);
// {1=Item [id=1, name=apple], 2=Item [id=2, name=banana], 3=Item [id=3, name=cherry]}
Type parameter: T-Predicate input type
Abstract method
boolean test(T t);
BiPredicate
There is also a functional interface called BiPredicate
that accepts two arguments that are a specialization of Predicate
.
Type parameter: T --Type of the first argument of the predicate U --Type of the second argument of the predicate
Abstract method
boolean test(T t, U u);
java.base : java.util.Collection<E>
removeIf
Collection.removeIf
default boolean removeIf(Predicate<? super E> filter) {
Objects.requireNonNull(filter);
boolean removed = false;
final Iterator<E> each = iterator();
while (each.hasNext()) {
if (filter.test(each.next())) {
each.remove();
removed = true;
}
}
return removed;
}
sample This is a sample that removes even numbers from list. Since removeIf has a side effect on List, an error will occur if it is an Immutable List.
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
// (1)
list.removeIf(new Predicate<Integer>() {
@Override
public boolean test(Integer t) {
return t % 2 == 0;
}
});
// (2)
list.removeIf(t -> {
return t % 2 == 0;
});
// (3)
list.removeIf(t -> t % 2 == 0);
java.base : java.util.Optional<T>
filter
Optional.filter
public Optional<T> filter(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
if (!isPresent()) {
return this;
} else {
return predicate.test(value) ? this : empty();
}
}
sample
Optional<Integer> opt = Optional.of(1);
// (1)
Optional<Integer> result = opt.filter(new Predicate<Integer>() {
@Override
public boolean test(Integer t) {
return t % 2 == 0;
}
});
// (2)
result = opt.filter(t -> {
return t % 2 == 0;
});
// (3)
result = opt.filter(t -> t % 2 == 0);
result.ifPresentOrElse(System.out::println, () -> {
System.out.println("it's odd number");
});
// → it's odd number
java.base : java.util.regex.Pattern
asPredicate
Pattern.asPredicate
public Predicate<String> asPredicate() {
return s -> matcher(s).find();
}
sample
Pattern pattern = Pattern.compile("^\\d+$");
List<String> list = List.of("123", "abc", "456", "78d");
List<String> result = list.stream()
.filter(pattern.asPredicate())
.collect(Collectors.toList());
result.forEach(System.out::println);
// → 123
// → 456
Type parameter: T --Type of result provided by this supplier
Abstract method
T get();
java.base : java.util.Optional<T>
orElseGet
Optional.orElseGet
public T orElseGet(Supplier<? extends T> supplier) {
return value != null ? value : supplier.get();
}
sample
Optional<String> fruit = Optional.ofNullable(null);
// (1)
String result = fruit.orElseGet(new Supplier<String>() {
@Override
public String get() {
return "banana";
}
});
// (2)
result = fruit.orElseGet(() -> {
return "banana";
});
// (3)
result = fruit.orElseGet(() -> "banana");
// (4)Method reference
result = fruit.orElseGet(SupplierDemo::getDefault);
SupplierDemo
public static String getDefault() {
return "banana";
}
orElseThrow
Optional.orElseThrow
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
if (value != null) {
return value;
} else {
throw exceptionSupplier.get();
}
}
sample
Optional<String> fruit = Optional.ofNullable(null);
// (1)
String result = fruit.orElseThrow(new Supplier<RuntimeException>() {
@Override
public RuntimeException get() {
return new FruitNotFoundException();
}
});
// (2)
result = fruit.orElseThrow(() -> {
return new FruitNotFoundException();
});
// (3)
result = fruit.orElseThrow(() -> new FruitNotFoundException());
// (4)Constructor reference
result = fruit.orElseThrow(FruitNotFoundException::new);
FruitNotFoundException
class FruitNotFoundException extends RuntimeException {
public FruitNotFoundException() {
super();
}
}
** Sample constructor reference to generate an array **
List<String> list = List.of("apple", "banana", "cherry", "durian", "elderberry");
// String[] fruits = list.toArray(size -> new String[size]);
// more than better?
String[] fruits = list.toArray(new String[0]);
The code to convert the above collection to an array is as follows when written with a constructor reference.
List<String> list = List.of("apple", "banana", "cherry", "durian", "elderberry");
String[] fruits = list.toArray(String[]::new);
java.logging : java.util.logging.Logger
log
Logger.log
public void log(Level level, Supplier<String> msgSupplier) {
if (!isLoggable(level)) {
return;
}
LogRecord lr = new LogRecord(level, msgSupplier.get());
doLog(lr);
}
An interface that has existed since version 1.8, but can be used as an assignment destination for lambda expressions or method references because it meets the requirements of a functional interface.
Abstract method
boolean accept(File pathname);
listFiles
File.listFiles
public File[] listFiles(FileFilter filter) {
String ss[] = list();
if (ss == null) return null;
ArrayList<File> files = new ArrayList<>();
for (String s : ss) {
File f = new File(s, this);
if ((filter == null) || filter.accept(f))
files.add(f);
}
return files.toArray(new File[files.size()]);
}
sample
File directory = new File("D:\\temp");
Long limit = 1024L * 1024L * 7L;
File[] overLimitFiles = directory.listFiles(pathname -> pathname.length() > limit);
Abstract method
void run();
java.base : java.util.Optional<T>
ifPresentOrElse
public void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction) {
if (value != null) {
action.accept(value);
} else {
emptyAction.run();
}
}
sample
Optional<String> opt = Optional.ofNullable(null);
opt.ifPresentOrElse(s -> {
System.out.println(s);
},() -> {
System.out.println("null");
});
Abstract method
boolean matches(Path path);
java.base : java.nio.file.FileSystem
getPathMatcher
FileSystem.getPathMatcher
public abstract PathMatcher getPathMatcher(String syntaxAndPattern);
sample
Deviating from the subject of the article, the value passed to the getPathMatcher
method is a string of Syntax and Pattern separated by a colon (:).
If you specify "glob" for Syntax, you can specify a simpler pattern similar to a regular expression for Pattern, and if you specify "regex", you can specify a regular expression for Pattern.
FileSystem fileSystem = FileSystems.getDefault();
PathMatcher matcher = fileSystem.getPathMatcher("glob" + ":" + "**/*.java");
Path path = Paths.get("D:", "temp");
try {
Files.walk(path).filter(matcher::matches).forEach(System.out::println);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
In addition to compare
, this interface declares an abstract method calledboolean equals (Object obj)
, as quoted below.
If the interface declares an abstract method that overrides one of the public methods in java.lang.Object, it will not be reflected in the number of abstract methods in the interface. The reason is that any implementation of that interface includes implementations from java.lang.Object or elsewhere.
It meets the conditions for a functional interface.
Abstract method
int compare(T o1, T o2);
min
Collections.min
@SuppressWarnings({"unchecked", "rawtypes"})
public static <T> T min(Collection<? extends T> coll, Comparator<? super T> comp) {
if (comp==null)
return (T)min((Collection) coll);
Iterator<? extends T> i = coll.iterator();
T candidate = i.next();
while (i.hasNext()) {
T next = i.next();
if (comp.compare(next, candidate) < 0)
candidate = next;
}
return candidate;
}
sample
This is a sample that sorts the Priority and Id of the Job class in ascending order and returns the smallest Job. As you can see below, if the collection has a large number of elements, it will be costly.
This method iterates through the collection, so it takes time proportional to the size of the collection.
List<Job> list = List.of(
// Id, Priority
new Job(1L, 3),
new Job(2L, 3),
new Job(3L, 2),
new Job(4L, 1),
new Job(5L, 1),
new Job(6L, 2)
);
// (1)
Job minJob = Collections.min(list, new Comparator<Job>() {
@Override
public int compare(Job j1, Job j2) {
if (Objects.equals(j1.getPriority(), j2.getPriority())) {
return Long.compare(j1.getId(), j2.getId());
}
return Integer.compare(j1.getPriority(), j2.getPriority());
}
});
System.out.println(minJob);
// → Job [id=4, priority=1]
// (2)
minJob = Collections.min(list, Comparator.comparing(Job::getPriority).thenComparing(Job::getId));
System.out.println(minJob);
// → Job [id=4, priority=1]
Abstract method
V call() throws Exception;
submit
ExecutorService.submit
<T> Future<T> submit(Callable<T> task);
sample
ExecutorService service = Executors.newSingleThreadExecutor();
// (1)
Future<LocalDateTime> future = service.submit(new Callable<LocalDateTime>() {
@Override
public LocalDateTime call() throws Exception {
TimeUnit.SECONDS.sleep(5L);
return LocalDateTime.now();
}
});
/*
// (2)
Future<LocalDateTime> future = service.submit(() -> {
TimeUnit.SECONDS.sleep(5L);
return LocalDateTime.now();
});
*/
LocalDateTime result = future.get();
service.shutdown();
Recommended Posts