21
28

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Java SE 8 実践プログラミングの練習問題で勉強

Last updated at Posted at 2015-08-21

前提

Java SE 8 に焦点をあてた技術書としてJavaプログラマーなら習得しておきたい Java SE 8 実践プログラミングという本があるのだが、そこに掲載されている練習問題がなかなか勉強になりそうなので読書しながら解いてみることにした。

尚、練習問題はここには全文は引用しない。興味を持たれた方はなかなかの良書なので購入して確認してみて欲しい。

第 1 章 ラムダ式とは

1. Lambda 式のスレッド

考察

現在のスレッド名は Thread.currentThread().getName() で取れるので、対象箇所がメインスレッドかどうかは例えば以下のように確認できる:

public final class Java {

    /**
     * メイン関数.
     *
     * @param args 引数
     */
    public static void main(final String... args) {
        printThreadName();
        new Thread(Java::printThreadName).start();
    }

    private static void printThreadName() {
        System.out.println(Thread.currentThread().getName());
    }
}
main
Thread-0

ここで Arrays.sort に対するコンパレータのコードを無名クラスとラムダ式の双方で実装し実行スレッドを確認する:

import java.util.Arrays;
import java.util.Comparator;

public final class Java {

    /**
     * メイン関数.
     *
     * @param args 引数
     */
    public static void main(final String... args) {
        printThreadName();

        final String[] src = {"this", "is", "a", "pencil"};
        Arrays.sort(src, new Comparator<String>() {
            @Override
            public int compare(final String x, final String y) {
                printThreadName();
                return Integer.compare(x.length(), y.length());
            }
        });
        Arrays.sort(src, (x, y) -> {
            printThreadName();
            return Integer.compare(x.length(), y.length());
        });
    }

    private static void printThreadName() {
        System.out.println(Thread.currentThread().getName());
    }
}
main
main
main
main
main
main
main
main
main

解答

sort メソッドを呼び出したスレッドで実行される。

2. すべてのサブディレクトリ探索

java.io.File クラスの listFiles(FileFilter) メソッドを使えとあるが再帰的には返してくれない。自分で再帰を実装する必要がある。ラムダ式とメソッド参照の双方のバージョンを実装。

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public final class Java {

    /**
     * メイン関数.
     *
     * @param args 引数
     */
    public static void main(final String... args) {
        final File file = new File("aaa");  // aaa ディレクトリの下を探索対象とする
        final List<File> files = getSubDirectories(file);
        files.stream().forEach(f -> System.out.println(f.getPath()));
    }

    /**
     * 指定 File 群のサブディレクトリを返す.
     *
     * @param src 探索対象ディレクトリ
     * @return サブディレクトリ
     */
    private static List<File> getSubDirectories(final File... src) {
        final List<File> dest = new ArrayList<>();
        Arrays.stream(src).forEach(file -> {
            final File[] directories = file.listFiles(File::isDirectory);  // メソッド参照版
            // final File[] directories = file.listFiles(f -> file.isDirectory());  ラムダ式版
            dest.addAll(getSubDirectories(directories));
            dest.addAll(Arrays.asList(directories));
        });
        return dest;
    }
}

3. 指定拡張子のファイル探索

java.io.File クラスの list(FilenameFilter) メソッドを使えとあるが、このメソッドの戻り値が文字列だし FilenameFilter の引数も文字列 (ディレクトリ名とファイル名) なので厳密にはこれだけだと実装できない。何故ならディレクトリ名にもピリオドを使うことができるので、名前だけだと拡張子なのかディレクトリ名の一部なのかが判定できないからだ。1

ともかく今回は厳密な観点は無視して実装。

import java.io.File;
import java.util.Arrays;

public final class Java {

    /**
     * メイン関数.
     *
     * @param args 引数
     */
    public static void main(final String... args) {
        final File directory = new File("aaa");
        final String[] files = getFiles(directory, "txt");
        Arrays.stream(files).forEach(System.out::println);
    }

