メンバーズキャリア採用広報担当の田中です!
今回は先日メンバーズキャリアで実施した、
WEBエンジニア向けの社内勉強会(有給研修)の様子をお届けします!
当社の技術顧問である久保 雅彦(@jflute)さんに
「匿名クラス&ラムダ式&Optional」をテーマに講義をしていただきました。
今回は、より分かりやすくお届けするために、実際に勉強会に参加したWEBエンジニアの方に
勉強会の内容を自分なりに整理してもらいましたので、そちらをご紹介します!!
研修テーマは「匿名クラス&ラムダ式&Optional」
匿名クラス&ラムダ式
問題:コールバックの中で呼び出し元のローカル変数を書き換えるとコンパイルエラーになるのはなぜ?
package console;
public class Test {
public static void main(String[] args) {
// mainのローカル変数
Boolean mainFlg = false;
// ️匿名クラス
Hoge anonymous = new Hoge() {
@Override
public void print() {
// ここでエラー
mainFlg = true;
}
};
anonymous.print();
}
public interface Hoge {
void print();
}
}
書き換え部分で以下のエラーが出ます
「Local variable mainFlg defined in an enclosing scope must be final or us final」
グーグル翻訳にかけてみると
「囲みスコープで定義されたローカル変数mainFlgはfinalまたはus finalでなければなりません」
上記を理解するために・・・
– 匿名(無名)クラスとは?
– ラムダ式とは?
– コールバックとは?
匿名(無名)クラスとは
一回しか利用しないクラスを定義&インスタンス作成します。
public static void main(String[] args) {
// Hogeインターフェースを実装した匿名クラスのインスタンスを作成
Hoge anonymous = new Hoge() {
@Override
public void print() {
System.out.println("TEST1");
}
};
anonymous.print();
}
public interface Hoge {
void print();
}
出力結果:TEST1
ラムダとは
匿名クラスを簡単に書ける記法 (ひとまずそう捉えて良い)
java
package console;
public class Test {
public static void main(String[] args) {
// ラムダ式
Hoge lambda = () -> System.out.println("TEST1");
lambda.print();
}
public interface Hoge {
void print();
}
}
出力結果:TEST1
上記のように、匿名クラスのコードと同様の結果になります。
そのため、eclipseではクイックフィックス機能を利用することで相互に変換できます。
匿名クラスを理解できていると、ラムダ式がどのような動きをしているか理解しやすいのでセットで覚えたいですね。
コールバックとは
以下のコードの出力はどのようになるでしょうか?
public class Test {
public static void main(String[] args) {
System.out.println("TEST_A");
// ラムダ式
Hoge lambda = () -> System.out.println("TEST_B");
System.out.println("TEST_C");
lambda.print();
}
public interface Hoge {
void print();
}
}
実行すると、A→C→Bの順に出力されます。
これはBの実行がlambda.print();の時点で行われるためです。
以下のコードでも実行結果はA→C→B2となります。
public class Test {
public static void main(String[] args) {
System.out.println("TEST_A");
// ラムダ式
// Hoge lambda = () -> System.out.println("TEST_B");
Hoge hogeImpl = new Hoge();
System.out.println("TEST_C");
// lambda.print();
hogeImpl.print();
}
public interface Hoge {
void print();
}
}
class HogeImpl implements Test.Hoge {
public HogeImpl() {
}
public void print() {
System.out.println("TEST_B2");
}
}
両者の違いは、匿名クラス(ラムダ式)でインスタンスを作成しているか、通常のクラス定義をしているかですが、この違いは”System.out.println(“TEST_B”)を記載する場所の違い”でもあります。
* ラムダ式の場合 = printメソッドの呼び出し元で記載
* 通常のクラス定義の場合 = printメソッドの呼び出し先で記載
前置きが長くなってしまいましたが、コールバックとは呼び出し元で記載した処理(関数オブジェクト)を別の処理内で動作させるための方法のようなものです。
ラムダ式や匿名クラスは、「インターフェースに定義したメソッドの実装を呼び出し元で定義&1回限り利用するクラス定義→インスタンス作成」することができます。
冒頭の問題:コールバックの中で呼び出し元のローカル変数を書き換えるとコンパイルエラーになるのはなぜ?
コールバックとして定義した処理をどのように利用するかは、呼び出し先の処理に委ねられています。複数回利用されたり、1回も利用されないこともあり得ます。
(上記のサンプルでは呼び出し元で、ラムダ式などによって作成したインスタンスから、print()を呼び出していましたが、呼び出さないことも可能)
呼び出し元のローカル変数をコールバック関数に含めた場合、ローカル変数がどのように扱われるかは、呼び出し先に委ねられることになってしまいます。
ローカル変数は、宣言したスコープの中でのみ利用されるべきであり、別の処理内で操作されることは避けるべきです。
そのため、コンパイルエラーとなります。
匿名クラスとラムダ式の動きを理解できていれば、コンパイルエラーになる理由がよく理解できると思います。
Optional(null安全)
データは getした際に、データが取得できることが保証されていないリソースの場合、getの結果が nullであることが想定されます。そのため、取得したデータが nullではないことをチェックする必要があります。
public class Test {
public static void main(String[] args) {
String hoge = getHoge();
//nullだった場合は処理しない
if(hoge == null) {
return;
}
}
static String getHoge() {
return null;
}
}
上記のように取得後にnullチェックをすることを徹底すればエラーは発生しません。が、人間がコーディングする以上ミスはありえます。
そこで、以下のようにOptional型でラップした型(ここではString)を返却するようにします。
import java.util.Optional;
public class Test {
public static void main(String[] args) {
String hoge = getHoge(); //ここでコンパイルエラー
//nullだった場合は処理しない
if(hoge == null) {
return;
}
}
//返却する型をOptionalでラップする
static Optional getHoge() {
return null;
}
}
こうすることで、mainの呼び出し部分で型が合わないためコンパイルエラーになります。
したがって呼び出し側は、getHogeを利用する際に以下のように呼び出すことを強制されます。
public class Test {
public static void main(String[] args) {
Optional hoge = getHoge(); //Optionalで結果を受け取る
//hogeがnullではない場合、コールバック関数を実行するOptional.ifPresentを利用し、アンラップする。
hoge.ifPresent(h -> System.out.println(h));
}
static Optional getHoge() {
//Optional.ofNullable(null);
return Optional.ofNullable("nullではない");
}
}
実行結果:nullではない
Optional.ifPresentはhogeがnullではない場合のみ、引数のコールバック関数を実行するメソッドです。
このように、呼び出し側にnullチェックを強制させることで、実行時にぬるぽでアプリケーションが落ちることを防止できます(コンパイル時にエラーに気づける)。
ちなみに、上記でgetHogeからOptional.ofNullable(null)が返却された場合、実行結果は何もありません。(hoge.ifPresentで引数のコールバック関数が実行されない)
バグをコンパイル時に見つけることができるOptionalは、コードの品質に直結するのでぜひ使いこなしたいですね。
信頼してもらえるエンジニアになるために
講義の予定時間が少し余ったため、エンジニアとして仕事をしていく上で大切な心構えをお話ししていただきました。その中でも特に「質問」についてのお話がとても印象深く、重要な事だと感じたので、
少しご紹介させていただきます。
質問についてのお話の中で私が重要だと感じた箇所は
- 質問する際は、その背景も添えて。
- 質問そのものが問題の本質であることが少ない
- 質問される側は、気付きとなる情報があれば問題の本質から解決できることが多い
です。
詳しくはこちらをご覧下さい!
具体的な質問の仕方は、こちらが参考になるかと思います。
ご登壇いただいた講師
今回ご登壇いただいた講師の久保 雅彦氏についてはこちらの紹介記事もご覧下さい!
株式会社メンバーズキャリアでは一緒に働く仲間を募集しています