[JAVA] Functional interface review notes

Overview

A review of the functional interface introduced in Java 1.8.

environment

reference

A simple review of how to write

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);

FunctionalInterface Annotation

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.

java.util.function package

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.

Consumer (T to void unary function)

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);

Usage example with JDK

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);

Function (T to R unary function)

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);

Usage example with JDK

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

Usage example with JDK

java.base : List<E>

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]}

Predicate (T to boolean function)

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);

Usage example with JDK

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

Supplier (infinite function to R)

Type parameter: T --Type of result provided by this supplier

Abstract method


T get();

Usage example with JDK

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

** 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);
}

Other than java.util.function package

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.

FileFilter

Abstract method


boolean	accept​(File pathname);

Usage example with JDK

java.base : File

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);

Runnable

Abstract method


void run();

Usage example with JDK

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");
});

PathMatcher

Abstract method


boolean	matches​(Path path);

Usage example with JDK

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);
}

Comparator<T>

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);

Usage example with JDK

java.base : Collections

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]

Callable<V>

Abstract method


V call() throws Exception;

Usage example with JDK

java.base : ExecutorService

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

Functional interface review notes
Functional interface introduction
Enum review notes
[Java] Functional interface
java standard functional interface
Java NIO 2 review notes
[Java] Functional interface / lambda expression
Java Collections Framework Review Notes
Queue / BlockingQueue / TransferQueue review notes
interface
Imitate Java's functional interface in Kotlin