こんにちは、Sawa です。
今回のブログは Java の基本を学んだ初学者の方がステップアップするために役立つようなことを書こうかと思います。
テーマは Stream です。 最終的には Stream API を扱えることをゴールにしようと思います。
Stream API は実務でもよく使いますのでさっそく勉強していきましょう。
前提知識
- インタフェース
- オーバーライド
関数型インタフェースとは?
ざっくり言うと、抽象メソッドを一つだけ持っているインタフェースのことです。
コード的にはこんな感じのインタフェースです。
interface FunctionInterface {
int addInt(int a, int b);
}
抽象メソッドを一つだけ持っているということが大事なので、こんな感じのインタフェースも関数型インタフェースに含まれます。
interface FunctionInterface {
int addInt(int a, int b); // 抽象メソッド
default int defaultAdd(int a, int b){
return a * b;
}
private int privateAdd(int a, int b){
return a * b;
}
}
関数型インタフェースとは何かがぼんやりわかったら次に進みましょう。
関数型インタフェースのメソッドを実装してみる
インタフェースなのでメソッドは実装しないとですね。
ただし、ここで注意すべきは関数型インタフェースのメソッドの実装はみなさんの知っているインタフェースのメソッドの実装の仕方とは違うということです。
まずは、普通のインタフェースのように addInt メソッドを実装してみます。
public class InterfaceTest implements FunctionInterface{
public static void main(String[] args){
InterfaceTest test = new InterfaceTest();
System.out.println(test.addInt(1,2));
}
@Override
public int addInt(int a, int b) {
return a + b;
}
}
interface FunctionInterface {
int addInt(int a, int b);
}
おそらく、インタフェースを学んだ後の人は上のようにメソッドを継承してインタフェースを実装していくかと思います。
しかし、関数型インタフェースの使い方は少し異なります。
さっそくコードから見ていきましょう。
public class InterfaceTest{
public static void main(String[] args){
FunctionInterface func = (int a, int b) -> {return a + b;};
System.out.println(func.addInt(1,2));
}
}
interface FunctionInterface {
int addInt(int a, int b);
}
はい、これが関数型インタフェースのメソッド実装です。
なにこれ?が正直な感想かと思いますが、これはまだわかりやすく書かれた方のコードです。
これまでの書き方と違いすぎるので細かく見ていきましょう。
1. implements が記述されていない
まず最初の違和感はここかと思います。
結論から言うと、関数型インタフェースを使用する際は implements は不要です。
なぜ不要かと言うと、インタフェースのメソッドを直接的に実装しているわけではないからです。
通常のインタフェースを実装する際にはメソッドをオーバーライドして直接的に実装してからそのメソッドを使用することで、メソッドを利用可能にしていました。
しかし、関数型インタフェースの場合はメソッドを直接的に実装しているのではなく、あくまで一時的にメソッドの中身を表現しているといった感じになります。
ここまで聞いてもよくわからない方がほとんどかと思いますが、ざっくりしたメリットを言いますと、インスタンスごとにメソッドの挙動を変えることができます。
実際にコードで確認してみましょう。
public class InterfaceTest{
public static void main(String[] args){
FunctionInterface func = (int a, int b) -> {return a + b;};
FunctionInterface func10 = (int a, int b) -> {return (a + b) * 10;};
System.out.println(func.addInt(1,2)); // 3
System.out.println(func10.addInt(1,2)); // 10
}
}
interface FunctionInterface {
int addInt(int a, int b);
}
こんな感じで同じメソッドでも挙動を簡単に変えることが可能です。
これの何がいいの?という疑問もあるかと思いますが、Stream API を使うときにはこの仕様が役立ってきます。
ただ、まだこの段階ではこんな感じのことができるのかくらいの理解度でオッケーです。
2. メソッドの実装方法
1 でさらっと流してしまいましたが、メソッドの中身の書き方もだいぶ気持ち悪いですよね、、
でも、インタフェースの定義と比較するとある程度書いてある内容は理解できるかと思います。
FunctionInterface func = (int a, int b) -> {return a + b;};
(int a, int b) はインタフェースの引数を表しているとすぐに理解できるかと思います。
そして、{return a + b;} はメソッドの中身ですが、これもすぐにわかると思います。
さらに言うと、() 内の変数名はインタフェースの抽象メソッドの引数名に合わせる必要はありません。
そのため、以下のように書いても問題ありません。
FunctionInterface func = (int num1, int num2) -> {return num1 + num2;};
また、関数型インタフェースを学習中だった私はこのような疑問を抱きました。
なぜ、メソッドを指定していないのに、引数や処理の中身を定義できるのだろうか?
ずばり、抽象メソッドが一つしかないからです!
当時の私はこれに気づかずずっと疑問を持っていました。
抽象メソッドを一つしか持たないので、必然的に右辺でしているメソッドの中身の定義は addInt() の中身のを定義していると特定できます。
これが関数型インタフェースの特徴なのです。
あともう一点厄介なのがいました。
こいつですね。 ->
こいつはアロー演算子と言います。
アロー演算子はラムダ式を使う際にには必ずと言っていいほど出てくるので見慣れておきましょう。
アロー演算子は左側の引数と右側の実装の定義部分を区切っているようなものです。
全然怖いものではありません。
単なるおまじないのようなものなのでこうやって -> を使って書くのね。くらいに思っておけばいいでしょう。
ここまでくれば関数型インタフェースを自分で実装することができるのではないでしょうか?
ぜひ自分で手を動かして関数型インタフェースを使ってみましょう。
でも実は、関数型インタフェースは自分で定義してメソッドを実装するよりも、すでに用意された関数型インタフェースの中身を実装する方が場面的には多いです。
次の回では有名な java.util.function 内の関数型インタフェースの使い方を学んでいきます。
【Java】関数型インタフェースを使ってみよう(Stream 理解への道2)