    /**
     * 指定されたディレクトリ直下の指定された拡張子を持つすべてのファイルを返す.
     *
     * @param directory 指定ディレクトリ
     * @param ext 拡張子
     * @return 対象のファイル名
     */
    private static String[] getFiles(final File directory, final String ext) {
        return directory.list((dir, name) -> name.endsWith("." + ext));
    }
}

すばらしくシンプルでいい。

解答

エンクロージングスコープからキャプチャされる変数は上記コード例だと ext である。

4. ディレクトリとファイルのパス名ソート

ディレクトリの次にファイルが来るようにし、且つそれぞれのグループで昇順ソートを行う。
以下コード実装したが、存在しないディレクトリやファイルを new File() しても例外スローしたりしないので、
その場合 isDirectory()isFile() が機能しないので正しく動作しない。
filter(File::exists) でフィルタリングして存在しないものは削ぎ落としている。

import java.io.File;
import java.util.Arrays;
import java.util.stream.Stream;

public final class Java {

    /**
     * メイン関数.
     *
     * @param args 引数
     */
    public static void main(final String... args) {
        final String[] paths = {"aaa/bbb", "aaa/bbb/bbb.txt", "aaa/ddd", "aaa/aaa.txt", "aaa/bbb/ccc", "dummy"};
        final File[] files = Stream.of(paths).map(File::new).filter(File::exists).toArray(File[]::new);
        Arrays.sort(files, (File x, File y) -> {
            if ((x.isDirectory() && y.isDirectory()) || (x.isFile() && y.isFile())) {
                return x.getPath().compareTo(y.getPath());
            }
            return x.isDirectory() ? -1 : 1;
        });
        Arrays.stream(files).forEach(file -> System.out.println(file.getPath()));
    }
}
aaa/bbb
aaa/bbb/ccc
aaa/ddd
aaa/aaa.txt
aaa/bbb/bbb.txt

5. 自分のプロジェクトでラムダ式使ってみよ

Android アプリでさんざん RetroLambda で活用しているから今更やらなくてもいいだろう。

解答

3, 4 行ほど短くなった。読みやすくなった。メソッド参照を利用することもできた。

6. チェック例外を使わないようにする

public final class Java {

    /**
     * メイン関数.
     *
     * @param args 引数
     */
    public static void main(final String... args) {
        new Thread(uncheck(() -> {
            System.out.println("Zzz");
            Thread.sleep(1000);
        })).start();
    }

    /**
     * 与えられたチェック例外スローする可能性のあるコードを実行時例外に変更する.
     *
     * @param runnable チェック例外スローする可能性のあるコード
     * @return 実行時例外に変更されたコード
     */
    public static Runnable uncheck(RunnableEx runnable) {
        return () -> {
            try {
                runnable.run();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        };
    }

    interface RunnableEx {
        void run() throws Exception;
    }
}

何故 RunnableEx の代わりに Callable を使用できないのか

戻り値が必要になってしまう、で合ってる? return null; を入れないとコンパイルが通らなくなる。
使用できないというわけではないと思うが。

7. 連続実行する Runnable 生成

public final class Java {

    /**
     * メイン関数.
     *
     * @param args 引数
     */
    public static void main(final String... args) {
        andThen(() -> System.out.println("first"), () -> System.out.println("second")).run();
    }

    /**
     * 2 つの Runnable を連続実行する Runnable を返す.
     *
     * @param first 1 番目の処理
     * @param second 2 番目の処理
     * @return 連続実行する Runnable
     */
    private static Runnable andThen(final Runnable first, final Runnable second) {
        return () -> {
            first.run();
            second.run();
        };
    }
}

8. ラムダ式が拡張 for 文の値をキャプチャした場合

これは書かなくてもわかると思うが、実際やってみた。

import java.util.ArrayList;
import java.util.List;

public final class Java {

