collect() と Collectors

終端操作の一つであるcollect()メソッドを使用した処理。
ストリームの要素をまとめて一つにする際に使用する。
collect()メソッドにはCollectorを引数に持たせるが、
汎用的なCollectorはCollectorsクラスによって提供されている。

以下、Collectorsのメソッドをいろいろ試す。

toList()でStreamのリスト化

Stream<String> stream = Stream.of("A", "B", "C", "D");
List<String> list = stream.collect(Collectors.toList());

joining()で文字列結合

Stream<String> stream = Stream.of("A", "B", "C", "D");
//引数なし(ただの連結)→ABCD
String joined = stream.collect(Collectors.joining());
//引数一つ(区切り文字)→A,B,C,D
String joinedWithComma = stream.collect(Collectors.joining(","));
//引数3つ(区切り文字とプレフィックス・サフィックス)→ [A,B,C,D]
String string = stream.collect(Collectors.joining(",", "[", "]"));

summingInt()で合計値の算出

Stream<String> stream2 = Stream.of("one", "two", "three", "four");
//summingInt()の引数はToIntFunctionのため、文字列をintに変換するラムダ式を渡している
//この例だと文字列長への変換
int sum = stream2.collect(Collectors.summingInt(s -> s.length()));
//15になる

averagingInt()で平均値の算出

Stream<String> stream2 = Stream.of("one", "two", "three", "four");
//summingInt()と同様にマッピングラムダ式を渡す。
//戻り値はdouble型となる
double sum = stream2.collect(Collectors.averagingInt(s -> s.length()));
System.out.println(sum);
//3.75

toSet()でSetへ変換

Stream<String> stream = Stream.of("A", "B", "C", "D", "C", "A");
Set<String> set = stream.collect(Collectors.toSet());
System.out.println(set);
//setに変換しているので重複を除いた要素A、B、C、Dとなる

toMap()でMapへ変換

Stream<String> stream = Stream.of("A", "BB", "CCC", "DDDD");
//第1引数に要素からキーを生成するFunction、
//第2引数にMapのValueにあたるオブジェクトを生成するFunction
Map<Integer, String> map = stream.collect(Collectors.toMap(s -> s.length(), s -> s));
//結果 → {1=A, 2=BB, 3=CCC, 4=DDDD}

//ただし、重複するキーがあると例外が発生する
//下の例だと文字列長が4桁のものが2つあるため、キー重複で例外発生
Stream<String> stream = Stream.of("A", "BB", "CCC", "DDDD", "EEEE");
Map<Integer, String> map = stream.collect(Collectors.toMap(s -> s.length(), s -> s));
//Exception in thread "main" java.lang.IllegalStateException: Duplicate key DDDD が発生!

//重複キーがありうる場合はオーバーロードされているtoMap()を使用する
//3つ目の引数で、重複した場合の関数を指定している。この例だと重複した場合に最初にMapに格納されていたほうを残す。
Map<Integer, String> map = stream.collect(Collectors.toMap(s -> s.length(), s -> s, (s1, s2) -> s1));

groupingBy()で要素をグループ化する

//下の例では、文字列長ごとにグループ化し、キーに文字列長、値にキーに属する要素のリストとなる。
List<String> list = Arrays.asList("java", "scala", "ruby", "C", "haskell", "Go");
Map<Integer, List<String>> map = list.stream().collect(Collectors.groupingBy(s -> s.length()));
System.out.println(map);

partitioningBy()で要素をtrueとfalseでグループ化する

Stream<String> stream = Stream.of("one", "two", "three", "four", "five", "six");
//partitioningBy()を使用して条件に合致するか否かでグループ化する。
//条件に合致したものとしないものをそれぞれリストとして保持したMapを生成する
Map<Boolean, List<String>> map = stream.collect(Collectors.partitioningBy(s -> s.length() < 4));
System.out.println(map);
//結果は→ {false=[three, four, five], true=[one, two, six]} 

mapping()によるマッピングおよびCollectorによる処理

//要素に対するマッピング処理を行った後、さらにCollectorによるリダクションを行う
List<String> list = Arrays.asList("java", "scala", "ruby", "C", "haskell", "Go");
Integer collect = list.stream().collect(Collectors.mapping(s -> s.length(), Collectors.summingInt(i -> i)));
//23

maxBy()による最大要素の生成

