LoginSignup

This article is a Private article. Only a writer and users who know the URL can access it.
Please change open range to public in publish setting if you want to share this article with other users.

More than 1 year has passed since last update.

Java8についてまとめてみた

Last updated at Posted at 2020-09-03

Java8まとめてみた

結構主観強いのでアレルギーある方はブラウザバック推奨。

Java8というか、ラムダやFunctionなどをまとめました。
とりあえず読めば使い方分る という基準を目指してます。
実際にエディタで書きながら読んでいただければ幸いです。

1. Functional Interface

Java8.java
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は、メソッドの別の書き方と思うところから入りましょう。

定義の仕方を見比べる.java
	public String メソッド名(String 変数名) {
	public Function<String, String> メソッド名 = 変数名 -> {

メソッドは、1.戻り値の型と、2. 引数の型3. 引数の変数名を設定しますよね。
Functionは、1. 引数の型2. 戻り値の型3. 引数の変数名を設定します。
順番が変わっただけで、設定内容は変わっていません。

おそらく初見で一番困惑するであろうところが

lambda.java
	// Function
	public Function<String, String> sample = str -> {
		String result = "result";
		return result;
	};
	
	// つまり
	str -> {処理};

これですよね

正確には

lambda.java
() -> {};

これで、引数 -> 処理 となります。
これがラムダらなではの書き方ですね。
例えば引数が2個で、文字列「0」を返却したいなら

lambda.java
(a, b) -> {return "0"};

引数が0個で処理が複数行の場合は

lambda.java
() -> {
    処理
    処理
};

といった具合になります。
まずはこれに慣れることが大切です。個人的には視覚的にわかりやすくてとても好きです。

ところで、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を入れられるんです。

FunctionInmethod.java
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.java
	@FunctionalInterface
	private interface これ<T, S, U> {
		void ignite(T e1, T e2, S e3, U e4);
	}

FunctionalInterfaceである主張としてアノテーションをつけましょう。お守りです。
上記で作った「これ」というものを使う場合は

how2use.java
	@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の中でも高速処理で有名なので、使える場面では率先して使いたいところ。
メソッドの呼び出し方がちょっと変わります。

とりあえずこう.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中に使うので
使いながら慣れるのが一番です。
たぶん使ってれば理解できてくるので、とりあえず存在と意味だけ覚えておくといいですね。

また

こうとも書く.java
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があります。

forEach.java
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つに分かれています。何度か中間操作を挟んで、最後に終端して終わりです。
まずはこちら

とりあえずこう.java
		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文がある状態ですね。

ここで特筆すべきなのが

こうかける.java
		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型があります。

Optionalの呼び方.java
		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の可能性があるものの処理によく使われます。ぬるぽ対策全一。

とりあえずこう.java
		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を読んでみたり
おもちゃはその辺に転がっていますので

存分にお楽しみください。

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