    /**
     * メイン関数.
     *
     * @param args 引数
     */
    public static void main(final String... args) {
        String[] names = {"Peter", "Paul", "Mary"};
        List<Runnable> runners = new ArrayList<>();
        for (String name : names) {
            runners.add(() -> System.out.println(name));
        }
        runners.stream().forEach(Runnable::run);
    }
}
Peter
Paul
Mary

間違ってもすべて Mary になったりはしない。キャプチャしているのは name であって添字ではない。String は不変オブジェクトなので値が書き換わったりはしない。これを従来の for にするとどうなるか。

import java.util.ArrayList;
import java.util.List;

public final class Java {

    /**
     * メイン関数.
     *
     * @param args 引数
     */
    public static void main(final String... args) {
        String[] names = {"Peter", "Paul", "Mary"};
        List<Runnable> runners = new ArrayList<>();
        for (int i = 0; i < names.length; i++) {
            int j = i;  // 事実上の final であれば final は付けなくても参照可能
            runners.add(() -> System.out.println(names[j]));  // i だと事実上 final でないのでコンパイルできない
        }
        runners.stream().forEach(Runnable::run);
    }
}
Peter
Paul
Mary

大丈夫。j は for ブロック内のローカル変数なので使いまわされない。これがもし i でコンパイルが通ってしまっていたら~~全部 Mary になったことだろう。~~嘘。ループを抜ける前に i++ が走るので実行時に IndexOutOfBoundsException がスローされる。

9. デフォルトメソッド forEachIf() 実装

よくわからんが以下のように実装:

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.function.Consumer;
import java.util.function.Predicate;

public final class Java {

    /**
     * メイン関数.
     *
     * @param args 引数
     */
    public static void main(final String... args) {
        class ArrayList2<T> extends ArrayList<T> implements Collection2<T> {
            // do nothing
        }
        String[] names = {"Peter", "Paul", "Mary"};
        Collection2<String> list = new ArrayList2<>();
        Collections.addAll(list, names);
        list.forEachIf(System.out::println, s -> s.length() == 4);  // Paul Mary
    }

    interface Collection2<T> extends Collection<T> {
        default void forEachIf(Consumer<T> action, Predicate<T> filter) {
            stream().forEach(item -> {
                if (filter.test(item)) {
                    action.accept(item);
                }
            });
        }
    }
}

Stream API で filter() した後に forEach() 若しくは peek() すると Stream の状態が変更されてしまうのでそういった時に使える……でいいのか?

10. Collections の是非

java.util.Collections は Collection に対する便利メソッドを寄せ集めたクラスで確かによく使うが設計上エレガントとは言いがたい。オブジェクト志向でなく命令型のコードになってしまう。

まぁインターフェースのデフォルト実装が出来たので本当は java.util.Collection<T> に入れるのがいいのだろう。多くはデフォルトメソッドになるはず。前方互換性を考慮しなければならないのでこのへんが Java の辛いところだな。

11. 同一シグネチャのメソッドを持つインターフェースを実装した時の挙動

以下調査した。

抽象メソッド同士の場合

実装クラスでは普通に 1 つのメソッドを実装するように求められる。これは Java7 以前と同様。

抽象メソッドとデフォルトメソッドの場合

実装クラスではメソッドを実装するように求められるが default 実装されたメソッドの実装を選択することもできる。

    static class My implements I, J {
        @Override
        public void f() {
            J.super.f();  // こういう書き方をする
        }
    }

    interface I {
        void f();
    }

