当サイトは、アフィリエイト広告を利用しています

【java8】関数型インターフェースを使ってみる

作成日:2022月03月02日
更新日:2023年12月01日

javaで関数側インターフェースを使ってみる。
javascriptでいうところの高階関数のように変数の中に関数を入れることができる。

関数型インターフェースとは?

javaで関数型インターフェースというインターフェースがあり、その型の変数に
関数を入れて扱うことができる。
関数型インターフェースにはいくつか種類があり、インターフェースごとに設定できる
関数の形(引数の数や戻り値)などが決まっている。
独自の関数を使いたい場合は関数型インターフェースを継承したクラスを作成する必要がある。

Functionインターフェース

関数型インターフェースのうち、Functionインターフェース型は 引数と戻り値がある関数を入れることができる。

java:Functionインターフェース
Function <Integer,String> make1 = a -> a + "です";
  • Integetは引数の型
  • Stringは戻り値の型

つまりmake1は引数がInteger型、戻り値がString型の関数を格納することができる。
実行する場合は下記のようになる

java:Functionインターフェース
String b = make1.apply(2);

applyメソッドでmake1内の関数を実行している。

サンプル

関数型インターフェースを使ったプログラムを書いてみる
ラムダ式も使用して!

java
import java.util.*;
import java.util.function.*;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) throws Exception {
//Personクラス
class Person {
private String name;
public Person(String name){
this.name = name;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
//名前のリストを作成
List<String>nameList = Arrays.asList("aa","bb","cc");
//Functionインターフェースを使って
//名前リスト分のPersonオブジェクトを作成し、Listに格納する関数を変数:genPersonに設定する
// ラムダ式のmapを使う
Function<List<String>,List<Person>> genPerson = (list)->list.stream().map(
(name)-> new Person(name)
).collect(Collectors.toList());
//変数に格納された関数をapplyメソッドで実行する
List<Person>resultList2 = genPerson.apply(nameList);
//BiFunctionインターフェースを使って
//PresonListの中に指定した名前が何個あるか調べる関数を変数:searchNameCountに設定する
//BiFunction<第一引数の型,第二引数の型,戻り値の型>
BiFunction<String,List<Person>,Integer> searchNameCount = (searchName,list2) -> {
//ラムダ式のfilterを使う
return list2.stream()
.filter( (per)->searchName.equals(per.getName())
).collect(Collectors.toList()).size();
};
//変数:searchNameCount内の変数をapplyメソッドで実行する。
System.out.println(searchNameCount.apply("addda",resultList2));
//関数の引数に関数を設定することもできる
//第三引数に戻り値がList<Person>である関数の入ったgenPersonを設定している。
System.out.println(searchNameCount.apply("bb",genPerson.apply(nameList)));
}
}

ラムダ式部分の別の書き方

蛇足だが上記のサンプルのラムダ部分の別の書き方でかくこともできる
※上記は省略したラムダ式で書いている。

java
import java.util.*;
import java.util.function.*;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) throws Exception {
class Person {
private String name;
public Person(String name){
this.name = name;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
List<String>nameList = Arrays.asList("aa","bb","cc");
// 省略なし
Function<List<String>,List<Person>> genPerson = (list)->{
List<Person>resultList = list.stream().map((name)->{
return new Person(name);
}).collect(Collectors.toList());
return resultList;
};
List<Person>resultList2 = genPerson.apply(nameList);
BiFunction<String,List<Person>,Integer> searchNameCount = (searchName,list2) -> {
//省略なし
List<Person>resList = list2.stream().filter( (per)->{
if (searchName.equals(per.getName())){
return true;
}else {
return false;
}
}).collect(Collectors.toList());
return resList.size();
};
//普通に呼ぶ
System.out.println(searchNameCount.apply("addda",resultList2));
//関数の中で関数を呼ぶこともできる
System.out.println(searchNameCount.apply("bb",genPerson.apply(nameList)));
}
}

またmapの部分をforEachで書くこともできる

java
import java.util.*;
import java.util.function.*;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) throws Exception {
class Person {
private String name;
public Person(String name){
this.name = name;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
List<String>nameList = Arrays.asList("aa","bb","cc");
// forEachを使う場合
Function<List<String>,List<Person>> genPerson = (list)->{
List<Person>resultList = new ArrayList<>();
list.stream().forEach((name)->{
// Person person = null;
Person person = new Person(name);
resultList.add(person);
});
return resultList;
};
List<Person>resultList2 = genPerson.apply(nameList);
// Your code here!
BiFunction<String,List<Person>,Integer> searchNameCount = (searchName,list2) -> {
//省略なし
List<Person>resList = list2.stream().filter( (per)->{
if (searchName.equals(per.getName())){
return true;
}else {
return false;
}
}).collect(Collectors.toList());
return resList.size();
};
//普通に呼ぶ
System.out.println(searchNameCount.apply("addda",resultList2));
//関数の中で関数を呼ぶこともできる
System.out.println(searchNameCount.apply("bb",genPerson.apply(nameList)));
}
}

参考

Java 関数型インターフェースのサンプル(Function)  

ラムダ式で3つ以上の引数に対応しよう  

関数型インターフェースでラムダ式を扱う

既に書いたが関数型インターフェース内でラムダ式を書く場合に
書き方にいくかパターンがあるのでまとめておく。

Predicateインターフェース

Predicateインターフェースを使ってListの中から
奇数だけを探す関数を使ってサンプルを書く

通常パターン

基本のパターン。
変数に関数をいれて、testメソッドで実行している

java:通常パターン
import java.util.*;
import java.util.function.*;
import java.util.function.*;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) throws Exception {
// 奇数を判定するPredicate
var numbers = List.of(18, 4, 22, 7, 31, 1, 12, 25, 36, 3);
// 奇数を判定するPredicate
//Predicateは一つの引数をとり、boolean型の戻り値を返すインターフェース
//Integerは引数の型
Predicate<Integer> isOdd = x -> x % 2 != 0;
//Predicateではtestメソッドで変数の中の関数を実行する
numbers.stream()
.filter(x -> isOdd.test(x))
.forEach(x->System.out.print(x));
}
}