//maxBy()に渡すComparetorの比較にしたがって、最大要素を返却する。
//この例だと、辞書順の最大がOptional型で返却される
List<String> list = Arrays.asList("java", "scala", "ruby", "C", "haskell", "Go");
Optional<String> result = list.stream().collect(Collectors.maxBy(String::compareTo));
//値はOptionalでラップされて返されるので、get()を使用して値を取得する
System.out.println(result.get());

minBy()による最小要素の生成

//使い方は上述のmaxBy()とほぼ同じ。
//こちらは最小要素を返却する
List<String> list = Arrays.asList("java", "scala", "ruby", "C", "haskell", "Go");
Optional<String> result = list.stream().collect(Collectors.minBy(String::compareTo));
System.out.println(result.get());

他にもいろいろあるが、とりあえずこのあたりで・・。
ちなみに、Collectorsクラスはあくまでよく使うであろうリダクション処理を実装しているだけなので、 独自でCollectorインターフェースを実装したクラスを作成すればオリジナルのリダクション処理用クラスが作れる。
そっちはまた今度やる。

メソッドを読みやすく

なんとなく。 読みやすさの定義はいろいろあると思うがふと思いついたので。
例えばこのような信号を表すクラスとそれに伴う挙動をenumで定義する。

使うクラスとか(これは準備)

   static class Signal {
        boolean isBroken;
        Color color;
        public boolean color() {
            return this.color;
        }
        public boolean isRed() {
            return this.color == Color.RED;
        }
        public boolean isBlue() {
            return this.color == Color.BLUE;
        }
        public boolean isYellow() {
            return this.color == Color.YELLOW;
        }
        public boolean isBroken() {
            return this.isBroken;
        }
        static enum Color {
            RED,
            YELLOW,
            BLUE;
        }
    }
    
    static enum Do {
        STOP,
        ATTENTION,
        GO;
    }

本題

あえての 読みづらいパターン

①変数への再代入をしている
②ネストが発生している
の2点があることで若干可読性に欠ける
以下のメソッドはこんな風に読める。
信号が壊れていないなら、
赤だったら止まるようにする。
赤でなくて黄色なら注意するようにする。
赤でなくて黄色でもなく、青なら進むようにする。
赤青黄のいずれでもないなら異常です。
赤青黄の色に従って動きます。

   Do doing(Signal signal) {
        Do doing = Do.STOP;
        //信号が壊れているならその時点で止まるでいいはず
        if (!signal.isBroken) {
            //赤でないなら~黄色でないなら~とかいう思考は普通しないはず
            if (signal.isRed()) {
                doing = Do.STOP;
            } else if (signal.isYellow()) {
                doing = Do.ATTENTION;
            } else if (signal.isBlue()) {
                doing = Do.GO;
            } else {
                throw new IllegalStateException();
            }
        } 
        return doing;
    }

ちょっと改善

①早期リターンを使用して信号が壊れていたらその時点でreturn
②条件ごとにreturnをすることでネストをなくす
の2点を実施することで、読みやすくなる。
以下のメソッドはこんな風に読める。
信号が壊れているなら、止まる。
信号が赤なら、止まる。
信号が黄色なら、注意する。
信号が青なら、進む。
そうじゃないならなんかおかしい。

   Do doing(Signal signal) {
        if (signal.isBroken()) return Do.STOP;
        
        if (signal.isRed()) return Do.STOP;
        if (signal.isYellow()) return Do.ATTENTION;
        if (signal.isBlue()) return Do.GO;

        throw new IllegalStateException();
    }

もうちょっと改善

enum自体にメソッドを持たせて挙動を変えさせる。
②呼び出し側ではenumの持つメソッドを呼び出すだけ。
ここまでする必要あるかわからんがenumの力をお借りして条件式をそもそもなくす。
条件式をなくすことで、コードリーディングが減る(でいいのかな?)

   static enum Color {
        RED {
            @Override
            Do doing() {
                return Do.STOP;
            }
        },
        YELLOW {
            @Override
            Do doing() {
                return Do.ATTENTION;
            }
        },
        BLUE {
            @Override
            Do doing() {
                return Do.GO;
            }
        };
        abstract Do doing();
    }

    Do doing(Signal signal) {
        if (signal.isBroken()) return Do.STOP;
        
        return signal.color().doing();
    }

まぁcolor().doing()ってのは変だけど。こんな感じかね。

