yucatio@システムエンジニア

趣味で作ったものいろいろ

Enumを宣言順に並び変える(ジェネリクス版)

Enumのリストを宣言順に並び変えたいときには、自然順に並べるか、宣言順であることを明示したい場合はEnum.ordinal()を使用します。

Enum (Java SE 17 & JDK 17)

// Enumクラス
public enum Element {
  FIRE, WATER, ELECTRRIC, POISON
}
List<Element> elements = List.of(Element.ELECTRRIC, Element.POISON, Element.WATER, Element.FIRE, Element.WATER, Element.POISON);

List<Element> sorted = elements.stream()
    .sorted()
    .toList();

// もしくは
//    List<Element> sorted = elements.stream()
//        .sorted(Comparator.comparing(Enum::ordinal))
//        .toList();

System.out.println(sorted);
// => [FIRE, WATER, WATER, ELECTRRIC, POISON, POISON]

メソッドに切り出し

並び変え部分をメソッドにします。

public static List<Element> sortByOrdinal(List<Element> list) {
  return list.stream()
      .sorted()
      .toList();
}
// 呼び出し側
List<Element> elements = List.of(Element.ELECTRRIC, Element.POISON, Element.WATER, Element.FIRE, Element.WATER, Element.POISON);
    
List<Element> sorted = sortByOrdinal(elements);
    
System.out.println(sorted);
// => [FIRE, WATER, WATER, ELECTRRIC, POISON, POISON]

ジェネリクス

ジェネリクス化します。Enumクラスの親クラスはE extends Enum<E>で表すことができます。

public static <E extends Enum<E>> List<E> sortByOrdinal(List<E> enumList) {
  if (enumList == null) {
    return null;
  }

  return enumList.stream()
      .sorted()
      .toList();

  // もしくは
  // return enumList.stream()
  //  .sorted(Comparator.comparing(Enum::ordinal))
  //  .toList();
}

呼び出し側は特に変更なしです。

// 呼び出し側
List<Element> elements = List.of(Element.ELECTRRIC, Element.POISON, Element.WATER, Element.FIRE, Element.WATER, Element.POISON);
    
List<Element> sorted = sortByOrdinal(elements);
    
System.out.println(sorted);
// => [FIRE, WATER, WATER, ELECTRRIC, POISON, POISON]

バージョン

Java 17

Optional.flatMap()は再帰的かどうか

疑問

Optional.flatMap()は再帰的かどうか(2段以上Optionalがネストされた場合に一番内側の値を取得するか)

結論

再帰的でない。

Optional.flatMap()とは

Optionalのマップの結果がOptionalのとき、その中身を取り出してくれるメソッド

Optional<String> o = Optional.of("a");
    
Optional<Integer> p = o.flatMap(a -> Optional.of(3));
    
p.ifPresent(i -> System.out.println(i));
// 3

OptionalにさらにOptionalをネストしてみる

Optional<String> o = Optional.of("a");

Optional<Optional<Integer>> q =  o.flatMap(a -> Optional.of(Optional.of(5)));

Optional<Integer> r = q.flatMap(Function.identity());

JavaのパラレルストリームでCollectors.toList()は順番に並ぶか

背景

Javaでパラレルストリームを使用すると、forEachでは順番がランダムになる。

List<String> list = List.of("A", "B", "C");
list.parallelStream().forEach(System.out::println); // B C A など

Collectors.toList()で集約したリストは元の配列の順番に並びます。

List<String> list = List.of("A", "B", "C");

List<String> parallelList = list.parallelStream().collect(Collectors.toList());
parallelList.forEach(System.out::println);  // A B C

Collectors.toList()を使用した場合、元の順番どおりに並ぶことは保証されているのか

結論

順番は保証されている

途中、sortedなどを挟んでいる場合はそちらの順番で並ぶ。

Javadocを確認する

Collectors.toList()のJavadocを確認すると、以下のように書いてあります。

すべての入力要素を検出順にList内に集めるCollector

Collectors (Java SE 11 & JDK 11 )

検出順とは何でしょうか。 In what order do the elements of a stream become available? | Maurice Naftalin's Lambda FAQ の記事が分かりやすかったので引用します。

The source has an encounter order : this is the order in which the source itself makes its elements available. For example, the encounter order for an array is defined by the ordering of its elements; for an ordered collection like a List or a NavigableMap, by its iteration order; for a generator function by the function itself, and so on.