省略パターン

通常、Functionインターフェース型であればapplyメソッド
Predicateインターフェース型であればtestメソッドで変数内の
関数を実行するがラムダ式の中で使う時はそれを省略できる場合がある。

java:省略パターン
import java.util.*;
import java.util.function.*;
import java.util.function.*;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) throws Exception {
// 奇数を判定するPredicate
var numbers = List.of(18, 4, 22, 7, 31, 1, 12, 25, 36, 3);
// 奇数を判定するPredicate
//Predicateは一つの引数をとり、boolean型の戻り値を返すインターフェース
//Integerは引数の型
Predicate<Integer> isOdd = x -> x % 2 != 0;
//ラムダの中で直接、isOddを呼ぶ
//testメソッドなしでも実行可能。
numbers.stream()
.filter(isOdd)
.forEach(x->System.out.print(x));
}

省略できる条件

変数に格納されている関数がラムダ式内で展開しても
問題なく実行できる場合はapplyメソッドやtestメソッドなしでも
実行できる。

java
// 奇数を判定するPredicate
var numbers = List.of(18, 4, 22, 7, 31, 1, 12, 25, 36, 3);
// 奇数を判定するPredicate
Predicate<Integer> isOdd = x -> x % 2 != 0;
//isOddに格納されてる関数をそのままfilter内で展開しても実行できる
numbers.stream()
.filter(x -> x % 2 != 0)
.forEach(x->System.out.print(x));
//上記のような場合は省略して書くことができる
numbers.stream()
.filter(isOdd)
.forEach(x->System.out.print(x));

参考

今さら聞けないJavaによる関数型プログラミング入門 ~ラムダ式、ストリーム、関数型インターフェース~  

新着記事

タグ別一覧
top