    interface J {
        default void f() {

        };
    }

抽象メソッドと static メソッドの場合

実装しなくてよい。static メソッドの実装が優先される。

デフォルトメソッド同士の場合

抽象メソッドとデフォルトメソッドの場合と同じ。実装するように求められるが default 実装を選択することもできる。

デフォルトメソッドと static メソッドの場合

実装しなくてよい。双方共存できる。

static メソッド同士の場合

問題ない。インターフェースの static メソッドはインターフェースから呼ぶしかできないので区別される。

スーパークラスとインターフェースで同一シグネチャがあった場合はどうか

抽象、デフォルト、static メソッドいずれも問題ない。

12. なんか概念的な話

ちょっとこのあたりが自信がない。抽象クラスを使った実装が良くないとされていたのは「共通的に使われる処理」や「実行前や実行後にまとめて行う処理」といったものを親クラスに書いて全部それでいく、ということがあって、実装後に「とあるクラスでは例外的にこういうのはしたくない」とか「とあるクラスではこういう処理をしたい」みたいな欲求が出てきた時にそれを親クラスの方に書いてしまって既に共通処理じゃなくなっているといった問題がよくあったからだと思う。つまり親のはずがいつの間にか子を意識した実装を入れてしまうような設計になりがちということ。

インターフェースのデフォルト実装にも同じ危険があるが、それを言ったら上述の抽象クラスもそうなわけで、インターフェースのみ実装を禁止する明確な理由が薄いような気がする。そしてインターフェースの実装ができないせいで Java は同じコードをコピペせざるを得ない、若しくは何とか移譲するようなコードを頑張って書くようなことになりがちだった。

なので「どれだけ安全なのか」と聞かれると正直「実装者の設計に依るので今も昔もそんなに変わらない」と思う。

Collection インターフェースを実装した独自クラスを使っていて、そのコードが stream メソッドを共存できないシグネチャで実装して使用していたらコンパイルが通らない。バイナリ互換性の話はちょっとよくわからない。JAR ファイルからの古いコードは基本的には動作するが例外はある。

第 2 章 ストリーム API の使い方

1. parallelStream() のような並列処理を for 文を用いて行え

書いて見るとかなり面倒だしオーバーヘッドがすごそうなコードになった。

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;

public final class Java {

    /**
     * メイン関数.
     *
     * @param args 引数
     */
    public static void main(final String... args) {
        final String contents;
        try {
            contents = new String(Files.readAllBytes(Paths.get("alice.txt")), StandardCharsets.UTF_8);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        final List<String> words = Arrays.asList(contents.split("[\\P{L}]+"));

        // 通常の for ループのバージョン
        /*
        int count = 0;
        for (String w : words) {
            if (w.length() > 12) {
                System.out.println(w);
                count++;
            }
        }
        System.out.println(count);
        */

        // リストのセグメントごとに並列処理を行うバージョン
        final My my = new My();
        for (String w : words) {
            new Thread(() -> {
                if (w.length() > 12) {
                    my.count++;
                }
                final int done;
                synchronized (My.class) {
                    done = ++my.done;
                }
                if (done == words.size()) {
                    System.out.println(my.count);
                }
            }).start();
        }
    }

    static class My {
        int count = 0;
        int done = 0;
    }
}

みなさんは、単一カウンターを更新するためにスレッドを使用したくはないでしょう。なぜですか。

上記のコードのようになってしまうから。並列処理を行うのはいいが、並列処理が全て終わったタイミングで結果の出力をしなければならない。並列処理が全て終わったタイミングを判定するのはアトミックでなければならない。

2. 必要な回数だけ filter がコールされることの検証

import java.util.stream.Stream;

public final class Java {