Map(Java8)

今更だけど復習の意味で。 意外と把握していないメソッドがいろいろ追加されていたので。

   //データ準備
    Map<Integer, String> map = new HashMap<>();
    map.put(1, "one");
    map.put(2, "two");
    map.put(3, "three");

getOrDefault

従来の取得メソッド(get)に存在しない場合のデフォルト値を設定できるようになったメソッド

   //1 はキーに存在するため、そのまま[one]を返却する
    System.out.println(map.getOrDefault(1, "none"));
    //4 はキーに存在しないため、デフォルト値の[none]を返却する
    System.out.println(map.getOrDefault(4, "none"));

forEach

すべてのエントリに対する処理を記述できるメソッド
引数にBiConsumerをとるので、ラムダ式による記述が可能

   map.forEach((k,v) -> System.out.println(k + v));

replaceAll

各種エントリに対して、指定されたマッピング処理を施して値を置換する

   //#replaceAll 置換する
    //ちなみにこの例だとキーと値を文字列結合しているので、
    //マップ要素は[1=1one, 2=2two, 3=3three] になる。
    map.replaceAll((k, v) -> k + v);
    System.out.println(map);

    //replaceAllは、javaDocにあるように以下と同等
    BiFunction<Integer, String, String> function = (key, value) -> key + value;
    for (Map.Entry<Integer, String> entry : map.entrySet()) {
        entry.setValue(function.apply(entry.getKey(), entry.getValue()));
    }

putIfAbsent

キーが存在しない場合に、指定された値をマップに追加する。
キーが存在していれば値が返却され、キーが存在していない場合はNULLが返る。

   //1 はすでにキーに存在するため、なにも起きない(戻り値は[one])
    System.out.println(map.putIfAbsent(1, "one"));
    //5 は存在しないため、mapに格納され、戻り値はNULL
    System.out.println(map.putIfAbsent(5, "five"));

remove(key, value)

指定したキー及び値に紐づくエントリが存在している場合に削除する。

   System.out.println(map.remove(1, "one"));

replace

値を置換する。オーバーロードされており、無条件で置換するものと、旧値に合致したものを新値に置換するものとにわかれる

    //#replace(key, oldValue, newValue)
    //指定したキー及び値(oldValue)に合致したエントリがある場合にnewValueで置き換える
    //キー及び値が合致するため、キー1の値が「oneone」に置換される(trueが返却される)
    System.out.println(map.replace(1, "one", "oneone"));
    //キーは存在するが値が異なるため、置換はされない(falseが返却される)
    System.out.println(map.replace(2, "second", "twotwo"));

    //キーにが存在する場合に値を置換する
    System.out.println(map.replace(1, "2222"));

computeIfAbsent

指定されたキーがマップに存在していない場合にマッピング関数を使用して値の計算をしてマップへ格納する

    System.out.println(map.computeIfAbsent(6, k -> String.valueOf(k)));

computeIfPresent

指定されたキーの値が存在している場合にキーと値からマッピングの計算を行う

//キー1は存在しているため、計算処理が行われる。この例だとキーと値を文字列結合している。
//戻り値は計算後の値(1one)となる
String result = map.computeIfPresent(1, (key, value) -> key + value);
//キー5は存在しないため、戻り値はNULL。mapの要素に変化なし。
String result2 = map.computeIfPresent(5, (key, value) -> key + value);
System.out.println(result);
System.out.println(result2);

merge

指定されたキーが存在しない場合には指定された値に関連付ける。
そうでない場合は(キーが存在しているとき)、再マッピング関数の結果で値を置換する。

//キー1 は既に存在しているため再マッピング関数が適用され値が置換される
map.merge(1, "one", String::concat);
//キー2 も既に存在している。再マッピング処理が適用される。
//ただし、マッピング処理による結果がNULLのため、Mapからは削除される。
map.merge(2, "two", (k, v) -> null);
//キー3も存在しているため再マッピング関数の適用。キー3に紐づく値は「three333」となる
map.merge(3, "333", (k, v) -> k + v);
//キー4は存在していないキーのため、再マッピング処理は行われずに、指定した値(この例だと「four」)がキーに紐づけられてマップへ追加。
map.merge(4, "four", (k, v) -> v + v);

//一応結果
{1=oneone, 3=three333, 4=four}

