Javaプログラミング Lambda道場¶
このページは、JJUG CCC 2013 Fall のハンズオンセミナー「R5-3 Project Lambdaハンズオン」(講師:櫻庭祐一氏)を受講した内容の復習です。セミナー資料は上述リンク先にあります。
Lambda式¶
Lambda式は、
(引数) -> {処理}
の形式で表します。引数は、関数型インタフェースの実装すべき(唯一の)メソッドの引数を、処理は、そのメソッドの実体を表します。
匿名クラスをLamda式に書き換え¶
問題2-1 次の匿名クラス生成式をLambda式に書き換えましょう¶
Comparator<Integer> comparator1 = new Comparator<Integer>() {
@Override
public int compare(Integer x, Integer y) {
return x - y;
}
};
解答2-1ーⅰ 省略表現のない素直な?解¶
Comparator<Integer> comparator1 = (Integer x, Integer y) -> {return x - y;};
解答2-1-ⅱ 引数の型を省略¶
引数の型はコンパイラが推論できるので省略することができます。
Comparator<Integer> comparator1 = (x, y) -> {return x - y;};
解答2-1-ⅲ returnキーワードを省略¶
処理がreturn式だけの場合、returnキーワードを省略することができます。
Comparator<Integer> comparator1 = (x, y) -> {x - y;};
解答2-1-ⅳ 処理の波括弧を省略¶
処理が1つだけの文の場合、処理の波括弧およびセミコロンを省略することができます。
Comparator<Integer> comparator1 = (x, y) -> x - y;
問題2-2 次の匿名クラス生成式をLambda式に書き換えましょう¶
Callable<Date> callable1 = new Callable<Date>() {
@Override
public Date call() throws Exception {
return new Date();
}
};
解答2-2-ⅰ returnキーワードおよび波括弧を省略¶
Callable<Date> callable1 = () -> new Date();
解答2-2-ⅱ メソッドリファレンスを使用¶
処理がstaticなメソッドを呼び出す場合、メソッドリファレンスを用いて記述することができます。
Callable<Date> callable1 = () -> Date::new;
コンストラクタはstaticなメソッドなので、このような呼び出しが可能です。
問題2-3 次の匿名クラス生成式をLambda式に書き換えましょう¶
Runnable runnable1 = new Runnable() {
@Override
public void run() {
doSomething();
}
};
解答2-3 returnキーワードおよび波括弧を省略¶
Runnable runnable1 = () -> doSomething();
問題2-4 次の匿名クラス生成式をLambda式に書き換えましょう¶
@FunctionalInterface
public interface Doubler<T extends Number> {
T doDouble(T x);
}
------
Doubler<Double> doubler = new Doubler<Double>() {
@Override
public Double doDouble(Double x) {
return 2.0 * x;
}
};
解答2-4-ⅰ 省略表現のない素直な解¶
Doubler doubler = (Double x) -> {return 2.0 * x;};
解答2-4-ⅱ 引数の型を省略¶
Doubler doubler = (x) -> {return 2.0 * x;};
解答2-4-ⅲ returnキーワードを省略¶
Doubler doubler = (x) -> {2.0 * x;};
解答2-4-ⅳ 波括弧およびセミコロンを省略¶
Doubler doubler = (x) -> 2.0 * x;
解答2-4-ⅴ 丸括弧を省略¶
Doubler doubler = x -> 2.0 * x;
問題2-5 Swingプログラムで使われる匿名クラス生成式をLambda式に書き換えましょう¶
public class LambdaForSwing {
private int count;
public LambdaForSwing() {
JFrame frame = new JFrame("Swing Lambda");
JButton button = new JButton("Count");
frame.add(button, BorderLayout.NORTH);
final JLabel counter = new JLabel(String.valueOf(count));
frame.add(counter, BorderLayout.CENTER);
button.addActionListener(new ActionListener() { // (1) ActionListener匿名クラス生成式
@Override
public void actionPerformed(ActionEvent e) {
count++;
counter.setText(String.valueOf(count));
}
});
frame.pack();
frame.setVisible(true);
}
public static void main(String... args) {
SwingUtilities.invokeLater(new Runnable() { // (2) Runnable匿名クラス生成式
@Override
public void run() {
new SwingLambda();
}
});
}
}
解答2-5(1) ¶
button.addActionListener(e -> {count++; counter.setText(String.valueOf(count));};
- 注記1 countはフィールドなのでLambda式内でアクセス可能
- 注記2 counterはfinalなローカル変数なのでLambda式内でアクセス可能
解答2-5(2)¶
SwingUtilities.invokeLater(SwingLambda::new);
- 注記1 関数型インタフェースRunnableの実装すべき唯一のメソッドrunの引数は0個なので本来省略できませんが、処理がメソッドリファレンスの場合は省略できます。
- 注記2 インスタンスメソッドも、変数名::メソッド名として記述できます。
for文の変換(Iterable)¶
問題3-1 拡張for文をforEachメソッドに書き換えましょう¶
List<String> strings = Arrays.asList("a", "b", "c", "d", "e");
StringBuilder builder = new StringBuilder();
for (String s: strings) {
builder.append(s);
}
System.out.println(builder.toString());
解答3-1-ⅰ 引数の丸括弧、処理の波括弧、セミコロンを省略¶
strings.forEach(s -> builder.append(s));
解答3-1-ⅱ メソッドリファレンス¶
strings.forEach(builder::append);
- 注記1 インスタンス変数のメソッドリファレンスを指定しています。
- 注記2 appendは引数1個(String)で一意に確定します。
拡張for文に対して性能はほとんど変わりません。invokeDynamicのブートストラップでクラスを生成しロードし、2回目以降はそれを使います。
問題3-2 拡張for文をforEachメソッドに書き換えましょう¶
List<Integer> numbers = Arrays.asList(10, 5, 2, 20, 12, 15);
int sum = 0;
for (Integer number: numbers) {
sum += number;
}
System.out.println(sum);
解答3-2 引数の丸括弧、処理の波括弧、セミコロンを省略¶
int sum; // フィールドに格上げ
...
numbers.forEach(number -> sum += number);
問題3-3 for文をforEachメソッドに書き換えましょう¶
for (int i = 0; i < 10; i++) {
System.out.print(i);
}
System.out.println();
解答3-3 IntStreamを導入¶
IntStream.range(0, 10).forEach(i -> System.out.print(i));
- IntStreamのrange(0, 10)は、0以上10未満の整数ストリームを生成します。
上限の値を含める場合は、rangeClosedを使用します。
for文の変換(Stream)¶
問題4-1 拡張for文をStreamに書き換えましょう¶
List<Integer> numbers = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
for (Integer x: numbers) {
if (x % 2 == 0) {
System.out.print(x);
}
}
System.out.println();
解答4-1-ⅰ 単純なforEachへの書き換え¶
numbers.stream().forEach(x -> {
if (x % 2 == 0) {
System.out.print(x);
}
});
解答4-1-ⅱ フィルタの導入¶
numbers.stream().filter(x -> x % 2 == 0).forEach(x -> System.out.print(x));
- 2回ループが回るように見えます。filterで一回条件に合致するものを調べるループが回り、forEachでループが回るように見えます。しかし、実際にはループは1回しか回りません。それはforEachの中で動きます。
解答4-1-ⅲ フィルタとメソッドリファレンスの導入¶
numbers.stream()
.filter(x -> x % 2 == 0)
.forEach(System.out::print);
- Stream APIを使うとメソッドチェーンが増えるので適宜折り返しを入れたくなりますが、その場合、メソッド呼び出しの.演算子の前で改行するのが習慣のようです。
解答4-1-ⅳ rangeの導入¶
IntStream.range(0, 11)
.filter(x -> x % 2 == 0)
.forEach(System.out::print);
問題4-2 拡張for文をStreamに書き換えましょう¶
Random random = new Random();
List<Double> numbers = new ArrayList<>();
for (int i = 0; i < 100; i++) {
numbers.add(random.nextDouble());
}
double ave = 0.0;
for (Double x: numbers) {
ave += x;
}
ave /= numbers.size();
double div = 0.0;
for (Double x: numbers) {
div += (x - ave) * (x - ave);
}
div /= numbers.size();
解答4-2-ⅰ¶
int ave = numbers.stream()
.reduce(0, (x, y) -> x + y) / numbers.size();
- reduceの第1引数は初期値
- reduceの第2引数のLamda式の引数部(x, y)において
- xは初回は初期値が、2回目以降は前回の結果が入る
- yはnumbersの要素が入る
この解は、ボクシングが多発しパフォーマンスが劣ります。
解答4-2-ⅱ¶
int ave = numbers.stream()
.mapToInt(x -> x)
.reduce(0, (x, y) -> x + y) / numbers.size();
- 解答4-2-ⅰのボクシング多発を改善した解
- mapToIntは、Integer型からint型へマッピングする
解答4-2-ⅲ¶
int ave = numbers.stream()
.mapToInt(x -> x)
.sum() / numbers.size();
- 汎用な処理をするreduceではなく合計を算出するsumを使用
解答4-2ーⅳ¶
int ave = numbers.stream()
.mapToInt(x -> x)
.average().getAsDouble();
- 平均を取るメソッドを使用、ただしaverage()はOptionalDouble型を返すのでgetAsDoubleでプリミティブのdoubleを取得
問題4-3 for文をStreamに書き換えましょう¶
try(BufferedReader reader = new BufferedReader(new FileReader(filename))) {
int wordCount = 0;
for (;;) {
String line = reader.readLine();
if (line == null) {
break;
}
String[] words = line.split(" ");
wordCount += words.length;
}
System.out.println(wordCount);
} catch (IOException ex) {
...
}
解答5-3-ⅰ ¶
int wordCount = reader.lines()
.flatMap(l -> Arrays.stream(l.split(" ")))
.count();
配列の場合、flatMapを使います。
オプション問題¶
for (int i = 0; i < 100; i++) {
numbers.add(random.nextDouble());
}
オプション解答ーⅰ¶
IntStream.range(0, 100)
.mapToObj(x -> random.nextDouble())
.collect(Collectors.toList());
オプション解答ーⅱ¶
IntStream.range(0, 100)
.mapToDouble(x -> random.nextDouble())
.boxed()
.collect(Collectors.toList());
オプション解答ーⅲ¶
DoubleStream.generate(random::nextDouble)
.limit(100)
.boxed()
.collect(Collectors.toList());