    /**
     * メイン関数.
     *
     * @param args 引数
     */
    public static void main(final String... args) {
        Stream.of("aaa", "bbbb", "ccccc", "dddddd", "eeeeeee", "ffffffff", "ggggggggg")
                .filter(s -> {
                    System.out.println(String.format("%s is filtered.", s));
                    return s.length() > 3;
                }).limit(5)
                .forEach(System.out::println);
    }
}
aaa is filtered.
bbbb is filtered.
bbbb
ccccc is filtered.
ccccc
dddddd is filtered.
dddddd
eeeeeee is filtered.
eeeeeee
ffffffff is filtered.
ffffffff

既に 5 件見つかったので ggggggggg がフィルタリングの対象となっていないのが確認できた。

3. parallelStream での速度の検証

英語の素材を用意するのが面倒なので省略で。Qiita 上でやっておられる方がいるのでそちらを参考にする。

4. Stream.of() に配列を渡した場合

Stream.of() の受け付ける引数が総称型引数 T 若しくは総称型可変引数 T... なので int[] 型の変数をそのまま渡すと T = int[] として解釈されてしまう。我々が想定しているのは T... = int[] なのだが。

Scala や Python などの言語であれば引数に展開した状態で渡す構文があるのだが Java にはそんなものはない。
幸い Arrays.stream() が用意されているのでそれを使えば良い。

5. 線形合同生成機を実装せよ

Math.random でなく線形合同生成機とやらを直接実装せよとのこと。よくわからないが式が書いてあるのでその通りに計算する。

import java.util.stream.Stream;

public final class Java {

    /**
     * メイン関数.
     *
     * @param args 引数
     */
    public static void main(final String... args) {
        final long a = 25214903917L;
        final int c = 11;
        final long m = (long) Math.pow(2, 48);
        final Stream<Long> stream = Stream.iterate(System.currentTimeMillis(), x -> ((a * x + c) % m)).skip(1);
        stream.limit(10).forEach(System.out::println);
    }
}
-1411176680831
96560841484024
187724558509987
89180819509874
-38511100393835
217416516840316
75995036052439
-231277494299498
175177363425257
273390213248064

seed は System.currentTimeMillis を使用。skip(1) で seed を無限ストリームから省いている。

6. ストリームを使用して 1 行で実装せよ

String.toCharArray() があるのでそれを使うと一発なのだが、テキストの指示通りに実装する:

import java.util.stream.IntStream;
import java.util.stream.Stream;

public final class Java {

    /**
     * メイン関数.
     *
     * @param args 引数
     */
    public static void main(final String... args) {
        characterStream("Hello").forEach(System.out::println);
    }

    /**
     * 与えられた文字列を 1 文字ずつ char にした Stream に変換する.
     *
     * @param str 対象文字列
     * @return Stream
     */
    public static Stream<Character> characterStream(String str) {
        return IntStream.range(0, str.length()).mapToObj(str::charAt);
    }
}

7. Stream が有限か無限かを判定するのはアリなのか

isFinite(Stream<T> stream) を作成するように求められているがそれはアリかどうか、という問題。
finite とは「有限の」という意味なので「ストリームが有限かどうかを判定したい」のだろう。
正直自信がない。以下考察。

まず Stream<T> には有限か無限かを調べるメソッドはない。
使えそうなのは count() あたりだろうが末端処理なのでストリームが閉じてしまう。
しかも無限ストリームで実行すると永遠に結果が返ってこない。

演算しないと求められない関係上「良くない考え」のような気がする。

8. ストリームの zip() メソッド作成

一旦リストに変換して行うことにした。ストリームのまま作れるのかは分からなかった。同時に Stream.forEach() できるのか?


import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public final class Java {

    /**
     * メイン関数.
     *
     * @param args 引数
     */
    public static void main(final String... args) {
        final Stream<String> first = Stream.of("a", "b", "c");
        final Stream<String> second = Stream.of("A", "B", "C", "D", "E");
        zip(first, second).forEach(System.out::println);
    }

    /**
     * 2 つのストリームの要素を交互に入れたストリームを返す.
     * どちらかが終端に達したらそこで終了とする.
     * 有限ストリーム且つ要素数は Integer.MAX_VALUE - 1 までの範囲とする.
     *
     * @param first ストリーム
     * @param second ストリーム
     * @param <T> ストリームのパラメータ型
     * @return 交互に入れたストリーム
     */
    public static <T> Stream<T> zip(Stream<T> first, Stream<T> second) {
        final List<T> firstList = first.collect(Collectors.toList());
        final List<T> secondList = second.collect(Collectors.toList());
        final int limit = Math.min(firstList.size(), secondList.size());
        final Stream.Builder<T> builder = Stream.builder();
        for (int i = 0; i < limit; i++) {
            builder.add(firstList.get(i));
            builder.add(secondList.get(i));
        }
        return builder.build();
    }
}

9. 3 つの reduce

3 つの形式の reduce() を試せとのことなのでそうした。意味は無い。

import java.util.ArrayList;
import java.util.Collections;
import java.util.stream.Stream;

public final class Java {

