20201112のJavaに関する記事は11件です。

Spring Security の BCryptPasswordEncoder でハッシュ化された password を Perl で扱う

Spring Security の BCryptPasswordEncoder でハッシュ化された password などの文字列を、Perl から使って認証とかに使いたい時だってあるじゃない?

いや、ねーよな。

前提

こんな出来事がありました。

DB に保存されたハッシュ値

$2a$10$Cc6/UiniMuKkyVsVM.FUt.rmgDm0UOQxuhuGSSuL/LzZUGrNeGvxq

こんな値が保存されていた。
Spring Security の仕業らしい。
見慣れぬこのハッシュ値は一体どういう値なんだい。

という状況にハマっていた時、色々ググったが、コレって情報が出てこなかった。

生パスワード

JscxAX:5[4Q]NYoK

とあるログインユーザは
「こいつが俺のパスワードなんだけど合ってる?」
などと、Perl の私に尋ねてきた。

えっ、さっきの Spring Security がハッシュ化して保存されてるあのヘンテコな文字列とこのパスワードの検証?
Perl の私にはわかんないよ?

いやいや、頑張りましょう

案外簡単なのですよ

コード

use strict;
use warnings;
use utf8;
use feature qw(say);
use Crypt::Eksblowfish::Bcrypt qw(bcrypt);

my $raw_password = 'JscxAX:5[4Q]NYoK';

my $hashed_password = '$2a$10$Cc6/UiniMuKkyVsVM.FUt.rmgDm0UOQxuhuGSSuL/LzZUGrNeGvxq';

my $salt  = substr($hashed_password, 0, 29); # "$2a$10$" + 22 文字分
my $check = bcrypt($raw_password, $salt);

if ($check eq $hashed_password) {
    say "ATTERUYO!";
}
else {
    say "ATTENAIYO!";
}

結果

ATTERUYO!

よかった。あってた。

…え、これ、毎回 ATTERUYO! が出たりしない?

もし、ユーザが間違ったパスワードを入力してたら

my $raw_password = 'JscxAX:4[5Q]NYoK';

一行だけ、微妙に間違ってる感じに変更。

結果

ATTENAIYO!

よかった。
ちゃんと間違いを判定できてる。

Perl からも同じマナーで保存しといてね

Spring Security さんが
「Perl から、パスワード変更して、保存しておく機能作っておいてね」
とおっしゃっている。

えぇ、貴方はその値を検証で使うんですよね?
貴方にも扱える形式にしないとダメですか…?

…当然、そうしないとダメですよね。

嫌われ者の Perl さんにもちゃんと出来るはずよ。だから頑張って

Java に出来て Perl に出来ないとか、ラクダの風上にも置けないこと言っちゃダメです。

コード

use strict;
use warnings;
use utf8;
use feature qw(say);
use Crypt::Eksblowfish::Bcrypt qw(bcrypt en_base64);
use Crypt::Random qw(makerandom_octet);

sub gen_salt {
    return en_base64(makerandom_octet(Length => 16));
}

my $raw_password = 'Z86J9_Kr_sDcw#o4'; # 新しいパスワード

my $salt = '$2a$10$' . gen_salt();
my $hashed_password = bcrypt($raw_password, $salt);

say $hashed_password;

結果

$2a$10$/IwrxT05tg1ZlmUrV.7eAOsowJsbkVHs6ku54FC0VHBew23HOm61W

それっぽいのが出来たじゃない

Spring Security で検証してね

Z86J9_Kr_sDcw#o4
というパスワードが上記の Perl のコードによって
$2a$10$/IwrxT05tg1ZlmUrV.7eAOsowJsbkVHs6ku54FC0VHBew23HOm61W
っていうそれっぽい文字列にされて DB に保存されてしまっている。
これ、Java (Spring Security) で本当にこのハッシュを正しく検証が出来るのか…?

コード

いかにも、Java とか普段書かない人間が書いた感の強い、随分と適当なコードによる検証を行った。

package com.example.demo;

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

public class DemoApplication {
    public static void main(String[] args) {
        String rawPassword = "Z86Jq_Kr_sDcw#o4";
        String hashedPassword = "$2a$10$/IwrxT05tg1ZlmUrV.7eAOsowJsbkVHs6ku54FC0VHBew23HOm61W";

        PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

        if (passwordEncoder.matches(rawPassword, hashedPassword)) {
            System.out.println("ATTERUYO!");
        }
        else {
            System.out.println("ATTENAIYO!");
        }
    }
}

結果

ATTERUYO!

ちゃんと出来たよよかったね。

… え、これ、毎回 ATTERUYO! が出たりしない?

もし、ユーザが間違ったパスワードを入力してたら

        String rawPassword = "Z86Jq_Kr_sDcw#o4";

一行だけ、微妙に間違ってる感じに変更。

結果

ATTENAIYO!

よかった。
ちゃんと間違いを判定できてる。

他のハッシュ化は?

SCryptPasswordEncoder とか Pbkdf2PasswordEncoder とかあるけど、今時点で自分の環境でそれは課題になってないので、いつか誰かがまとめたらいいね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Java】toString()メソッドの使い方

プログラミング勉強日記

2020年11月12日
数値を文字列に変換するためのtoStringメソッドについてまとめる。

toStringメソッドとは

 数値型などをString型に変換するために使う。引数にString型の文字列に変換したい引数を指定し、戻り値はString型の文字列を返す。

数値を文字列に変換する
public class Main {
  pubic static void main(String[] args) {
    int num1 = 1234;
    int num2 = 5678;
    System.out.println(num1 + num2);

    // 数値を文字列に変換する
    String str1 = Integer.toString(num1);
    String str2 = Integer.toString(num2);
    System.out.println(str1 + str2);
  }
}
実行結果
6912
12345678

toStringメソッドをオーバーライドして使う

 自作クラスでtoStringメソッドを使うためには、オーバーライドする必要がある。オーバーライドするときは@Overrideをつける。

自作クラスでtoStringメソッドをオーバーライドする
class Age {
  int age = 21;
  @Override 
  public String toString() {
    return "age =" + age;
  }
}
public static Main() {
  public static void main(String89 args) {
    Age age = new age();
    String str = profile.toString();
    System.out.println(str);
  }
}
実行結果
age = 22

HashMapでtoStringメソッドを使う

 toStringメソッドはjava.lang.Objectクラスで定義されているので、どのクラスでも使うことができる。そのため、無意識にtoStringメソッドを使っていることもある。

import java.util.HashMap;

public class Main{
    public static void main(String[] args){
        HashMap<String, String> map = new HashMap<>();
        map.put("key1", "value1");
        map.put("key2", "value2");
        map.put("key3", "value3");
        System.out.println(map);
    }
}
実行結果
{key1=value1, key2=value2, key3=value3}

 このコードでは最後にSystem.out.println()の引数の中にjava.util.HashMapのオブジェクトを指定して、オブジェクト内の要素を確認するコードになっている。java.lang.String以外の参照型が引数に指定された場合は、System.out.println(map.toString());と記述しているのと同じようになり、そのオブジェクトのtoString()メソッドで文字列が出力されている。

参考文献

【Java入門】toStringで数値を文字列へ変換(オーバーライドも解説)
[Java]toString()メソッドの有用な使い方

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Java の SimpleDateFormat で日時のパース・フォーマットをするサンプルコード