今更だけど復習完了。 こうしてみてみると、やっぱりメソッドの命名って大事なんだなってことに気づく。 「値が存在しない場合にputする」とか、処理そのままの命名になっていたりするからなー。 このあたりもしっかり学ばなければいかんわ。

Stream の評価順

Streamの評価順序は、要素ひとつひとつに対して連続して行われる。
また、終端処理が実行されるまでは中間処理は実行されない。

   public static void main(String[] args) {
        
        Stream<String> stream = Stream.of("banana", "apple", "orange")
                .filter(lengthFilter)
                .peek(e -> System.out.println(e + " "))
                .map(toUpper)
                .peek(e -> System.out.println(e + " "));
        System.out.println("OK");
        stream.count(); //この終端処理が実行されるまでは上記の中間処理は実行されない
    }
    
    static Predicate<String> lengthFilter = new Predicate<String>() {
        @Override
        public boolean test(String t) {
            System.out.println("filtering...");//わかりやすいように標準出力する
            return t.length() > 5;
        }
    };
    
    static Function<String, String> toUpper = new Function<String, String>() {
        @Override
        public String apply(String t) {
            System.out.println("mappering...");
            return t.toUpperCase();
        }
    };

結果

OK
filtering...
banana 
mappering...
BANANA 
filtering...
filtering...
orange 
mappering...
ORANGE 

結果を見るとわかるように、終端処理(この例ではstream.count())がされるまでは、中間処理の内容が実行されていない。
(標準出力に「OK」が先に来ている)
また、Streamの要素一つ一つに対して処理が行われていることもわかる。

最初に、一つ目の要素(banana)に対して、filter()がかかり、map()が処理されていることが標準出力の内容から見て取れる。
また、フィルタ処理ではじかれるappleについては「filtering...」の標準出力後に最後の要素のorangeのfiltering...が出力されていることから、
マッピング処理に移らず、次の要素のフィルタリングへ移っていることもわかる。

以上。

JDBCいろいろ

接続

//接続先URL
String url = "jdbc:mysql://localhost:3306/study?autoReconnect=true&useSSL=false&serverTimezone=UTC";
//ユーザ名
String user = "root";
//パスワード
String pass = "root";
//接続する
//JavaSE7よりtry-with-resourcesが使えるようになっているので、
//クローズし忘れや簡潔なコード記述のためにこう書くのがベター
try (Connection con  = DriverManager.getConnection(url, user, pass)) {
    //SQLの実行はここに書く
} catch (SQLException e) {
    e.printStackTrace();
}

ちなみにURLの個所についていろいろ
例外が出る場合がある。
①The server time zone value ' (W)' is unrecognized or represents more than one time zone. You must configure either the server or JDBC driver
②Establishing SSL connection without server's identity verification
URLの部分の、
①autoReconnect=true&useSSL=false
②&serverTimezone=UTC
で、それぞれ対処。 SSL接続をしないこと、サーバのタイムゾーン指定をすることで例外抑止する。

SQLの実行

try (Connection con  = DriverManager.getConnection(url, user, pass)) {
    //Statementオブジェクトの生成
    Statement st = con.createStatement();
    String selectSql = "select * from user";
    String update = "update user set id = 3";
    //select分の発行は#executeQueryを使用する
    st.executeQuery(selectSql);
    //更新SQLの場合は#executeUpdateを使用する
    st.executeUpdate(update);
    //なお、#executeメソッドも存在する
    //executeは結果が存在する場合にtrueを返す
    boolean result = st.execute(selectSql);
    
} catch (SQLException e) {
    e.printStackTrace();
}

SQLの実行②結果の取得・操作

//クエリの結果はResultSetオブジェクトが保持している
ResultSet resultset = st.executeQuery(selectSql);
//更新系は更新件数が戻り値となる
int updateCnt = st.executeUpdate(update);

//ResultSetからのデータ取得
resultset.next();
//このように書くことで結果セットの全レコードに対する処理ができる
while (resultSet.next()) {
    System.out.println(rs.getInt("id"));
}
//

並行処理いろいろ

Runnableを使用した場合

//Runnableオブジェクトを生成する
Runnable r = () -> {
    System.out.println("Task is executed.");
    System.out.println("end");
};

//Executorを使用して実行
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.execute(r);

