33
50

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

関数型インターフェースのおさらいメモ

Last updated at Posted at 2019-02-06

概要

Java 1.8で導入された関数型インターフェースのおさらいです。

環境

  • Windows 10 Professional
  • OpenJDK 11.0.2

参考

書き方の簡単なおさらい

このコードで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パッケージ

このパッケージには基本的な関数型インターフェースが定義されています。代表的なものではConsumerFunctionPredicateSupplierがあり、これらを特殊化したIntCounsumerIntFunctionなどもあります。
また、このパッケージ外にも特定目的のために実装された関数型インターフェースがあります。

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メソッドがあります。

Iterable.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
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;
}

サンプル

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

サンプル

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

サンプル
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
Optional.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
Pattern.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
Optional.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);
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();
    }
}

サンプル

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);
FruitNotFoundException
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
Logger.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
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()]);
}

サンプル

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
FileSystem.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
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;
}

サンプル

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
ExecutorService.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();
33
50
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
33
50

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?