概要

  • Java の SimpleDateFormat クラスで日時文字列をパースして Date オブジェクトを文字列化する

サンプルコード

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * SimpleDateFormat でパース・フォーマットするサンプル。
 */
public class Sample {

  public static void main(String[] args) {

    String[] sourceList = {
      "1-2-3 4:5:6",
      "99-2-3 4:5:6",
      "999-2-3 4:5:6",
      "2000-12-31 12:34:56",
      "9999-12-31 12:34:56.0001",
      "9999-12-31 12:34:56.000001",
      "10000-12-31 12:34:56",
      "XXXX-YY-ZZ AA:BB:CC",
      "9999X-12X-31X 12X:34X:56X",
      "X9999-X12-X31 X12:X34:X56",
    };

    parseAndFormat(sourceList, "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm:ss");
    parseAndFormat(sourceList, "y-M-d H:m:s", "yyyy-MM-dd HH:mm:ss");
    parseAndFormat(sourceList, "yyyy-MM-dd HH:mm:ss", "y-M-d H:m:s");
  }

  /**
   * パースしてフォーマットする。
   * @param sourceList パース対象の文字列の配列
   * @param parsePattern 日時文字列をパースするパターン文字列
   * @param formatPattern Date オブジェクトをフォーマットするパターン文字列
   */
  private static void parseAndFormat(String[] sourceList, String parsePattern, String formatPattern) {

    // パーサーを準備
    SimpleDateFormat parser = new SimpleDateFormat(parsePattern);

    // フォーマッターを準備
    SimpleDateFormat formatter = new SimpleDateFormat(formatPattern);

    System.out.println("****************************************");
    System.out.println("Parse:  " + parsePattern);
    System.out.println("Format: " + formatPattern);

    for (String source : sourceList) {
      try {
        // 日時文字列を Date オブジェクトに変換
        Date date = parser.parse(source);
        // Date オブジェクトを日時文字列に変換
        String text = formatter.format(date);
        System.out.println(source + " -> " + text);
      } catch (ParseException e) {
        System.out.println(source + " -> " + e.toString());
      }
    }

    System.out.println();
  }
}

実行結果

Java 8 (AdoptOpenJDK 1.8.0_272-b10) + macOS Catalina にて実行した例。

****************************************
Parse:  yyyy-MM-dd HH:mm:ss
Format: yyyy-MM-dd HH:mm:ss
1-2-3 4:5:6 -> 0001-02-03 04:05:06
99-2-3 4:5:6 -> 0099-02-03 04:05:06
999-2-3 4:5:6 -> 0999-02-03 04:05:06
2000-12-31 12:34:56 -> 2000-12-31 12:34:56
9999-12-31 12:34:56.0001 -> 9999-12-31 12:34:56
9999-12-31 12:34:56.000001 -> 9999-12-31 12:34:56
10000-12-31 12:34:56 -> 10000-12-31 12:34:56
XXXX-YY-ZZ AA:BB:CC -> java.text.ParseException: Unparseable date: "XXXX-YY-ZZ AA:BB:CC"
9999X-12X-31X 12X:34X:56X -> java.text.ParseException: Unparseable date: "9999X-12X-31X 12X:34X:56X"
X9999-X12-X31 X12:X34:X56 -> java.text.ParseException: Unparseable date: "X9999-X12-X31 X12:X34:X56"

****************************************
Parse:  y-M-d H:m:s
Format: yyyy-MM-dd HH:mm:ss
1-2-3 4:5:6 -> 0001-02-03 04:05:06
99-2-3 4:5:6 -> 1999-02-03 04:05:06
999-2-3 4:5:6 -> 0999-02-03 04:05:06
2000-12-31 12:34:56 -> 2000-12-31 12:34:56
9999-12-31 12:34:56.0001 -> 9999-12-31 12:34:56
9999-12-31 12:34:56.000001 -> 9999-12-31 12:34:56
10000-12-31 12:34:56 -> 10000-12-31 12:34:56
XXXX-YY-ZZ AA:BB:CC -> java.text.ParseException: Unparseable date: "XXXX-YY-ZZ AA:BB:CC"
9999X-12X-31X 12X:34X:56X -> java.text.ParseException: Unparseable date: "9999X-12X-31X 12X:34X:56X"
X9999-X12-X31 X12:X34:X56 -> java.text.ParseException: Unparseable date: "X9999-X12-X31 X12:X34:X56"

****************************************
Parse:  yyyy-MM-dd HH:mm:ss
Format: y-M-d H:m:s
1-2-3 4:5:6 -> 1-2-3 4:5:6
99-2-3 4:5:6 -> 99-2-3 4:5:6
999-2-3 4:5:6 -> 999-2-3 4:5:6
2000-12-31 12:34:56 -> 2000-12-31 12:34:56
9999-12-31 12:34:56.0001 -> 9999-12-31 12:34:56
9999-12-31 12:34:56.000001 -> 9999-12-31 12:34:56
10000-12-31 12:34:56 -> 10000-12-31 12:34:56
XXXX-YY-ZZ AA:BB:CC -> java.text.ParseException: Unparseable date: "XXXX-YY-ZZ AA:BB:CC"
9999X-12X-31X 12X:34X:56X -> java.text.ParseException: Unparseable date: "9999X-12X-31X 12X:34X:56X"
X9999-X12-X31 X12:X34:X56 -> java.text.ParseException: Unparseable date: "X9999-X12-X31 X12:X34:X56"

参考資料

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Decorator Pattern

オブジェクトをラップし、ラップしたオブジェクトの機能を拡張する

Design Pattarm MENU

サンプルコードでは、新しく機能を追加する代わりに、Stringオブジェクトをカッコでラップします

以下のクラス構成で確認します

クラス 説明
abstract
Display.class
各クラスの共通型とする
抽象メソッドを定義
Message.class Displayを拡張
このクラスのStringフィールドがラップ対象
Decorator.class Displayを拡張
Decoratorを実装する
user(Main.class) 動作を確認する

*user 他の開発者がこのパターンを利用する、という意味合いを含みます

以下はサンプルコードです

abstract_class_Display
abstract class Display{
  abstract String getStr();
}
Message.class
class Message extends Display{
      String  msg;
      Message(String s){this.msg=s;}
      String  getStr() {return msg;} 
}
Decorder.class
class Decorator extends Display{
      Display   display;
      StringBuffer sb = new StringBuffer();