//Executorは通常ファクトリメソッドを使用して生成する
Executors.newSingleThreadExecutor();
Executors.newCachedThreadPool();
Executors.newFixedThreadPool(3);
Executors.newScheduledThreadPool(2);
//違いはよくわからない(勉強中)

Callableを使用する場合

//Runnableでは戻り値や例外処理ができないため、
//そのような場合はCallbele<T>を使用する
Callable<String> call = () -> {
    return "called!";
};
//Callableを使用する場合はExecutorのsubmitメソッドを使用する
//futureオブジェクトが返却される
Future<String> future = executor.submit(call);
//Callableの実装の戻り値を取得する場合は#get()を使用する
System.out.println(future.get());

なお、タスクの実行が終了しても、Executorそのものは自動的に終了しない。
そのため、#shutdown()を明示的に呼び出す必要がある。

//Executorの終了
executor.shutdown();

ちなみに、Executorの終了後にタスクを実行すると例外が発生する

executor.shutdown();
//Executorの終了後にタスクを実行すると、
//java.util.concurrent.RejectedExecutionExceptionが発生
executor.submit(call);

Fork/Join フレームワーク

//RecursiveActionを継承したクラスを作る
public class MyAction extends RecursiveAction {

    @Override
    protected void compute() {
        
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("MyAction!");
    }

}

//実行側
    public static void main(String[] args) throws Exception {
        ForkJoinPool executor = new ForkJoinPool();
        executor.execute(new MyAction());
        System.out.println("main:ok");
        Thread.sleep(3000);
        System.out.println("done!");
    }

Streamを使った並列処理

List<Integer> list = new ArrayList<>(Arrays.asList(1,2,3,4,5));
//parallelStreamを使用してStream化した場合、並列ストリームになる
//以下のコードは2乗した数字が出る順序は不定
list.parallelStream().map(i -> i * i).forEach(System.out::println);

//順次ストリームへの変換
list.parallelStream()
.map(i -> i * i)
.sequential() //このsequential()を呼び出すことで順次制御になる
.forEach(System.out::println);
//値は一定になる

Java Date and Time API

Java Date and Time APIについていろいろ

//年月日日時
System.out.println(LocalDateTime.now());
//年月日
System.out.println(LocalDate.now());
//時間のみ
System.out.println(LocalTime.now());

結果

2018-05-04T11:36:00.778
2018-05-04
11:36:00.779

LocalDateオブジェクトの生成

//指定した日付のLocalDateを生成
LocalDate day = LocalDate.of(2018, 5, 1);

//月を表すenumがあるので、そちらを使用したほうがよい
LocalDate day2 = LocalDate.of(2018, Month.MAY, 1);

//LocalDateはコンストラクタではなくファクトリメソッドでオブジェクトを生成する

日付の加減算

//基準日の生成(2018/1/1)
LocalDate day = LocalDate.of(2018, Month.JANUARY, 1);
//1年後
LocalDate after1Years = day.plus(1, ChronoUnit.YEARS);
//10日前
LocalDate before10Days = day.minus(10, ChronoUnit.DAYS);

//第1引数に値、第2引数にどの日時単位の加減算をするかを表すchronoUnitを指定する

日付の間の時間量

//月数の時間量
long until = day.until(day2, ChronoUnit.MONTHS);
//日数の時間量
long untilDate = day.until(day2, ChronoUnit.DAYS);

Periodクラスを使用した時間量の計算

//基準日の生成(2018/1/1)
LocalDate day = LocalDate.of(2018, Month.JANUARY, 1);
//基準日の生成(2020/5/25)
LocalDate day2 = LocalDate.of(2020, Month.MAY, 25);

Period period = Period.between(day, day2);
//日付間隔 → 24
int days = period.getDays();
//月間隔 → 4
int months = period.getMonths();
//年間隔 → 2
int years = period.getYears();

日付フォーマッタを使用したフォーマット

LocalDate date = LocalDate.of(2018, Month.MAY, 1);
//BASIC_ISO_DATEでフォーマットする
//これ以外にも基本的なフォーマットはDateTimeFormatterクラスに用意されている
DateTimeFormatter formatter = DateTimeFormatter.BASIC_ISO_DATE;
//2018-05-01
System.out.println(date);
//20180501
System.out.println(formatter.format(date));

//ofPatternを使用すると独自フォーマッタが作成できる
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyねんMMがつddにち");
//2018ねん05がつ01にち
System.out.println(formatter.format(date));