概要
Java 1.8で導入された関数型インターフェースのおさらいです。
環境
- Windows 10 Professional
- OpenJDK 11.0.2
参考
- [パッケージjava.util.function] (https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/util/function/package-summary.html)
- [Java 8 のイディオム: パススルーに代わる手段] (https://www.ibm.com/developerworks/jp/java/library/j-java8idioms5/index.html)
- [Java 8 のイディオム: カスタム関数型インターフェースを作成する方法を学び、可能な場合は常に組み込み関数型インターフェースを使うべき理由を学ぶ] (https://www.ibm.com/developerworks/jp/java/library/j-java8idioms7/index.html)
書き方の簡単なおさらい
このコードでforEachメソッドが受け取っているのがConsumer型の関数型インターフェースです。
この書き方は簡略化されていますが、
List<String> list = List.of("a", "b", "c");
list.forEach(System.out::println);
Java 1.8以前の書き方をすれば、次のようになります。
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);
また、匿名クラスの書き方にすれば、次のようになります。
List<String> list = List.of("a", "b", "c");
list.forEach(new Consumer<Object>() {
@Override
public void accept(Object t) {
System.out.println(t);
}
});
これらの書き方を、Java 1.8で導入されたラムダ式を使うことで簡略化することができます。
// (1) 匿名クラスの書き方をラムダ式に変えると、このように書けます。
list.forEach((String t) -> {
System.out.println(t);
});
// (2) 引数の型は自明なので省略できます。
list.forEach((t) -> {
System.out.println(t);
});
// (3) 引数が1つだけの場合は、引数の()を省略できます。
list.forEach(t -> {
System.out.println(t);
});
// (4) ブロック内のコードが1行の場合は{}を省略できます。コードのセミコロンも不要です。
list.forEach(t -> System.out.println(t));
// (5) 関数型インターフェースの抽象メソッド(この場合はConsumer.accept)のシグネチャと
// 実行するメソッドのシグネチャが一致している場合はメソッド参照という書き方にすることができます
list.forEach(System.out::println);
[FunctionalInterfaceアノテーション] (https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/lang/FunctionalInterface.html)
関数型インターフェースには@FunctionalInterface
アノテーションが付いていますが、
@FunctionalInterface
public interface Consumer<T> {
//...
}
インタフェース型の宣言を、Java言語仕様に定義されている関数型インタフェースとすることを目的としていることを示すために使われる情報目的の注釈型です。 概念上、1つの関数型インタフェースには抽象メソッドが1つだけあります。
上記に引用したように、関数型インターフェースの定義を満たす条件は、インターフェースに抽象メソッドが1つだけ宣言されていることです。
インタフェース宣言にFunctionalInterface注釈型が存在しているかどうかにかかわらず、コンパイラは関数型インタフェースの定義を満たすどのインタフェースも関数型インタフェースとして扱います。
また、この条件を満たせばこのアノテーションの有無に関わらずコンパイラは関数型インターフェースとして扱われます。
java.util.functionパッケージ
このパッケージには基本的な関数型インターフェースが定義されています。代表的なものではConsumer
、Function
、Predicate
、Supplier
があり、これらを特殊化したIntCounsumer
やIntFunction
などもあります。
また、このパッケージ外にも特定目的のために実装された関数型インターフェースがあります。
Consumer (Tからvoidへの単項関数)
型パラメータ:
T - オペレーションの入力の型
void accept(T t);
BiConsumer
Consumer
を特殊化した、2つの引数を受け取るBiConsumer
という関数型インターフェースもあります。
型パラメータ:
T - オペレーションの第1引数の型
U - オペレーションの第2引数の型
void accept(T t, U u);
JDKでの利用例
[java.base] (https://docs.oracle.com/javase/jp/11/docs/api/java.base/module-summary.html) : [java.util.Iterator<E>] (https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/util/Iterator.html)
forEach
Consumerの使い方でよくみるものにイテレータのforEachメソッドがあります。
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
サンプル
Java 1.8以降は(1)のような書き方はせず、ラムダ式で(2)または(3)のように書きます。
(2)のように1行でかける場合はブロックで囲む必要がなく(3)のように書けます。
さらに(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) メソッド参照
list.forEach(System.out::println);
Function (TからRへの単項関数)
型パラメータ:
T - 関数の入力の型
R - 関数の結果の型
R apply(T t);
BiFunction
Function
を特殊化した、2つの引数を受け取るBiFunction
という関数型インターフェースもあります。
型パラメータ:
T - 関数の第1引数の型
U - 関数の第2引数の型
R - 関数の結果の型
R apply(T t, U u);
JDKでの利用例
[java.base] (https://docs.oracle.com/javase/jp/11/docs/api/java.base/module-summary.html) : [java.util.Map<K,V>] (https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/util/Map.html)
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;
}
サンプル
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) インスタンスのメソッド参照
value = map.computeIfAbsent("durian", String::length);
UnaryOperator<T>
スーパーインターフェースにFunction
を持ち、引数の型と同じ型を返すFunctionを特殊化した関数型インターフェースです。
型パラメータ:
T - 演算子のオペランドと結果の型
JDKでの利用例
[java.base] (https://docs.oracle.com/javase/jp/11/docs/api/java.base/module-summary.html) : [List<E>] (https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/util/List.html)
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()));
}
}
サンプル
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) インスタンスのメソッド参照
list.replaceAll(String::toUpperCase);
UnaryOperator.identity
UnaryOperatorインターフェースにはidentityというstaticメソッドがあります。(Functionインターフェースにもあります。)
static <T> UnaryOperator<T> identity() {
return t -> t;
}
このメソッドの利用例としてよく見るコードでは、下記のようなListをMapに変換するものがあります。
この変換は、mapのキーにItemクラスのidを、mapの値にItemクラスのインスタンスをセットしますが、入力引数を返すidentityメソッドを使うことでコードの意図が明確になります。
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からbooleanへの単項関数)
型パラメータ:
T - 述語の入力の型
boolean test(T t);
BiPredicate
Predicate
を特殊化した2つの引数を受け取るBiPredicate
という関数型インターフェースもあります。
型パラメータ:
T - 述語の第1引数の型
U - 述語の第2引数の型
boolean test(T t, U u);
JDKでの利用例
[java.base] (https://docs.oracle.com/javase/jp/11/docs/api/java.base/module-summary.html) : [java.util.Collection<E>] (https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/util/Collection.html)
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;
}
サンプル
listから偶数を取り除くサンプルです。removeIfはListに対して副作用を持つので不変(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] (https://docs.oracle.com/javase/jp/11/docs/api/java.base/module-summary.html) : [java.util.Optional<T>] (https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/util/Optional.html)
filter
public Optional<T> filter(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
if (!isPresent()) {
return this;
} else {
return predicate.test(value) ? this : empty();
}
}
サンプル
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] (https://docs.oracle.com/javase/jp/11/docs/api/java.base/module-summary.html) : [java.util.regex.Pattern] (https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/util/regex/Pattern.html)
asPredicate
public Predicate<String> asPredicate() {
return s -> matcher(s).find();
}
サンプル
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 (Rへの無限関数)
型パラメータ:
T - このサプライヤから提供される結果の型
T get();
JDKでの利用例
[java.base] (https://docs.oracle.com/javase/jp/11/docs/api/java.base/module-summary.html) : [java.util.Optional<T>] (https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/util/Optional.html)
orElseGet
public T orElseGet(Supplier<? extends T> supplier) {
return value != null ? value : supplier.get();
}
サンプル
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) メソッド参照
result = fruit.orElseGet(SupplierDemo::getDefault);
public static String getDefault() {
return "banana";
}
orElseThrow
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
if (value != null) {
return value;
} else {
throw exceptionSupplier.get();
}
}
サンプル
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) コンストラクタ参照
result = fruit.orElseThrow(FruitNotFoundException::new);
class FruitNotFoundException extends RuntimeException {
public FruitNotFoundException() {
super();
}
}
コンストラクタ参照のサンプル
配列を生成するコンストラクタ参照のサンプル
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]);
上記のコレクションを配列に変換するコードは、コンストラクタ参照で書くと下記のようになります。
List<String> list = List.of("apple", "banana", "cherry", "durian", "elderberry");
String[] fruits = list.toArray(String[]::new);
[java.logging] (https://docs.oracle.com/javase/jp/11/docs/api/java.logging/module-summary.html) : [java.util.logging.Logger] (https://docs.oracle.com/javase/jp/11/docs/api/java.logging/java/util/logging/Logger.html)
log
public void log(Level level, Supplier<String> msgSupplier) {
if (!isLoggable(level)) {
return;
}
LogRecord lr = new LogRecord(level, msgSupplier.get());
doLog(lr);
}
java.util.functionパッケージ以外
バージョン1.8以前からあるインターフェースですが、関数型インターフェースの条件を満たすため、ラムダ式またはメソッド参照の代入先として使用できます。
[FileFilter] (https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/io/FileFilter.html)
boolean accept(File pathname);
JDKでの利用例
[java.base] (https://docs.oracle.com/javase/jp/11/docs/api/java.base/module-summary.html) : [File] (https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/io/File.html)
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()]);
}
サンプル
File directory = new File("D:\\temp");
Long limit = 1024L * 1024L * 7L;
File[] overLimitFiles = directory.listFiles(pathname -> pathname.length() > limit);
[Runnable] (https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/lang/Runnable.html)
void run();
JDKでの利用例
[java.base] (https://docs.oracle.com/javase/jp/11/docs/api/java.base/module-summary.html) : [java.util.Optional<T>] (https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/util/Optional.html)
ifPresentOrElse
public void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction) {
if (value != null) {
action.accept(value);
} else {
emptyAction.run();
}
}
サンプル
Optional<String> opt = Optional.ofNullable(null);
opt.ifPresentOrElse(s -> {
System.out.println(s);
},() -> {
System.out.println("null");
});
[PathMatcher] (https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/nio/file/PathMatcher.html)
boolean matches(Path path);
JDKでの利用例
[java.base] (https://docs.oracle.com/javase/jp/11/docs/api/java.base/module-summary.html) : [java.nio.file.FileSystem] (https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/nio/file/FileSystem.html)
getPathMatcher
public abstract PathMatcher getPathMatcher(String syntaxAndPattern);
サンプル
記事の主題からそれますが、getPathMatcher
メソッドに渡す値はSyntaxとPatternをコロン(:)で区切った文字列になります。
Syntaxに"glob"を指定した場合はPatternに正規表現に似たより単純なパターンを指定でき、また"regex"を指定した場合は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>] (https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/util/Comparator.html)
このインターフェースにはcompare
の他にboolean equals(Object obj)
という抽象メソッドが宣言されていますが、以下に引用したとおり
インタフェースが、java.lang.Objectのpublicメソッドの1つをオーバーライドする抽象メソッドを宣言する場合も、それはインタフェースの抽象メソッド数に反映されません。理由は、そのインタフェースのいずれかの実装にjava.lang.Objectまたは他の場所からの実装が含まれるからです。
関数型インターフェースとしての条件を満たしています。
int compare(T o1, T o2);
JDKでの利用例
[java.base] (https://docs.oracle.com/javase/jp/11/docs/api/java.base/module-summary.html) : [Collections] (https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/util/Collections.html)
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;
}
サンプル
JobクラスのPriority、Idを昇順でソートし最小のJobを返すサンプルです。
なお、下記に引用するようにコレクションの要素数が多い場合はコストがかかります。
このメソッドはコレクション全体で反復処理を行うので、コレクションのサイズに比例した時間が必要です。
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>] (https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/util/concurrent/Callable.html)
V call() throws Exception;
JDKでの利用例
[java.base] (https://docs.oracle.com/javase/jp/11/docs/api/java.base/module-summary.html) : [ExecutorService] (https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/util/concurrent/ExecutorService.html)
submit
<T> Future<T> submit(Callable<T> task);
サンプル
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();