      Decorator(Display d){this.display=d;}
      String getStr(){
             return makeBorder(display.getStr());}
      String makeBorder(String msg){
             sb.append("<")
               .append(msg)
               .append(">");
             return sb.toString();
  }
}
user(Main.class)
public static void main(String[] args){
       Display d1 = new Message("Hello java");
       Display d2 = new Decorator(new Decorator(new Decorator(d1)));
       System.out.println(d2.getStr());
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Java】マルチスレッド処理 - 排他制御

マルチスレッド処理ではデータ共有に注意

  • マルチスレッド処理ではデータがスレッド間で共有されているかどうかを意識する
  • 共有データに複数のスレッドが同時に処理実施するとデータ矛盾が発生してしまう!

NG例

  • メインスレッドで用意したvalueフィールドを10万個のスレッドで並行してインクリメントする例(結果は10万を期待)
//NG例
public class SynchronizedNotUse {
//複数スレッドで共有するデータ
  private int value = 0;

//10万個スレッド実行
  public static void main(String[] args) {
    final int TASK_NUM = 100000;
    var th = new Thread[TASK_NUM];
    var tb = new SynchronizedNotUse();
    //スレッド生成、実行
    for (var i = 0; i < TASK_NUM; i++) {
      th[i] = new Thread(() -> {
        tb.increment();
      });
      th[i].start();
    }
    //スレッド終了まで待機
    for (var i = 0; i < TASK_NUM; i++) {
      try {
        th[i].join();
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }

    System.out.println(tb.value); //毎回異なる
  }
  //valueフィールドをインクリメント
  void increment() {
    this.value++;
  }
}
  • いつも結果は10万を期待しているのに、毎回異なる値が出てしまう
  • 問題なのはSynchronizedNotUseクラスのincrementメソッド
    • incrementメソッドではインスタンスフィールドのvalueの値を++演算子で加算
    • 一見して他スレッドは割り込めない気がするが、++演算子は内部的に
    • 変数の現在地を取得、値を加算、演算結果の再代入
    • 処理途中で他スレッドの割り込み発生すると結果が正しく反映されない!
  • synchronized命令を使う!

synchronizedブロックで排他制御

  • synchronizedブロック内で囲まれた処理は複数のスレッドで同時に呼ばれることがなくなる
  • ほぼ同時でも後に呼ばれた方が先行処理(ロックを獲得)が終わるまで待ち状態になる
  • ロック:特定の処理を占有すること
  • 排他制御(同期処理):ロックで同時実行によるデータ不整合を防ぐこと
    • 同期処理が正しく動作するのはロック対象のオブジェクトが同一のインスタンスの場合のみ
    • 異なるロックオブジェクト間では同期できない
  • ブロックを抜けたら自動でロック解放
public class SynchronizedUse {
  private int value = 0;

  public static void main(String[] args) {
    final int TASK_NUM = 100000;
    var th = new Thread[TASK_NUM];
    var tb = new SynchronizedUse();
    for (var i = 0; i < TASK_NUM; i++) {
      th[i] = new Thread(() -> {
        tb.increment();
      });
      th[i].start();
    }
    for (var i = 0; i < TASK_NUM; i++) {
      try {
        th[i].join();
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }

    System.out.println(tb.value);
  }
   //ロック対象のオブジェクトを現在のインスタンスに指定
  void increment() {
    synchronized(this) {
      this.value++;
    }
  }
}

synchronized修飾子

  • メソッド全体を同期化の対象にしたいときに使う
    • あるスレッドがsynchronizedメソッド1を実行している間は別スレッドがsynchronizedメソッド2を実行できない
  • メソッドを抜けたら自動でロック解放
  • コンパイラがキャッシュしたコードを生成する(最適化)場合に、synchronizedでは元のメモリから読まれるので整合性が保証される
    • cf: 変数の代入/取得のみの場合にはvolatile修飾子も利用できる
synchronized void increment() {
    this.value++;
}

明示的なロック

  • 明示的なロック:明示的にロックの獲得/解除が必要
  • 暗黙的なロック:ロックの獲得/解除を意識しなくていい(synchronized)
  • ReentrantLockクラス
    • 明示的なロックを獲得した場合、その直後からtryブロックでくくる
    • finallyブロックでロック解除を保証
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockBasic {
  private int value = 0;
  private final Lock lock = new ReentrantLock();

  public static void main(String[] args) {
    final int TASK_NUM = 100000;
    var th = new Thread[TASK_NUM];
    var tb = new LockBasic();
    for (var i = 0; i < TASK_NUM; i++) {
      th[i] = new Thread(() -> {
        tb.increment();
      });
      th[i].start();
    }
    for (var i = 0; i < TASK_NUM; i++) {
      try {
        th[i].join();
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }

    System.out.println(tb.value);
  }


  void increment() {
    //ロック獲得
    lock.lock();
    try {
      this.value++;
    } finally {
      //ロック解除
      lock.unlock();
    }
  }
}

tryLockメソッド

  • ロック獲得可能か事前にチェックできる * ロック待ち不要ならばスキップ
  if (lock.tryLock(10,TimeUnit.SECONDS)) {
    try {
    //排他制御すべき処理
    } finally {
      lock.unlock();
    }
  }else{
  //ロック獲得できない場合の処理
}

ロックフリーな排他制御

  • 単一の変数に対する代入、加減算などをハードウェアレベルでアトミックに実現
    • cf: synchronizedjava.util.concurrent.locksはロックを前提とした排他制御
  • アトミック:途中割り込みがないことを保証されている状態
    • Javaではlong/double型以外の変数への読み書きはアトミックが保証
    • long/double型は必ずしもアトミックではないのでマルチスレッド環境で割り込みの可能性がある(データ破損)
  • AtomicIntegerクラスのgetAndIncrementメソッドを使うことで++演算子をアトミックに実施できる!
import java.util.concurrent.atomic.AtomicInteger;

public class AtomicBasic {
  private AtomicInteger value = new AtomicInteger();

  public static void main(String[] args) {
    final int TASK_NUM = 100000;
    var th = new Thread[TASK_NUM];
    var tb = new AtomicBasic();
    for (var i = 0; i < TASK_NUM; i++) {
      th[i] = new Thread(() -> {
        tb.increment();
      });
      th[i].start();
    }
    for (var i = 0; i < TASK_NUM; i++) {
      try {
        th[i].join();
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
    System.out.println(tb.value);
  }

  void increment() {
    value.getAndIncrement();
  }
}

スレッドプール

  • アプリが利用するスレッドをあらかじめ用意しプールしておくこと
  • プールしたスレッドは必要に応じて取り出し、使い終わったらプールに戻す
  • スレッドを再利用することで生成/破棄のオーバーヘッド節約!
  • Executorsクラス:スレッドプール生成
  • executeメソッド:指定の処理をスレッド経由で実行
  • shutdownメソッド
  • スレッドプールによりスレッド生成を意識する必要がなくなる!
public class ThreadPool implements Runnable {
  @Override
  public void run() {
    for (var i = 0; i < 30; i++){
      System.out.println(Thread.currentThread().getName() + ":" + i);
    }
  }
}
import java.util.concurrent.Executors;

public class ThreadPoolBasic {

  public static void main(String[] args) {
    //スレッドを10個準備したスレッドプール生成
    var es = Executors.newFixedThreadPool(10);
    //executeメソッドで呼び出して実行
    es.execute(new ThreadPool());
    es.execute(new ThreadPool());
    es.execute(new ThreadPool());
    es.shutdown();
  }
}

スケジュール実行

  • ScheduledThreadServiceオブジェクト
  • ExecutorsクラスのnewScheduledThreadPool/newSingleScheduledExecutorメソッドで生成
  • scheduleメソッド:指定時間経過後、一度だけ処理実行
  • scheduleAtFixedRate/scheduleWithFixedDelayメソッド:指定時間間隔でなんども処理実行
import java.time.LocalDateTime;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class ThreadSchedule {
  public static void main(String[] args) {
  //スレッドプールの準備
    var sche = Executors.newScheduledThreadPool(2);
  //スケジュール実行登録
    sche.scheduleAtFixedRate(() -> {
      System.out.println(LocalDateTime.now());
    }, 0, 5, TimeUnit.SECONDS);

    //スケジュール実行を待ってメインスレッドを休止
    try {
      Thread.sleep(10000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }

    //スレッドプールをシャットダウン
    sche.shutdown();
  }
}

スレッドの処理結果を受け取る

  • ExecutorService / ScheduledExecutorService + Callableインターフェース
  • *Callable *:戻り値型にT型を割り当てる
  • callメソッド:Callableインターフェースで実行すべきコード
  • submitメソッド:定義したCallableオブジェクトの処理を実行
    • 戻り値はFuture型:非同期処理の結果
    • getメソッド:実際の戻り値を取り出す
//別スレッドで乱数を求めミリ秒数分スレッド休止
//その値をメインスレッドで表示
import java.util.Random;
import java.util.concurrent.Callable;

//Callable<Integer> でInteger型を割り当てる
public class ThreadCallable implements Callable<Integer> {
  @Override
  //Callableインターフェースで実行すべきコード
  public Integer call() throws Exception {
    var rnd = new Random();
    var num = rnd.nextInt(1000);
    Thread.sleep(num);
    return num;
  }
}
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;

public class ThreadCallableBasic {

  public static void main(String[] args) {
  //スレッド実行
    var exe = Executors.newSingleThreadExecutor();
    var r1 = exe.submit(new ThreadCallable());
    var r2 = exe.submit(new ThreadCallable());
    var r3 = exe.submit(new ThreadCallable());

  //スレッド結果表示
    //submitメソッドの戻り値はFuture<Integer>
    try {
      System.out.println("r1: " + r1.get());
      System.out.println("r2: " + r2.get());
      System.out.println("r3: " + r3.get());
    } catch (InterruptedException | ExecutionException e) {
      e.printStackTrace();
    }
    exe.shutdown();
  }
}

スレッド処理の後処理

  • CompletableFutureクラス
  • 非同期処理結果を受けた後処理を簡潔に書ける
    • 非同期処理を直列に実行
    • 非同期処理を並列に実行
  • 実行すべき非同期処理をsupplyAsyncメソッドに渡す
//別スレッドで乱数を求めミリ秒数分スレッド休止
//その値を表示

import java.util.Random;
import java.util.concurrent.CompletableFuture;

public class FutureBasic {

  public static void main(String[] args) {
    //非同期処理実行
    CompletableFuture.supplyAsync(() -> {
      var r = new Random();
      var num = r.nextInt(1000);
      heavy(num);
      return num;
    })
      //完了後の処理
      .thenAcceptAsync((result) -> {
        System.out.println(result);
      });
    System.out.println("...任意の後処理...");
    heavy(7000);
  }
  //ダミー処理(重い)で指定時間のみ処理休止
  public static void heavy(int num) {
    try {
      Thread.sleep(num);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
}

成功/エラーの処理を振り分ける

  • whenCompleteAsyncメソッド
import java.util.Random;
import java.util.concurrent.CompletableFuture;

public class FutureComplete {

  public static void main(String[] args) {
    CompletableFuture.supplyAsync(() -> {
      var r = new Random();
      var num = r.nextInt(1000);
      heavy(num);
      return num;
    })
      .whenCompleteAsync((result, ex) -> {
        //成功
        if (ex == null) {
          System.out.println(result);
        } else {
        //失敗
          System.out.println(ex.getMessage());
        }
      });
    heavy(7000);
  }

  public static void heavy(int num) {
    try {
      Thread.sleep(num);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
}

複数の非同期処理を直列に実行

  • CompleteFutureの真価発揮
    • supplyAsync:Supplier(引数なし戻値あり)
      • 結果の値生成
    • thenApplyAsync:Function(引数あり戻値あり)
      • 値の加工、変換
    • thenAcceptAsync:Consumer(引数あり戻値なし)
      • 結果受け取り処理
import java.util.Random;
import java.util.concurrent.CompletableFuture;

public class FutureSeq {

  public static void main(String[] args) {
  //処理1(乱数生成)
    CompletableFuture.supplyAsync(() -> {
      var r = new Random();
      var num = r.nextInt(5000);
      heavy(2000);
      System.out.printf("処理1: %d\n", num);
      return num;
    })
  //処理2(乱数倍)
    .thenApplyAsync((data) -> {
      var result = data * 2;
      heavy(2000);
      System.out.printf("処理2: %d\n", result);
      return result;
    })
  //処理3(乱数さらに倍)
    .thenAcceptAsync((data) -> {
      var num = data * 2;
      heavy(2000);
      System.out.printf("処理3: %d\n", num);
    });
    heavy(7000);
  }

  public static void heavy(int num) {
    try {
      Thread.sleep(num);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Composite Pattern

再帰的にメソッドを呼び出す
Composite Patternは木構造を表現できる
(木構造になるのならCompositeを検討できる)

Design Pattarm MENU

以下のクラス構成で確認します

クラス 説明
interface
face
異なるオブジェクトをface型で扱う
sam.class faceを実装する
samList.class faceを実装する
Listを保持し、再帰呼出しする
user(Main.class) 動作を確認する

*user 他の開発者がこのパターンを利用する、という意味合いを含みます

下記にサンプルコードを記載します

interface_face
interface face{void print();}
sam.class
class sam implements face{
      public void print(){
      System.out.println("."+this);
}}
samList.class
class samList implements face{
      List   list = new ArrayList();
      void   add(face fc){list.add(fc);}
      public void print(){
             Iterator it = list.iterator();
             while(it.hasNext()){
                   face fit = (face) it.next();
                   System.out.print("/"+fit);
                   fit.print(); // fitが sam型 なら sam.class のprint()が呼ばれ、こちらは再帰しません
                                // fitが samList型 なら samListのprint()が再帰的に呼び出されます
             }
      }
}
user(Main.class)
public static void main(String[] args){
  samList  sam1 = new samList();
  samList  sam2 = new samList();
  samList  sam3 = new samList();
  sam2.add(new  samList());
  sam2.add(new  sam());
  sam3.add(new  samList());
  sam3.add(new  sam());
  sam1.add(sam2);
  sam1.add(sam3);
  sam1.print();
}}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

文字列に変数を混ぜる方法を言語ごとにまとめてみた

ゆるっと Advent Calender 2020 の初日に参加させていただきましたー。
よろしくお願いします。

初日にふさわしいかわかりませんが、ゆるゆる仕事してたら恥をかいた件を書きますね。

先日先輩にレビューしてもらったとき、文字列の中に変数を足しこむやり方を、+演算子を使ってやっていたら先輩に『ダサい』と一蹴されました。(*'▽')ガフッ

string.js
const word = "hogehoge";
const text = "彼はおもむろにこう言った。\n" + word + " 』、と。";
console.log(text);
彼はおもむろにこう言った。
『 hogehoge 』、と。

勉強不足でごめんなさい。( ;∀;)
あと、ユニク〇ばかり着ててごめんなさい。

反省したので自分の学んだことのある言語での書き方や名称をまとめたい思います。

Javascript, Node.js

Javascript および Node.js ではテンプレートリテラルという名称になっています。

文字列をグレイヴ・アクセント(`)で囲み、変数を入れるときはドル記号と波括弧でくくります。

templateLiterals.js
const word = "hogehoge";
const text = `彼はおもむろにこう言った。\n『 ${word} 』、と。`;
console.log(text);
彼はおもむろにこう言った。
『 hogehoge 』、と。

Python

Python ではフォーマット済み文字列リテラルという名称になっています。

f または F を先頭に書いてから文字列を書いていきます。
変数を入れるときは波括弧で区切ります。

formattedStringLiteral.py
word = "hogehoge";
text = f"彼はおもむろにこう言った。\n{word} 』、と。";
print(text);
彼はおもむろにこう言った。
『 hogehoge 』、と。

Java

ない。

強いて言うなら String.format() を使うとできる。

stringFormat.java
String word = "hogehoge";
String text = "彼はおもむろにこう言った。\n『 %s 』、と。";
String output = String.format(text, word);
System.out.println(output);
彼はおもむろにこう言った。
『 hogehoge 』、と。

C♯

C# では文字列補間という名称になっています。

$ を先頭に書いてから文字列を書いていきます。
変数を入れるときは波括弧で区切ります。

stringInterpolation.cs
string word = "hogehoge";
string text = $"彼はおもむろにこう言った。\n『 {word} 』、と。";
System.Console.WriteLine(text);
彼はおもむろにこう言った。
『 hogehoge 』、と。

おわりに

Java にはなかったもんなー( ゚Д゚)

知らなかったの私だけかもしれませんが、実践に勝る経験はありませんね。

この記事が誰かの助けになれば幸いです。|д゚)

自身のメモ用でもあるので、新しい言語を使うときには更新していきますー。

ではまた!(^^)/

参考にさせていただきましたm(_ _)m

MDN - テンプレートリテラル
Python documentation - フォーマット済み文字列リテラル
.NET documentation - 文字列補間

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Java】マルチスレッド処理

結論

  • プロセスとスレッドの違いは重要
  • マルチスレッドの意義は独立に動かし、かつ同じプロセス内のメモリ空間を共有することができること

スレッドとは

  • プログラムを実行する処理の最小単位
  • アプリはメインスレッドという単一スレッドで動く
  • しかし単一スレッドでは、ネットワーク通信時などで、アプリ利用者は次の操作を通信終了まで待たないといけなくなる
  • スレッドが複数ある(=マルチスレッド)と、
    ネットワーク通信はバックグラウンドで他のスレッドが実行し、通信中でもメインスレッドで処理を並行して実行することができる

プロセスとは

  • プログラムのインスタンスそのもの
  • プロセス起動にはCPUとメモリの割り当てが必要
  • プロセスは1つ以上のスレッドを持つ
    • 1対1..n(1以上のn)関係
  • UML表記では、プロセス側から見てスレッドは 1…n 個

 プロセス 1
   |
 スレッド 1..n

メモリ構成

  • プロセス:Main関数で動いているプログラム
  • プロセスという大きい箱のなかにスレッドがある
    • Main関数は1スレッドでうごいている
    • あるプロセスは1個以上のスレッドを持たないといけない(mainスレッド

1111_1.png

スレッドの特徴 - 独立・メモリ空間共有

  • スレッド同士はお互い独立して動く
    • 1個止まってもバックグラウンドで動くスレッドは独立して稼働
    • メインスレッドで通信処理をしていると通信して答えが返ってくる間止まってしまうので、
その間も処理させるために並列処理させる
  • マルチスレッドで3スレッド立ち上げたときに1プロセス内にある理由はメモリ空間を共有するため
    • Mainスレッド内に変数を定義したら全スレッドがアクセスできる
public class Main {
    String hoge = "hoge"; //この変数は共有される
    public static void main(String[] args) {
        new Main().run();
    }
    private void run(){
        while (true) {
            System.out.println("1"); //Mainスレッド
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("2");
        }
    }
}

スレッド生成・実行

  • Threadクラスを継承
  • Runnableインターフェース実装

Threadクラスを継承

  • Thread派生クラスでは絶対runメソッド(呼び出すエンドポイント)をオーバーライドする
  • Thread派生クラスのメソッド
    • currentThread:実行中スレッド取得
    • getId:スレッドID取得
    • isAlive:スレッド生存確認
    • join:スレッド終了するまでms待機
    • setPriority:優先順位設定
    • sleep:スレッド実行休止
public class MyThread extends Thread {
//スレッド実処理はThread派生クラス
  @Override
  public void run() {
    for (var i = 0; i < 30; i++) {
      //getNameでスレッド名取得
      System.out.println(this.getName() + ": " + i);
    }
  }
}
public class ThreadBasic {

  public static void main(String[] args) {
    //インスタンス化しスレッド生成
    var th1 = new MyThread();
    var th2 = new MyThread();
    var th3 = new MyThread();
    //startメソッドでスレッド開始
    th1.start();
    th2.start();
    th3.start();
  }
}

Runnableインターフェース実装

  • 継承ではなく実装(implements Runnable)
    • 関数型インターフェースである
  • スレッド名に直接アクセスできないのでThread.currentThread()静的メソッドで現スレッドを取得し、getNameメソッドにアクセス
  • インスタンス化にはThreadコンストラクターにRunnable実装クラスのインスタンスを渡す
public class MyRunner implements Runnable {
  //スレッド実処理
  @Override
  public void run() {
    for (var i = 0; i < 30; i++) {
      System.out.println(Thread.currentThread().getName() + ": " + i);
    }
  }
}
public class RunnableBasic {

  public static void main(String[] args) {
    //スレッド生成
    var th1 = new Thread(new MyRunner());
    var th2 = new Thread(new MyRunner());
    var th3 = new Thread(new MyRunner());
    //スレッド開始
    th1.start();
    th2.start();
    th3.start();
  }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JUnit単体テストの前処理と後処理の順番

はじめに

単体テストを実装する間が空くたびに、コンストラクタ、テストクラス前処理とテストクラス後処理、テストメソッド前処理、テストメソッド後処理の順番を忘れるので個人参照用に残しておく。

環境

  • JREシステム・ライブラリー[JavaSE-11]
  • JUnit 4.13

テスト対象

テストサンプル

スクリーンショット 2020-11-12 9.15.08.png

結果

スクリーンショット 2020-11-12 9.15.55.png

やってみるとあたりまえの結果ではあったが、とりあえず単体テスト実装時の「あれ…順番どうだったたけ?」が減りそうなのでメモとして残しておく。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Java】Stream API - ストリーム終端処理

ストリーム終端処理

  • ストリーム処理で加工/フィルタされた値を最終的に出力したり集計する
  • ストリームは終端処理をトリガーにまとめて処理される
  • 終端処理は省略不可
    • cf:中間処理は省略可能
  • 終端処理後にストリーム再利用は不可能(IllegalStateException)

要素を順に処理

  • forEachメソッド
  • System.out::printlnメソッド参照をそのまま渡す以外にラムダ式で渡すこともできる
import java.util.stream.Stream;
public class StreamForEach {
  public static void main(String[] args) {
    Stream.of("Munchkin", "Siamese", "Persian", "Tama")
    .forEach(v -> System.out.println(v));
  }
}
  • forEachメソッドは並列ストリームでは順序を保証しない!
  • →順番を守るにはforEachOrderedメソッドを使う!
import java.util.stream.Stream;
public class Main {
  public static void main(String[] args) {
    Stream.of("Munchkin", "Siamese", "Persian", "Tama")
    .parallel()
    .forEach(v -> System.out.println(v));
    //Persian Tama Siamese Munchkin 

    /*順番を守りたい場合
    .forEachOrdered(v -> System.out.println(v)); 
    Munchkin Siamese Persian Tama
    */
  }
}

最初の値を取得する

  • findFirstメソッド
  • ストリームがからである可能性もあるので、戻り値はOptional型
import java.util.stream.Stream;

public class Main {
  public static void main(String[] args) {

    var str = Stream.of("Munchkin", "Siamese", "Persian", "Scottish Fold", "Tama")
        .filter(s -> s.startsWith("S"))
        .findFirst();
      //orElseメソッドでnullの場合"-"に置き換える
      System.out.println(str.orElse("-")); //Siamese
  }
}
  • findAnyメソッドは並列ストリームで得られた最初の結果を返す*ので結果が変わることも
import java.util.stream.Stream;

public class Main {
  public static void main(String[] args) {

    var str = Stream.of("Munchkin", "Siamese", "Persian", "Scottish Fold", "Tama")
    .parallel()
    .filter(s -> s.startsWith("S"))
    .findAny();
     System.out.println(str.orElse("-")); //Scottish Fold
  }
}

値が特定の条件を満たすか判定

  • anyMatch/allMatch/noneMatchメソッド
//リスト内の値が全部0以上か確認
import java.util.stream.IntStream;

public class StreamMatch {

  public static void main(String[] args) {
    System.out.println(
        IntStream.of(1, 10, 5, -5, 12)
          .allMatch(v -> v >= 0)
      ); //false
  }
}

配列/コレクションに変換

  • toArrayメソッド
  • ストリーム処理結果を文字列配列として取得
import java.util.stream.Stream;

public class Main {
  public static void main(String[] args) {
    var list = Stream.of("Munchkin", "Siamese", "Persian", "Scottish Fold", "Tama")
        .filter(s -> s.startsWith("S"))
        .toArray();
    System.out.println(list[0]); //Siamese
  }
}
  • collectメソッドでコレクションに変換
  • ストリーム処理結果をリストに変換
    • toList:リストへの変換
    • toSet:セットへの変換
    • toMap:マップへの変換
    • toCollection:一般的なコレクションへの変換
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class StreamTrans2 {

  public static void main(String[] args) {
    var list = Stream.of("Munchkin", "Siamese", "Persian", "Scottish Fold", "Tama")
        .filter(s -> s.startsWith("S"))
        .collect(Collectors.toList());
    System.out.println(list);
  }
}
  • toMapメソッド
    • 重複する可能性がある場合、merge引数を設定すること(IllegalStateException)
//Personオブジェクト配列を名前:メールアドレスのマップ形式に変換
public class StreamCollectMap {

  public static void main(String[] args) {
    System.out.println(
        Stream.of(
          new Person("山田太郎", "tyamada@example.com"),
          new Person("鈴木花子", "hsuzuki@example.com"),
          new Person("井上三郎", "sinoue@example.com"),
          new Person("佐藤久美", "ksatou2@example.com"),
          new Person("山田太郎", "yamataro@example.com")
        ).collect(Collectors.toMap(
          //引数keyがマップのキーを表す
          Person::getName,
          //引数valueがマップのキーを表す
          Person::getEmail,
          //重複する可能性がある場合
          (s, a) -> s + "/" + a
          //重複時に値を上書き
          // (s, a) ->  a
        ))
      );
  }
}
public class Person {
  private String name;
  private String email;
  public Person(String name, String email) {
    this.name = name;
    this.email = email;
  }
  public String getName() {
    return name;
  }
  public String getEmail() {
    return email;
  }
}

最大、最小を求める

  • min.maxメソッド
  • 引数にComparator型
  • naturalOrderで自然順ソート
  • 戻り値はOptional型なので、値取り出しにはorElseメソッド
import java.util.Comparator;
import java.util.stream.Stream;

public class StreamMin {

  public static void main(String[] args) {
    var str = Stream.of("めばる", "さんま", "ひらめ", "いわし", "ほっけ")
        .min(Comparator.naturalOrder());
      System.out.println(str.orElse("")); //いわし
  }
}

要素個数を求める

  • countメソッド
//文字列長3より大きい文字列の個数を求める
import java.util.stream.Stream;

public class Main {
  public static void main(String[] args) {
    System.out.println(
    Stream.of("Munchkin", "Siamese", "Persian", "Scottish Fold", "Tama")
      .filter(s -> s.length() > 7)
      .count() //2
    );
  }
}

合計/平均値を求める

  • sum/averageメソッド
  • 基本型ストリームでのみ使用可能
  • averageメソッドの戻り値はOptionalDouble型
    • orElseメソッドでとり出す
//int配列合計、平均を求める
import java.util.stream.IntStream;

public class StreamSum {

  public static void main(String[] args) {
    var list = new int[] { 5, 1, 10, -3 };
    System.out.println(IntStream.of(list).sum()); //13
    System.out.println(IntStream.of(list).average().orElse(0)); //3.25
  }
}

ストリームの値を一個にまとめる

  • リダクション
    • max/min/countメソッドもリダクション
  • reduceメソッドでリダクションを汎用的に行う

引数1個の場合:

  • reduceメソッド(ラムダ式)が受け取る引数
    • 演算結果を格納するための変数(result)
    • 個々の要素を受け取るための変数(str)
//文字列ストリームをカンマで区切りでまとめる
import java.util.stream.Stream;

public class Main {
  public static void main(String[] args) {

    System.out.println(
      Stream.of("Munchkin", "Siamese", "Persian", "Scottish Fold", "Tama")
        .sorted()
        .reduce((result, str) -> {
          return result + "," + str;
        })
        .orElse("") //Munchkin,Persian,Scottish Fold,Siamese,Tama
    );
  }
}

引数2個の場合:

  • 初期値を受け取ることができる
  • 結果は非nullであるので非Optional
    • orElse経由なしでも値取得可能
  • 並列ストリームでは分散された分、初期値がラムダ式に適用されるので結果が変化
    • 並列化した分重複する初期値が可能性がある
import java.util.stream.Stream;
public class StreamReduce2 {
  public static void main(String[] args) {
    System.out.println(
      Stream.of("Munchkin", "Siamese", "Persian", "Scottish Fold", "Tama")
        .sorted()
        // .parallel()
        .reduce("Himalayan", (result, str) -> {
          return result + "," + str;
        })
    ); //Himalayan,Munchkin,Persian,Scottish Fold,Siamese,Tama
  }
}

引数3個の場合:

  • Stream要素型と最終結果型が異なる場合に使用
  • reduceで個々の要素を取得する際に演算結果(result)、個々の要素の変数(value)が異なる型でもOK
    • 引数result(Integer)に文字列valueを数値に変換したものを加算
import java.util.stream.Stream;

public class Main {
  public static void main(String[] args) {
    System.out.println(
      Stream.of("153", "211", "112", "350", "418", "208")
        .parallel()
        .reduce(0,
          //個々の要素を取得
          //演算結果をresultに格納、個々の要素をvalueが受け取る
          (result, value) -> {
            return result + Integer.parseInt(value); //文字列valueを数値に変換
          },
          //分散された結果をまとめる(並列ストリームのみ)
          (result1, result2) -> {
            return result1 + result2; //1452
          }
      )
    );
  }
}

ストリーム要素をコレクションにまとめる(1)

  • collectメソッド
  • 可変リダクション:コレクションやStringBuilderのように可変なコンテナ(入れ物)に値を蓄積して返す
    • cf: reduceはストリーム内要素をint/Stringのような単一型にまとめる
//与えられた文字列ストリームをソートしてリストに変換
import java.util.ArrayList;
import java.util.stream.Stream;

public class Main {
  public static void main(String[] args) {
    System.out.println(
      Stream.of("Munchkin", "Siamese", "Persian", "Scottish Fold", "Tama"
        .sorted()
        .collect(
          //式数でリダクション最初の値に値を格納するコンテナ生成
          ArrayList<String>::new,
          //引数はコンテナ、個々の要素
          (list, str) -> list.add(str),
          //並列ストリームの場合
          (list1, list2) -> list1.addAll(list2)
        )
    );
  }
}

ストリーム要素をコレクションにまとめる(2)

  • collectメソッドはオーバーロードを持つ
  • Collector.of静的メソッドで生成できる
  • コレクター属性
    • CONCURRENT:マルチスレッドでaccumulator実行可能
    • UNORDERED:要素の順序を保証しない
    • IDENTITY_FINISH:finisherを省略可能
import java.util.ArrayList;
//import java.util.Arrays;
import java.util.stream.Collector;
import java.util.stream.Stream;

public class Main {
  public static void main(String[] args) {
    System.out.println(
      Stream.of("Munchkin", "Siamese", "Persian", "Scottish Fold", "Tama")
        .sorted()
        .collect(
          Collector.of(
            ArrayList::new,
            ArrayList::add,
            (list1, list2) -> {
              list1.addAll(list2);
              return list1;
            },
            Collector.Characteristics.IDENTITY_FINISH
          )
        )
    );
  }
}

標準コレクター

  • Collectorsクラスにcollectメソッドで利用できるコレクターを生成するファクトリーメソッドがある
  • 典型的なリダックション処理を簡単に実装!
    • toList、toMapメソッドもファクトリーメソッドに含まれる

joiningメソッド

  • 文字列結合のコレクター生成
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Main {

  public static void main(String[] args) {
    System.out.println(
        Stream.of("Munchkin", "Siamese", "Persian", "Scottish Fold", "Tama")
          .sorted()
          .collect(Collectors.joining(",", "<", ">")) //<Munchkin,Persian,Scottish Fold,Siamese,Tama>
      );
  }
}

groupingByメソッド

  • 指定キーで値をグループ化するコレクター生成
//文字列を長さ別に分類
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Main {

  public static void main(String[] args) {
    System.out.println(
        Stream.of("Munchkin", "Siamese", "Persian", "Scottish Fold", "Tama")
          .sorted()
          .collect(
          //引数strで要素型を受け取りグループ化キーを生成
          //文字列単位でグループ化
            Collectors.groupingBy(str -> str.length()
          )) //{4=[Tama], 7=[Persian, Siamese], 8=[Munchkin], 13=[Scottish Fold]}
      );
  }
}
  • グループ化した結果を別のコレクターで処理
    • joiningに引数指定
public class Main {

  public static void main(String[] args) {

    System.out.println(
        Stream.of("Munchkin", "Siamese", "Persian", "Scottish Fold", "Tama")
          .sorted()
          .collect(
            Collectors.groupingBy(
              str -> str.length(),
              Collectors.joining("/") //{4=Tama, 7=Persian/Siamese, 8=Munchkin, 13=Scottish Fold}
          ))
      );
  }
}

partitioningByメソッド

  • true/falseに分割するシンプルなグループ化
//文字列が7文字以内か長いかで分ける
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Main {

  public static void main(String[] args) {

    System.out.println(
        Stream.of("Munchkin", "Siamese", "Persian", "Scottish Fold", "Tama")
          .sorted()
          .collect(
            Collectors.partitioningBy(
              str -> str.length() > 7
          ) //{false=[Persian, Siamese, Tama], true=[Munchkin, Scottish Fold]}
        )
      );
  }
}

collectingAndThenメソッド

  • コレクター実行後に終了処理実行
  • コレクターと後処理を連結
//toListメソッドでストリームをリスト化
//Collections::unmodifiableListで読み取り専用に変換

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

public class Main {

  public static void main(String[] args) {
    System.out.println(
        Stream.of("Munchkin", "Siamese", "Persian", "Scottish Fold", "Tama")
          .sorted()
          .collect(
            Collectors.collectingAndThen(
              Collectors.toList(),
              Collections::unmodifiableList
            )
          )
      );
  }
}

IntSummaryStatisticsクラス

  • 数値の基本的な統計情報を取得
  • collectメソッドの戻り値は(Int)SummaryStatisticsオブジェクト
  • ゲッターメソッドで統計情報取得
    • getAverage():平均値(値がない場合0)
    • getCount():要素個数
    • getMax():最大
    • getMin():最小
    • getSum():合計(値がない場合0)
import java.util.IntSummaryStatistics;
import java.util.stream.IntStream;

public class Main {

  public static void main(String[] args) {
    var summary = IntStream.of(5, 13, 7, 2, 30)
      .collect(
     //コンストラクタ、accept、combineDBをparseIntソッドの参照を渡す
        IntSummaryStatistics::new,
        IntSummaryStatistics::accept,
        IntSummaryStatistics::combine
      );
    System.out.println(summary.getMin()); //2
    System.out.println(summary.getSum()); //57
    System.out.println(summary.getAverage()); //11.4
  }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Java】Stream API - ストリーム中間処理

ストリーム中間処理

  • ストリームを流れる値を加工、フィルターする
  • 実行されるのは終端処理が呼ばれたタイミング

指定条件で値をフィルタ

  • filterメソッド
import java.util.stream.Stream;
public class Main {
  public static void main(String[] args) {
    Stream.of(
      "https://qiita.com/",
      "Neko",
      "CAT",
      "https://www.amazon.co.jp/",
      "https://ja.wikipedia.org/wiki/Neko_(%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2)"
    )
      .filter(s -> s.startsWith("https://"))
      .forEach(System.out::println);
/*
https://qiita.com/
https://www.amazon.co.jp/
https://ja.wikipedia.org/wiki/Neko_(%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2)
*/
  }
}

与えられた値を加工

  • mapメソッド
  • 文字列リストを文字列長リストに変換して出力
  • 生成された直後はString<String>で、mapメソッドを経たらString<Integer>になる
import java.util.stream.Stream;
public class Main {
  public static void main(String[] args) {

    Stream.of("Munchkin", "Siamese", "Persian", "Scottish Fold", "Tama")
    .map(s -> s.length())
    .forEach(System.out::println); //8 7 7 13 4
  }
}
  • 基本型ストリームを生成するmapToInt/mapToLong/mapToDoubleメソッド
  • String<Integer>ではなくIntStreamが生成される
  • ボクシングの負担減少
import java.util.stream.Stream;
public class Main {
  public static void main(String[] args) {
    Stream.of("Munchkin", "Siamese", "Persian", "Scottish Fold", "Tama")
    .mapToInt(s -> s.length())
    .forEach(System.out::println); //8 7 7 13 4
  }
}

与えられた要素を加工

  • flatMap/flatMapToXXメソッド
  • 変換結果をStream型で返すのがmapとの違い
  • 個々の要素で返したStreamが最終的に結合されて返る
//二次元配列listを一次元配列に変換
import java.util.Arrays;
public class Main {
  public static void main(String[] args) {

    var list = new String[][] {
      { "neko", "nekko", "nekkko" },
      { "inu", "innu" },
      { "tori", "torri" }
    };
    //flatMapで入れ子の配列を渡し、Arrays.streamメソッドでストリーム化
    //flatMapで連結することで一次元配列にフラット化
    Arrays.stream(list)
      .flatMap(v -> Arrays.stream(v))
      .forEach(System.out::println);
     // neko nekko nekkko inu innu tori torri
    }
}
  • ストリーム加工の際に入れ子で値を加工することも可能
//二次元配列をフラット化する際に頭文字を取り出す
import java.util.Arrays;
public class Main {
  public static void main(String[] args) {

    var list = new String[][] {
      { "neko", "nekko", "nekkko" },
      { "inu", "innu" },
      { "tori", "torri" }
    };

    Arrays.stream(list)
    .flatMap(v -> Arrays.stream(v).map(str -> str.substring(0, 1)))
    .forEach(System.out::println);
    //n n n i i t t
    }
}

要素を並び替える

  • sortedメソッド
  • 自然順序によるソート
import java.util.stream.Stream;
public class Main {
  public static void main(String[] args) {

    Stream.of("Munchkin", "Siamese", "Persian", "Scottish Fold", "Tama")
    .sorted()
    .forEach(System.out::println);
// Munchkin
// Persian
// Scottish Fold
// Siamese
// Tama
  }
}
  • 規則を設定したい場合、ラムダ式で設定
import java.util.stream.Stream;
import java.util.Comparator;

public class Main {
  public static void main(String[] args) {

    Stream.of("Munchkin", "Siamese", "Persian", "Scottish Fold", "Tama")
    .sorted((str1, str2) -> str1.length() - str2.length())
    .forEach(System.out::println);
    // Tama
    // Siamese
    // Persian
    // Munchkin
    // Scottish Fold

    /*Comparatorインターフェースで辞書逆順
     Stream.of("Munchkin", "Siamese", "Persian", "Scottish Fold", "Tama")
     .sorted(Comparator.reverseOrder())
     .forEach(System.out::println);
    */
  }
}

m~n番目の要素を取り出す

  • skip/limitメソッド
  • skipメソッド:m番目までの要素を切り捨てる
  • limitメソッド:n+1番目以降の要素を切り捨てる
//skipで4要素をスキップし、limitでそこから10個分の要素を取り出す
import java.util.stream.IntStream;
public class StreamLimit {
  public static void main(String[] args) {
    IntStream.range(1, 20)
      .skip(4)
      .limit(10)
      .forEach(System.out::println); //5,6,7,8,9,10,11,12,13,14
  }
}

先頭から条件をみたす間の値を除去

  • dropWhileメソッド
  • 条件式に合致してる値をスキップ
    • ストリームの先頭から負数を除去
    • あくまでも先頭から連続する値なので途中の負数は除去しない
    • →途中の場合はfilterで除去
import java.util.stream.IntStream;
public class StreamDrop {
  public static void main(String[] args) {
    IntStream.of(-2, -5, 0, 3, -1, 2)
      .dropWhile(i -> i < 0)
      .forEach(System.out::println); //0,3,-1,2
  }
}

先頭から条件を満たす間の値のみ取り出す

  • takeWhileメソッド
  • ストリームの先頭から負数であるものだけを処理する
    • あくまでも処理するのは先頭から連続する値
import java.util.stream.IntStream;
public class StreamTake {
  public static void main(String[] args) {
    IntStream.of(-2, -5, 0, 3, -1, 2)
      .takeWhile(i -> i < 0)
      .forEach(System.out::println); //-2,-5
  }
}

ストリームの途中状態を確認する

  • peekメソッド
  • 途中で任意の処理を挟むことができる
  • peekメソッドはストリームに影響を及ぼさない
//ソート前後でストリーム内容を出力
import java.util.stream.Stream;
public class StreamPeek {
  public static void main(String[] args) {
    Stream.of("さかな", "あか", "こだま", "きんもくせい")
      .peek(System.out::println)
      .sorted()
      .forEach(System.out::println);
  }
}

値の重複を除去

  • distinctメソッド
  • ストリームに含まれる値の重複を除去
  • 任意のクラスの特定フィールドをキーに重複除去できない
  • →filterメソッドを使う
import java.util.stream.Stream;
public class Main {
  public static void main(String[] args) {
    Stream.of("ねこ", "猫", "ねっこ", "ねこ", "ねこねこ")
      .distinct()
      .forEach(System.out::println);
    // ねこ
    // 猫
    // ねっこ
    // ねこねこ
  }
}
  • Personクラスでnameフィールドをキーに重複をチェック
import java.util.HashSet;
import java.util.stream.Stream;

public class StreamDistinctObj {

  public static void main(String[] args) {
    var set = new HashSet<String>();
    Stream.of(
        new Person("山田", 40),
        new Person("高野", 30),
        new Person("大川", 35),
        new Person("山田", 45)
      )
      //filterメソッドの中で値をHashSetに追加
      //HashSet.addメソッドは値を追加できなかった(重複)場合falseを返す

      .filter(p -> set.add(p.name))
      .forEach(System.out::println);
  }
}
public class Person {
  public String name;
  public int age;

  public Person(String name, int age) {
    this.name = name;
    this.age = age;
  }
  @Override
  public String toString() {
    return String.format("%s(%d歳)", this.name, this.age);
  }
}

基本型ストリームの変換

  • asLongStream/asDoubleStream/LongStreamで型変換
//IntStreamからDoubleStreamへの変換
import java.util.stream.IntStream;
public class Main {
  public static void main(String[] args) {
    IntStream.range(1, 5)
      .asDoubleStream()
      .forEach(System.out::println);
      //1.0 2.0 3.0 4.0
  }
}

基本型、参照型ストリームを相互変換

  • 基本型から参照型:boxedメソッド
  • 参照型から基本型:mapToObjメソッド
//IntStreamからStream<Integer>に変換
import java.util.stream.IntStream;
public class Main {
  public static void main(String[] args) {
    IntStream.range(1, 5)
      .boxed()
      .forEach(System.out::println);
      //1 2 3 4

    IntStream.range(1, 5)
      .mapToObj(Integer::valueOf)
      .forEach(System.out::println);
      //1 2 3 4
  }
}
//Stream<Integer>からIntStreamに変換
import java.util.stream.Stream;
public class StreamUnboxed {
  public static void main(String[] args) {
    Stream.of(1, 2, 3, 4)
      .mapToInt(i -> i) //アンボクシングでInteger→int
      .forEach(System.out::println); //1 2 3 4
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む