    /**
     * メイン関数.
     *
     * @param args 引数
     */
    public static void main(final String... args) {
        final ArrayList<String> list1 = new ArrayList<>();
        Collections.addAll(list1, "a", "b", "c");
        final ArrayList<String> list2 = new ArrayList<>();
        Collections.addAll(list2, "d", "e", "f");
        final ArrayList<String> list3 = new ArrayList<>();
        Collections.addAll(list3, "g", "h", "i");

        reduce1(Stream.of(list1, list2, list3)).forEach(System.out::println);
        reduce2(Stream.of(list1, list2, list3)).forEach(System.out::println);
        reduce3(Stream.of(list1, list2, list3)).forEach(System.out::println);
    }

    public static <T> ArrayList<T> reduce1(Stream<ArrayList<T>> stream) {
        return stream.reduce(new ArrayList<>(), (ts, ts2) -> {
            ts.addAll(ts2);
            return ts;
        });
    }

    public static <T> ArrayList<T> reduce2(Stream<ArrayList<T>> stream) {
        return stream.reduce((ts, ts2) -> {
            ts.addAll(ts2);
            return ts;
        }).get();
    }

    public static <T> ArrayList<T> reduce3(Stream<ArrayList<T>> stream) {
        final ArrayList<T> list = new ArrayList<>();
        return stream.reduce(list, (ts, ts2) -> {
            ts.addAll(ts2);
            return ts;
        }, (ts, ts2) -> {
            ts.addAll(ts2);
            return ts;
        });
    }
}

10. Stream の平均を reduce() で計算せよ

DoubleStream ならメソッド一発なのでこんな独自実装は辛いが、頑張ってみた。

まず平均を取るための個数を順々に増やしていき、前の値からどういうふうにデータを加工すればよいかをみる。
a0, a1, ..., an の数列があり, 前の平均値を S で今回の値を x とすると:

a0: (0 / 1) * S + (1 / 1) * a0
a0, a1: (a0 + a1) / 2 = (1 / 2) * S + (1 / 2) * a1
a0, a1, a2: (a0 + a1 + a2) / 3 = (2 / 3) * S + (1 / 3) * a2
a0, a1, a2, a3: (a0 + a1 + a2 + a3) / 4 = (3 / 4) * S + (1 / 4) * a3
...
a0, ..., an: (a0 + ... + an) / n = ((n - 1) / n) * S + (1 / n) * an

となることに着目する。この一番下の式 (一般項) を満たすようにかけばよい。

import java.util.stream.DoubleStream;
import java.util.stream.Stream;

public final class Java {

    /**
     * メイン関数.
     *
     * @param args 引数
     */
    public static void main(final String... args) {

        // 正規の方法
        System.out.println(DoubleStream.of(1.2f, 2.8f, 3.3f).average().getAsDouble());

        // reduce を使った方法
        My result = Stream.of(1.2f, 2.8f, 3.3f).reduce(new My(), (my, x) -> {
            my.average = (my.index / (double) (my.index + 1)) * my.average + (1 / (double) (my.index + 1)) * x;
            my.index++;
            return my;
        }, (my, my2) -> {
            my.average = (my.index / (double) (my.index + 1)) * my.average + (1 / (double) (my.index + 1)) * my2.average;
            my.index++;
            return my;
        });
        System.out.println(result.average);
    }