訳すと、

入力は検出順序を持つ: 検出順序とは、そのオブジェクトが持っているメンバーを取り出す際の順序のことである。例えば、配列の検出順序はその並びそのものであるし、ListやNavigableMapなどのコレクションの検出順序は、イテレータで取り出される順序のことであるし、ジェネレータであれば、それが生成する順のことである。 ればその順番が保存されるということです。

つまり、Collectors.toList()は入力のListの順番通りに並ぶことが分かります。

参考

JavaのパラレルストリームでforEachは並列実行されるか

結論

javaのパラレルストリームでforEachは並列実行される

コードを書いて確かめてみた

forEach内でsleepして動作を確かめてみた

List<String> list = List.of("A", "B", "C");

list.parallelStream()
    .forEach(s -> {
      System.out.println(s + ": start");
      System.out.println(s + ": " + Thread.currentThread().getName());
      try {
        Thread.sleep(100);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      System.out.println(s + ": end");
    });

実行結果

B: start
C: start
C: ForkJoinPool.commonPool-worker-1
A: start
A: ForkJoinPool.commonPool-worker-2
B: main
B: end
A: end
C: end

Javaでリストの重複しているインデックスを取得する(ストリームAPI使用)

Javaで、リスト内で重複している要素の、インデックスをグルーピングして取得します。ストリームAPIを使用すると簡単に書けます。

List<String> list = List.of("A", "B", "C", "B", "B", "D", "A");
Map<String, List<Integer>> indexGroup = IntStream.range(0, list.size())
    .boxed()
    .collect(Collectors.groupingBy(list::get));
    
indexGroup.forEach((k, v) -> System.out.println(k + ":" + v));
// A:[0, 6]
// B:[1, 3, 4]
// C:[2]
// D:[5]

重複しているインデックスを取得する

単に、重複しているインデックスがほしい場合はこちら。

List<String> list = List.of("A", "B", "C", "B", "B", "D", "A");

Map<String, Long> countMap = list.stream().collect(
    Collectors.groupingBy(
        Function.identity(),
        Collectors.counting()));

List<Integer> duplicatedList = IntStream.range(0, list.size())
    .filter(i -> countMap.get(list.get(i)) > 1)
    .boxed()
    .collect(Collectors.toList());

duplicatedList.forEach(System.out::println);
// 0
// 1
// 3
// 4
// 6

こちらもどうぞ

yucatio.hatenablog.com

Javaでリストの重複をカウントする(ストリームAPI使用)

Javaでリストの重複をカウントする方法です。ストリームAPIを使用すると簡単に書けます。

重複をカウントする

List<String> list = List.of("A", "B", "C", "B", "B", "D", "A");

Map<String, Long> countMap = list.stream().collect(
    Collectors.groupingBy(
        Function.identity(),
        Collectors.counting()));

countMap.forEach((k, v) -> System.out.println(k + ":" + v));
// A:2
// B:3
// C:1
// D:1

重複が多い順に並び変える

さらに、重複数が多い順に並び変えます

List<String> list = List.of("A", "B", "C", "B", "B", "D", "A");

Map<String, Long> countMap = list.stream().collect(
    Collectors.groupingBy(
        Function.identity(),
        Collectors.counting()));

List<Map.Entry<String, Long>> sortedCountList = countMap.entrySet().stream()
    .sorted(Map.Entry.<String, Long> comparingByValue().reversed())
    .collect(Collectors.toList());

sortedCountList.forEach(e -> System.out.println(e.getValue() + ":" + e.getKey()));
// 3:B
// 2:A
// 1:C
// 1:D

Java歴13年がJava Gold(SE 11)を受けてみた

無事Java Silverの資格を取得したので(過去記事: Java歴13年がJava Silver (SE 11)を受けてみた【勉強編】 - yucatio@システムエンジニア を参照)、本命のJava Goldを受験しました。

受験のきっかけ

  • TwitterJava Silverを受験する人を見かけて興味を持った
  • 自分自身がJavaを勉強したときのバージョンが5で、それ以降のバージョンは適宜勉強してきたつもりだが、この機会にちゃんと勉強しようと思った
  • 将来的にプログラミングの先生になるのもよいと思っているので、資格があるとよいと思った

私について

  • Web系システムエンジニア。歴13年
  • Javaは業務で使うもののばりばり書くというより、調査・設計からリリースまでの一環でコードを書くといったイメージ

勉強期間

3週間。 勉強時間は1日1-2時間程度。

ざっくりしたスケジュールは以下です

  • 1-7日目: 黒本1周目(模試以外)
  • 8-14日目: 黒本2周目(模試含む)
  • 15-試験本番: 黒本3周目、黒本で苦手分野の復習、Qiita記事を読む、Oracle公式サンプル問題を解く

勉強方法

勉強をする際に購入した書籍は『徹底攻略Java SE 11 Gold問題集[1Z0-816]対応』通称「黒本」です。Silverのときと同様、問題に対し解説が豊富でかつ読みやすいです。Java Gold関連の書籍は、このほかにも「紫本」「白本」とよばれる書籍も存在しますが、私の場合は黒本のみで合格することができました。

[商品価格に関しましては、リンクが作成された時点と現時点で情報が変更されている場合がございます。]

徹底攻略Java SE 11 Gold問題集[1Z0-816]対応 [ 志賀 澄人 ]
価格:4400円(税込、送料無料) (2022/10/8時点)


黒本1周目(模試以外)

試験の出題範囲と出題傾向を確認するため、はじめに黒本を1周しました。このときは1問ずつ問題を解いて、解答を確認しました。

1周目にあまり時間をかけないことは試験の概要の把握にはとてもよかったです。

ちなみに最初の正答率は20%くらいでした。しかし、各章のトピックは実務で扱ったものが多く、勉強すれば何とかなるという気がしました。(逆に、黒本1周して扱ったことがない分野が多いと感じるなら、紫本など読んだほうがよいのかもしれません)

黒本2周目(模試以外)

2周目は1周目の確認と、より理解を深めるように学習しました。本のコードを実際に書いたり、Javadocを確認しました。正答率は87%でした。

1周目では「がんばればいけそう」と思っていましたが、2周目では「覚えることが多すぎて覚えきれない」とややネガティブになりました。

黒本(模試)

Goldの黒本には模試が1回しかついていないので(Silverは2回ついていた)、黒本を2周してある程度理解した後に模試に挑みました。このことはよかったと思っています。

正答率が82%でした。ここで受験日を決めました。

黒本3周目以降

3周目以降は苦手分野を中心に復習していきました。JDBC、モジュール、セキュアコーディングが苦手だったのでそちらを何度か読みました。ほかの分野は流し読みしました。覚えていないAPIについてはJavadocを確認しました。

Qiita記事を読む

黒本、Oracle公式ドキュメント以外では、Qiitaで"Java Gold"と検索して、出てきた記事を1つずつ読みました。

試験の要点をまとめている記事は、黒本に書かれていない項目もあり、勉強になりました。

合格体験記は、勉強の方法や、問題の出題傾向、難易度を知るのに役立ちました。

Oracle公式サンプル問題

"Java Gold サンプル"で検索すると公式のサンプル問題を見るとこができます。SE 8 ですが、試験範囲はほぼ同じなので解きました。

受験の申し込みと受験

受験の流れはSilverの時と同じです。

yucatio.hatenablog.com

出題内容

出題される内容は、ほとんどが黒本かOracle公式サンプルと同じか類題でした。体感では、黒本(もしくは公式サンプル)とほぼ同じ・類題・初見の割合は、40:50:10でした。

出題される分野は、ストリームAPIに関連する問題が全体の50%以上だったように思います。ほかの分野はまんべんなく出題されました。アノテーション関連は私のときは出題されませんでした。

結果

試験時間は180分でしたが、90分程度で終了しました。 89%で合格しました。

感想

受験してよかったか?

よかった。当初の目的である、Java 5より後の機能について知ることができました。特に、ストリームAPIでは、便利なAPIを知ることができました。Javaでgroupbyができないと思い込んでいましたが、あることが知れてよかったです。

デメリットは高い受験料くらいです。

ほかの人に受験を勧める?

受験料がネックですが、勧めます。

試験範囲は、実務でよく使う機能が中心となっています。新人にとっては、これから使う機能の予習になりますし、ベテランにとっては新しいバージョンの機能を学ぶ機会となると同時に実力の証明にもなります。

終わりに

難しいと思われているJava Goldですが、黒本と公式のサンプル問題が解けるようになれば受かります。機会があれば受験をおすすめします。