Java8まとめてみた
結構主観強いのでアレルギーある方はブラウザバック推奨。
Java8というか、ラムダやFunctionなどをまとめました。
とりあえず読めば使い方分る という基準を目指してます。
実際にエディタで書きながら読んでいただければ幸いです。
1. Functional Interface
package java8;
import java.util.function.Function;
public class Java8 {
// 呼び方
public void run() {
// メソッド
sample("引数");
// Function
sample.apply("引数");
}
// いつもの
public String sample(String str) {
String result = "result";
return result;
}
// Function
public Function<String, String> sample = str -> {
String result = "result";
return result;
};
}
とりあえずこうです。
Functionは、メソッドの別の書き方と思うところから入りましょう。
public String メソッド名(String 変数名) {
public Function<String, String> メソッド名 = 変数名 -> {
メソッドは、1.戻り値の型と、2. 引数の型、3. 引数の変数名を設定しますよね。
Functionは、1. 引数の型、2. 戻り値の型、3. 引数の変数名を設定します。
順番が変わっただけで、設定内容は変わっていません。
おそらく初見で一番困惑するであろうところが
// Function
public Function<String, String> sample = str -> {
String result = "result";
return result;
};
// つまり
str -> {処理};
これですよね
正確には
() -> {};
これで、引数 -> 処理 となります。
これがラムダらなではの書き方ですね。
例えば引数が2個で、文字列「0」を返却したいなら
(a, b) -> {return "0"};
引数が0個で処理が複数行の場合は
() -> {
処理
処理
};
といった具合になります。
まずはこれに慣れることが大切です。個人的には視覚的にわかりやすくてとても好きです。
ところで、Function<String, String>
の型の書き方だと
引数1つと戻り値、という設定しかできません。
引数を増やしたい場合、Function<String, String, String>
と書きたいですよね。
しかし、Functionではこの書き方はできません。
ここで一つ誤解を解いておくと、
「Functional Interface」と「Function」は別のことを意味しています。
正確には、「Functional Interface」という種類のやつらのうちの一つが「Function」です。
すなわち、「Function」に似ている変化形が、他にいっぱいあります。
引数一つの時しか使えないとか不便すぎますからねw
以下がよく使うinterfaceの一覧になります。全量はこちらを参照ください。
引数 | 戻り値 | インターフェース名 | 備考 |
---|---|---|---|
なし | なし | Runnable | Runnable xxx = () -> xxx; あんま使わんかも |
1つ | なし | Consumer | 消費する、の意味 |
なし | 1つ | Supplier | 供給する、の意味 |
1つ | 1つ | Function | 上記でも使ったやつ。代表的 |
2つ | 1つ | BiFunction | 2個になるとBiってつく |
2つ | なし | BiConsumer | 2個になるとBiってつく |
1つ | boolean | Predicate | Functionで代用できるが、Predicateのが処理が速い |
で、じゃあ「なんで存在してるの?」「メソッドでよくね?」という疑問が浮かぶわけです。
実際、メソッドを使うときは、戻り値 = メソッド名(引数)
で気軽に使えますが、
Functionインターフェースを使うときは、戻り値 = Function名.apply(引数)
や
戻り値 = Predicate名.test(引数)
というように、1回、インターフェース標準実装のメソッドで発火することで
初めて実装した処理が呼ばれるわけです。
つまり2度手間なんです。処理もメソッドで呼ぶ方が、インターフェース+起爆メソッドより早いです。
なんなら実装するときも、いちいち引数の個数確認しなきゃいけない+引数増えたらインターフェースの種類も変わる
(FunctionからBiFunctionに変える)など、結構面倒なことが多いです。
それは、今までメソッドの役割だったものをFunction達にやらせているからで
Function達の輝く場所は、実は他にあります。
それは、メソッドの引数に入れるという点です。
メソッドの引数にメソッドって入れられないですよね。
メソッドの引数に、Functionを入れられるんです。
package java8;
import java.util.function.Function;
public class Java8 {
public Function<String, String> sample = str -> {
return "result";
};
public String test(String str, Function<String, String> sample) {
String result = sample.apply(str);
return result;
}
public void run() {
test("aaa", sample);
}
}
Functionという、メソッドの型、なので変数となれるわけですね。
設定の仕方も型 名前 = 内容
になっています。
メソッド内部で、他で用意した処理を起爆したい!というときに使えます。
普段使いでもたまにありますし、後述のstreamやOptionalでふんだんに使われます。
また、ジェネリクスを使うことで、独自のインターフェースを作成できます。
@FunctionalInterface
private interface これ<T, S, U> {
void ignite(T e1, T e2, S e3, U e4);
}
FunctionalInterfaceである主張としてアノテーションをつけましょう。お守りです。
上記で作った「これ」というものを使う場合は
@FunctionalInterface
private interface これ<T, S, U> {
void ignite(T e1, T e2, S e3, U e4);
}
これ<String, Integer, Boolean> kore = (a, b, c, d) -> {
// 処理
// aとbがString
// cがInteger
// dがBoolean
};
public void run() {
kore.ignite("a", "b", 3, true);
}
となります。わざと同じ型の引数を2つにしてみました。
Functionにとってのapplyが
これ にとってのigniteになっているわけですね
また、igniteにあたるメソッドからExceptionを出すようにも設定できます。
これにより、Functionalinterfaceでエラーハンドリングをすることもできます。
参照されたし:ラムダでエラーハンドリング
2. メソッド参照
つづいてはメソッド参照という記述方法です。
Javaの中でも高速処理で有名なので、使える場面では率先して使いたいところ。
メソッドの呼び出し方がちょっと変わります。
private String test(String str) {
return str + "test";
}
// メソッド参照
Function<String, String> a = this::test;
// 普通にメソッド呼ぶ
Function<String, String> b = str -> {
return test(str);
};
// やってることはこれと同じ
Function<String, String> c = str -> {
return str + "test";
};
上記3つの呼び出し方は同じものです。
処理内容 = メソッド参照
です。
戻り値を受け取るものではありません。
引数や戻り値はいったん無視して、「しくみはこれ!」って感じですね
主にラムダの中、Functionalの実装やstream中、Optional中に使うので
使いながら慣れるのが一番です。
たぶん使ってれば理解できてくるので、とりあえず存在と意味だけ覚えておくといいですね。
また
package java8;
import java.util.function.Function;
public class Java8 {
private static String test(String str) {
return str + "test";
}
Function<String, String> a = Java8::test;
}
最初の例はthis::メソッド名
でしたが
staticならクラス名::メソッド名
で呼べます。ここは普段のメソッドと同じですね。
3. stream
おそらく一番Java8っぽいのってstreamじゃないでしょうか。
streamの前に、ラムダの書き方としてforEach
があります。
package java8;
import java.util.LinkedList;
import java.util.List;
public class Java8 {
public static void main(String[] args) {
List<String> list = new LinkedList<>();
list.add("a");
list.add("a");
list.add("a");
list.add("a");
list.add("a");
// いつもの拡張for
for (String s : list) {
// 処理
}
// forEach
list.forEach(v -> {
// 処理
});
// forEach (処理が1行)
list.forEach(v -> {});
// メソッド参照
list.forEach(Java8::test);
}
public static void test(String str) {
System.out.println(str);
}
}
基本的には拡張forが処理速度も速く、一般的な書き方ですが
forEachは記述量も少なく、個人的には視覚的にわかりやすいですね。
また、forEachメソッド自体は戻り値を外に出しません。ただのループさせるだけの処理ですからね。
なので、ループ内部でやる処理をforEach(ここ)
に書くわけですが
処理を書くなら最速のメソッド参照が使えるわけで、相性がいいですね。
streamは基本的に、上記のforEachにいろんな機能を盛り込んだ感じで使います。
streamは、中間操作と終端操作の2つに分かれています。何度か中間操作を挟んで、最後に終端して終わりです。
まずはこちら
List<String> list = new LinkedList<>();
list.add("a");
list.add("a");
list.add("a");
// 1. streamにする
list.stream();
// 2. 中間操作
list.stream().filter(v -> "a".equals(v));
// 3. 終端操作
list.stream().filter(v -> "a".equals(v)).forEach(v -> System.out.println(v));
list.stream()
で、List<String>型
からStream<String>型
に切り替わります。
高速道路に乗ったと思ってください。
.filter()
で、フィルタにかけられます。
今回なら、「a」と同じ文字だけ通れます。
高速のETCのとこではじかれたと思ってください。
.forEach()
でトドメです。
通過してきた車だけがこの処理を実行できます。
書き換えれば、for文の中にif文がある状態ですね。
ここで特筆すべきなのが
Predicate<String> etc = str -> "a".equals(str);
Consumer<String> echo = System.out::println;
list.stream().filter(etc).forEach(echo);
streamのfilterは、引数にPredicate型を取ります。
if文の条件の部分ですね。
なのであらかじめPredicateを用意していれば、それをあてがうだけ。
また、forEachは引数一つに戻り値なしなので、Consumer型
になります。
streamの最も基礎的な使い方がコレだと思うので、まずはこれがどういう仕組みで書けるのか
十全に使いこなせるようになりましょう。
そして、もちろん中間操作、終端操作にはバリエーションがあります。
以下を参照
中間操作
終端操作
※大変見やすくわかりやすい記事をありがとうございます
よくつかうのは中間操作で「filter」と「map」、デバッグ用に「peek」
終端操作で「forEach」「collect」「count」「anyMatch」などを使います
機能知っておけば、使う場面で調べればいいだけなので
何ができるかだけ覚えましょう。使ってるうちに覚えます。
4. Optional
最後にOptionalについて書きます。
Optionalもstream同様に、中間操作と終端操作があります。
まずは呼び方から。
なにかしらの値を包んでくれる枠としてOptional型があります。
String a = null;
// nullなら空っぽになる
Optional<String> opt1 = Optional.ofNullable(a);
// nullだったらぬるぽだよ
Optional<String> opt2 = Optional.of(a);
// 空っぽ
Optional<String> opt3 = Optional.empty();
ofNullableだと、nullでもエラーが出ずに操作を続けられますので
nullの可能性があるものの処理によく使われます。ぬるぽ対策全一。
String a = null;
Optional.ofNullable(a);
Optional.ofNullable(a).map(v -> v + "a");
Optional.ofNullable(a).map(v -> v + "a").get();
Optional.ofNullable(a).map(v -> v + "a").orElse("nullでした");
Optional.ofNullable(a).map(v -> v + "a").isPresent();
Optional.ofNullable(a).map(v -> v + "a").ifPresent(v -> System.out.println("nullじゃない値がありました"));
だんだん進化していく感じでまとめました。
.map()
は引数にFunctionを取ります。今回だとFunction<String, String>
ですね
入ってきた値に「a」を加える、という操作です。
このmapはstreamでも使える便利なメソッドです。
そして終端操作がOptionalの真骨頂。
.get()
で、Optionalでくるんでいたものの中身を取り出します。
この際中身がnullだと、NoSuchElementException
になるので注意が必要。
.orElse()
で、中身があればget、中身がなければ指定した値を返す、とできます。
今回なら中身はnullなので、"nullでした"という文字列が取得できます。
.isPresent()
で、中身があるかどうかのbooleanが取得できます。
今回はnullなので、falseになりますね。
ifPresent()
で、もし中身があれば処理を行う、とできます。
引数一つの戻り値なし、Consumer
が書かれます。
もちろん処理をFunctionalInterfaceであらかじめ実装してから呼ぶこともできますし、 メソッドで実装してメソッド参照でも呼べますし なんなら普通に`v -> method(v)`みたく呼ぶこともできます。
今回紹介したすべてが複合的に使えるので
とりあえず全部使えるようになったのちに
見やすさ、処理性能の良さを意識しながら使い分けられれば良いですね。
なお、IntStreamやparalellStreamなどもありますので
気になった方はいろいろ調べてみてください。
なんならjava標準なので、stream()の実装をのぞいてみたり
javadocを読んでみたり
おもちゃはその辺に転がっていますので
存分にお楽しみください。