    static class My {
        int index = 0;
        double average = 0;
    }
}

単純に合計を計算して count() で割ることができないのはなぜですか。

reduce() が終端処理なので count() が計算出来ない。

11. 単一の ArrayList 云々

ちょっと何言ってるのかわからないので無回答で……。

12. 並列ストリーム操作

AtomicInteger を使えという話。

import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;

public final class Java {

    /**
     * メイン関数.
     *
     * @param args 引数
     */
    public static void main(final String... args) {
        final AtomicInteger[] shortWords = new AtomicInteger[12];
        Arrays.setAll(shortWords, value -> new AtomicInteger(0));
        Stream.of("aaa", "bb", "c", "dddd", "eeeee", "fffffff").parallel().forEach(s -> {
            if (s.length() < 12) {
                shortWords[s.length()].getAndIncrement();
            }
        });
        System.out.println(Arrays.toString(shortWords));
    }
}

13. collect メソッドを使用せよ

  1. の続き。
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public final class Java {

    /**
     * メイン関数.
     *
     * @param args 引数
     */
    public static void main(final String... args) {
        Map<Integer, Long> result = Stream.of("aaa", "bb", "c", "dddd", "eeeee", "fffffff")
                .parallel().filter(s -> s.length() < 12)
                .collect(Collectors.groupingBy(String::length, Collectors.counting()));
        for (final Map.Entry entry : result.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }
    }
}

第 3 章 ラムダ式を使ったプログラミング

1. 条件も評価しないロギング

まぁ以下の様なものが想定されるのだろうということでやってみた:

import java.util.function.BooleanSupplier;
import java.util.function.Supplier;

public final class Java {

    private static boolean IS_PRODUCTION = true;

    /**
     * メイン関数.
     *
     * @param args 引数
     */
    public static void main(final String... args) {
        int i = 10;
        logIf(Level.FINEST, () -> {
            System.out.println("判定された");
            return i == 10;
        }, () -> {
            System.out.println("ログ実行");
            return "ログはかれた";
        });
    }

    enum Level {
        FINEST,

        WARNING,

        CRITICAL;
    }

    public static void logIf(Level level, BooleanSupplier condition, Supplier<String> log) {
        if (IS_PRODUCTION && level == Level.FINEST) {
            return;
        }
        if (!condition.getAsBoolean()) {
            return;
        }
        final String logMessage = log.get();
        System.out.println(logMessage);
    }
}

「判定された」「ログ実行」「ログはかれた」いずれも出力されない。

2. ロックとアンロックをラッピングするメソッド実装

問題の難易度の落差がすごい。勿論これは簡単な方だ。

import java.util.concurrent.locks.ReentrantLock;

public final class Java {

    /**
     * メイン関数.
     *
     * @param args 引数
     */
    public static void main(final String... args) {
        ReentrantLock lock = new ReentrantLock();
        withLock(lock, () -> System.out.println("ロック中の処理"));
    }

    /**
     * 本処理前にロック実行し, 本処理実行後のロック解除を行うメソッド.
     *
     * @param lock ロック
     * @param runnable 本処理
     */
    public static void withLock(ReentrantLock lock, Runnable runnable) {
        lock.lock();
        try {
            runnable.run();
        } finally {
            lock.unlock();
        }
    }
}

3. なぜアサーションはライブラリの機能として提供されなかったのか

Java 1.4 時点ではラムダ式の構文がなく、コードブロックの遅延実行がなかったせいでアサーション対象のコードを書くとそのコードが実行されてしまうため。Java 8 ならばラムダ式があるのでできる。

4. Filter を含む関数型インターフェースが Java API にいくつあるか

数えるのか? ... 面倒なので省略で。

  1. java.io.File オブジェクトになっていれば isDirectory() メソッドがあるから判定可能

21
28
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
21
28

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?