20200908のJavaに関する記事は14件です。

数当てゲーム(めも)

課題に出されたものがどうしてもできないから、今日はいくつか指定すべき条件を省いてやってみた。
ひとまず意味わからなかったエラーがぜんぶ消えて、動いて嬉しい:hotdog::grinning:

package kazuate;

public class kazuate {


    public static void main(String[] args) {
        //乱数を発生させる
        int Ran=new java.util.Random().nextInt(1000);

        //ゲーム開始文。
        System.out.println("数当てゲーム");
        System.out.println("3桁の整数を入力してください");

        //ゲームの上限回数を設定
        int Limit=5;

        //以下 for文で回しながら 3桁入力を受付、その都度の処理。

        for(int i=0;i<=Limit;i++) {
            int th = i + 2;

            //キーボード受付を設定
            int yourN=new java.util.Scanner(System.in).nextInt();

            //数字が0以下の場合もしくは1000以上の場合のエラーコードを設定
            if(yourN>1000||yourN<0) {
                throw new IllegalArgumentException("数字は100以上、999以下で設定してください");
            }

            //ゲーム上限回数に達した時の処理。最後に正解したときはおめでとう、それ以外は回答を表示しゲーム終了
            if(Limit==th) {
                if(Ran==yourN) {
                    System.out.println("最後の挑戦で成功しました!!おめでとう!ゲームを終了します");
                }else{
                    System.out.println("ゲーム終了。正解は"+Ran+"です");}
                break;}


            //乱数よりも入力受付した数字が大きかった時の処理
            else if(Ran<yourN) {
            System.out.println("かずが大きいです。もういちど入力してください");


            //乱数よりも入力受付した数字が小さかった時の処理
            }else if(Ran>yourN) {
            System.out.println("かずが小さいですもういちど入力してください");

            //回答が正解だった時の処理。正解までに挑戦した回数も表示する
            }else if(Ran==yourN) {
                System.out.println((i+1)+"回目で成功です!");
                break;
            }
        }
        }
        }



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

【SpringBoot】DataSourceProperties$DataSourceBeanCreationException

発生事象

簡単に動作検証のために、SpringBootを使用してWebアプリを作ろうと思ったら、ビルド時に失敗。
下記のようなエラーが出た。

Description:

Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.

Reason: Failed to determine a suitable driver class

Action:
Consider the following:
    If you want an embedded database (H2, HSQL or Derby), please put it on the classpath.
    If you have database settings to be loaded from a particular profile you may need to activate it (no profiles are currently active).

データソースの設定に失敗しているらしい?

対応方法

どうやら、後で使おうと思って記載していたMyBatisを記載していたことが原因だったみたいでした。
pom.xmlからMyBatisの定義を削除したら正常に起動しましたが、他にも以下のような解決方法があるみたいです。

1.EnableAutoConfigurationアノテーションをつける。

[プロジェクト名Application.java]ファイルに、アノテーションを追加します。

sample.java
@EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})
@SpringBootApplication
public class MyAppSampleAppApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyAppSampleApplication.class, args);
    }
}

2.素直にデータソースを設定する

application.properties
spring.datasource.url=jdbc:postgresql://localhost:5432/postgres
spring.datasource.username=postgres
spring.datasource.password=postgres
spring.datasource.driverClassName=org.postgresql.Driver

※ドライバーを予め落としておく必要があります。

補足:複数のデータソースを設定する方法

調べている中で、複数のデータベースを利用したい場合の記述を見つけたので、メモ。
2つのデータソースを指定する方法

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

【Java】printfで書式を指定して出力する%s,%d,%f【フォーマット指定子】

表示形式を指定する関数

printf()などの、最後にfがつく関数を使用する場合には、表示形式を指定して出力することが可能。

今まで使っていなかったが、いろいろ自分で使って試してみないことにはものにできないので、みながら試してみましょう。

それぞれの書式の意味

指定子 対応する型 説明
%s string 文字列を出力
%d int 整数を10進法で出力
%f float 実数を出力

使用例

public class MyApp {

    public static void main(String[] args) {
        int score = 50; // %dが対応する
        double height = 165.8; // %fが対応する
        String name = "mako"; // %sが対応する

        System.out.printf("name: %s, score: %d, height: %f\n", name, score, height);
        System.out.printf("name: %-10s, score: %10d, height: %5.2f\n", name, score, height);

        String s = String.format("name: %-10s, score: %-10d, height: %5.2f\n", name, score, height);
        System.out.println(s);
    }
}
実行結果
name: mako, score: 50, height: 165.800000
name: mako      , score:         50, height: 165.80
name: mako      , score: 50        , height: 165.80

アルファベットの意味を理解していればそれほど敬遠するものでもなかった

  1. %sのsはString
  2. %fのfはfloat(浮動小数点)
  3. %dのdはDecimal(10進法)

これを念頭に考えれば、それほど敬遠するような物でもなかったですね。ただ、printfの引数の書き方はちょっと初心者にはややこしく移る気がしますが、後ろに順番通りに変数名を引数として渡してあげればいいだけのこと。

落ち着いて2、3個自分で試してみるとすぐ理解できる。

表示桁を指定したり、右詰め左詰めも指定できる

%10s // 10桁分準備して右詰め
%-10s // 10桁分準備して左詰め
%10s // 10桁分準備して右詰め
%-10s // 10桁分準備して左詰め
%5.3f // 整数部分を5桁、少数部分を3桁で表示

最後の%5.3fについては、整数部分は指定してもあんまり意味がないような気がします。。。

public static void main(String[] args) {
  double f = 12345.12345;
    System.out.printf("%f\n", f);
    System.out.printf("%.3f\n", f);
    System.out.printf("%3.3f\n", f);
  }
}

実行結果
12345.123450
12345.123
12345.123

整数部分は指定した桁数よりも多い場合はそのまま表示される。
変化があるのは少数部分。

%.3fと%3.3fでは表示は同じ。

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

【Java】参照型である文字列におけるequalsと==の違い

参照場所の比較と値の比較

比較方法 内容
equals メモリ領域の比較
== 値の比較

プログラム例

public class MyApp {

    public static void main(String[] args) {

        String s1 = "ab"; // メモリ領域を格納
        String s2 = "ab"; // メモリ領域を格納(上と同じ領域)

        if(s1.equals(s2)) { // 内容が同じか比較
            System.out.println("same1 equals");
        }

        if(s1 == s2) { // 基本データ型の比較
            System.out.println("same2 ==");
        }
        // 文字列オブジェクトの生成(equalsメソッドを持つ)
        String ss1 = new String("ab"); // メモリ領域の格納
        String ss2 = new String("ab"); // メモリ領域の格納(上とは別の領域を作る)

        // 基本データ型の比較
        if(ss1 == ss2) { // メモリ領域の比較をしているのでfalseになるので実行されない
            System.out.println("same3 == オブジェクト");
        }
        // データの内容のの比較
        if(ss1.equals(ss2)) {
            System.out.println("same4 equals オブジェクト"); // メモリ領域は違うが値は同じなので実行される
        }
    }
}

実行例

same1 equals
same2 ==
same4 equals オブジェクト

文字列はクセがある

本来参照型である文字列には、変数には参照先のメモリ領域の場所が格納される。

そして、例ではabという同じ文字列をs1とs2に代入しているが、なんとs1にはメモリ領域の場所が入って、同じ文字列をs2に代入していると言うことで同じメモリ領域の場所が入ってしまうらしい。つまり

String s1 = "ab";
String s2 = s1;

と同じ意味?になる。

試しに実行してみると

same1 equals
same2 ==
same4 equals オブジェクト

参照場所は同じということはどちらかで参照先の値を帰るとどうなる?s1の値だけ変えても両方変わる?

String s1 = "ab";
String s2 = s1;
s2 = "cd";

にするとどうなるのか...わかりやすく

        System.out.println("s1: " + s1);
        System.out.println("s2: " + s2);

を追加して実行。

s1: ab
s2: cd
same4 equals オブジェクト

s2のみ変更され、s1には反映されていない。

そういえば、文字列は基本データ型と同じような振る舞いをするということもあり、int型と同じ挙動をするという。。。

値が同じうちは同じ参照先で、値の更新があった場合は新しいメモリ領域を作ると言うことですかね?

ややこしいですが、実行してみて改めて理解しました。

配列なら両方の値が更新されるんでしたね。復習復習!

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

【React.useRef】イベントリスナー内で最新のステートを参照できない時の対処法

はじめに

関数コンポーネントのイベントリスナー内で、State(ステート/状態)が正しく扱えない場合の対処方法です。

React.useState()のステートを、イベントリスナーのコールバック関数内で参照しようとすると、値が最新の状態になっていないことがあります。

うまくいかない例

  1. counterというStateを宣言
  2. ボタンをクリックするとcounter+1
  3. クリックに反応したイベントリスナーがfunc()を発火
  4. counterの値を出力
関数コンポーネント
// ①
const [counter, setCounter] = React.useState(0);

// ④
const func = () => {
  console.log(counter);
};

// ③
React.useEffect(() => {
  window.addEventListener("click", func);
}, []);

// ②
return (
  <button onClick={() => setCounter(counter + 1)}>
    ボタン
  </button>

この例では、④ console.log(counter)で出力される値は常に0です。
イベントリスナーの登録時点のcounterの値で固定されてしまい、ステートが更新されても出力は変わりません。

これでは何回クリックしたかわからない...。

useRef()を使う

React.useRef()を使うことで、常に最新の状態を参照することができます。

うまくいく例
const [counter, setCounter] = React.useState(0);

// 追加
const counterRef = useRef(); // refオブジェクトを作成
counterRef.current = counter; // refはcounterを参照する


const func = () => {
  // refを出力する
  console.log(counterRef.current);
};

// -----以下は変更なし-----
React.useEffect(() => {
  window.addEventListener("click", func);
}, []);

return (
  <button onClick={() => setCounter(counter + 1)}>
    ボタン
  </button>

コールバック内でcounterを使うかわりに、counterRef.currentを使用します。
このようにすることで、最新のステートを参照することができます。

まとめ

関数コンポーネントのイベントリスナー内で、最新のstateが参照できない場合は、コールバック内でStateを使うかわりに、Stateを参照するRefを使用します。

このようにすることで、最新のステートを参照することができます。

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

Spring Securityで独自の認証項目を追加する

概要

Spring Bootで認証を実装しようとする際に、Spring Securityを使うこともあると思います。
Spring Securityではログイン時に項目を設定すれば自動で認証する仕組みがあるのですが、基本的にはユーザ名とパスワードのセットで認証を行います。それ以外に認証用の項目を追加したい場合はどうすればいいのか、書いてみます。

前提など

実装サンプル

SecurityConfigに、authenticationProviderを追加します。authenticationProviderは後述の独自に実装したAuthenticationProviderImplを設定します。
また、configureGlobalにてauthenticationProviderを設定します。

SecurityConfig.java
  @Autowired
  private AuthenticationProviderImpl authenticationProvider;

  @Autowired
  public void configureGlobal(
    AuthenticationManagerBuilder auth,
    @Qualifier("userService") UserDetailsService userDetailsService,
    PasswordEncoder passwordEncoder) throws Exception {

    authenticationProvider.setUserDetailsService(userDetailsService);
    authenticationProvider.setPasswordEncoder(passwordEncoder);
    auth.eraseCredentials(true)
      .authenticationProvider(authenticationProvider);
  }

独自で実装するauthenticationProvider。テーブルにstatus列を追加して、activeではないユーザは認証NGとしています。

AuthenticationProviderImpl.java
@Component
public class AuthenticationProviderImpl extends DaoAuthenticationProvider {
  @Override
  protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
    super.additionalAuthenticationChecks(userDetails, authentication);
    User user = (User) userDetails;

    // 追加の条件
    if (!user.getStatus().equals("active")) {
      throw new AccountStatusNotActiveException("Status is not active");
    }
  }

  public static class AccountStatusNotActiveException extends AuthenticationException {
    public AccountStatusNotActiveException(String message) {
      super(message);
    }
  }

  @Override
  protected void doAfterPropertiesSet() {}
}

その他参考

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

Javaでじゃんけん

openjdk インストール

apt install -y openjdk-14-jdk

ソース

Janken.java
import java.util.Random;

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

        Random rand = new Random(Integer.valueOf(args[0]));
        int comA = rand.nextInt(3);
        int comB = rand.nextInt(3);
        String strA, strB;
        strA = "";
        strB = "";
        switch(comA){
            case 0 :
                strA = "グー";
                break;

            case 1 :
                strA = "チョキ";
                break;

            case 2 :
                strA = "パー";
                break;

            default:
                strA = "";
                break;
        }
        switch(comB){
            case 0 :
                strB = "グー";
                break;
            case 1 :
                strB = "チョキ";
                break;
            case 2 :
                strB = "パー";
                break;

            default:
                strB = "";
                break;
        }
        if(comA == comB){
            System.out.println(
                    "コンピュータA:" + strA
                    );
            System.out.println(
                    "コンピュータB:" + strB
                    );
            System.out.println(
                    "あいこ"
                    );
        }
        else if(((comA == 0) && (comB == 1)) || ((comA == 1) && (comB == 2)) || ((comA == 2) && (comB == 0))){
            System.out.println(
                    "コンピュータA:" + strA
                    );
            System.out.println(
                    "コンピュータB:" + strB
                    );
            System.out.println(
                    "コンピュータAの勝ち"
                    );
        }
        else{
            System.out.println(
                    "コンピュータA:" + strA
                    );
            System.out.println(
                    "コンピュータB:" + strB
                    );
            System.out.println(
                    "コンピュータAの負け"
                    );
        }
    }
}
# コンパイル
javac Janken.java
$ java Janken.java 
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 0 out of bounds for length 0
    at Janken.main(Janken.java:7)

$ java Janken.java 0
コンピュータA:グー
コンピュータB:チョキ
コンピュータAの勝ち

$ java Janken.java 1
コンピュータA:グー
コンピュータB:チョキ
コンピュータAの勝ち

$ java Janken.java 2
コンピュータA:チョキ
コンピュータB:グー
コンピュータAの負け
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【AndroidStudio】TextViewを任意のフォントに変更する方法【Java】

左画像から右画像へ

はじめに

フリーフォントを用意します。
FONT FREE(https://fontfree.me/)
フリーフォントケンサク(https://cute-freefont.flop.jp/)
他にも沢山ありますが、上記のサイトおすすめです。
今回はこちら「ロンドB」を使用します。使用したいフォントをダウンロードしてください。
image.png

プロジェクトを用意します

今回 TestFont という名前でプロジェクトを作成しました。
MainActivity.java と activity_main.xml があることを前提に進めていきます。

プロジェクト名や保存場所などは、ご自身のプロジェクトに合わせて変更してください。

image.png
image.png

assets フォルダを用意します

フォントファイルは asset フォルダに置く必要がありますが、最初はありませんので作成します。
AndroidStudio 画面左上の app の上で右クリック、New → Folder → Asset Folder を選択します。
image.png
Target Source Set が main になっていることを確認して Finish を押します。
image.png
assets フォルダが作成されました。
image.png
assets フォルダの上で右クリック、Show in Explorer を選択します。
(もちろんエクスプローラーを自身で展開していっても大丈夫です)
image.png
assets フォルダの中にダウウンロードしたフォントファイルを置きます。
image.png

AndroidStudio の assets フォルダを開いたときフォントファイルが追加されていれば準備完了です。
image.png

.xmlファイルにタグ付け

デフォルトから2行追加しました。
.java ファイルでタグを使用するので、 text="Hello World!" にタグをつけます。2行目です。
文字が小さくて見にくいので大きくしました。6行目です。

activity_main.xml
    <TextView
        android:id="@+id/text01"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        android:textSize="30sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

.javaファイルに導入

デフォルトがこちら

MainActivity.java
package test.com.testfont;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

onCreateメソッドの3,4,5行目が追加した行です。 変数(rondeB,text など)は適宜変更してください。
import は自動追加されると思います。
(自動追加されないときは Alt + Enter で追加されます)

MainActivity.java
package test.com.testfont;

import androidx.appcompat.app.AppCompatActivity;

import android.graphics.Typeface;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Typeface rondeB = Typeface.createFromAsset(getAssets(), "Ronde-B_square.otf");
        TextView text = findViewById(R.id.text01);
        text.setTypeface(rondeB);
    }
}

できあがったもの

参考

【Android Studio】カスタムフォントを使用する方法(Javaコード編)

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

Java学習のためにAndroidアプリを自作してみる

※記事の内容的に、はてなブログの方に移管を検討中。

どういうアプリにするか

長男が保育園のクラスで「おりこうちょきん」なるものをしていて、
それがたまると何かしらいいことがあるというのを楽しんでいるので、
子ども向けの、ポイントを管理するスタンプカードのようなアプリを自作してみることに。
そういうアプリは探せばいくらでもあるとは思いますが、学習が目的なので自作します。

環境 言語 アプリ名
AndroidStudio4.0.1 Java OrikouChokin(おりこうちょきん)

スタンプカードをどうデザインするか

とりあえずパッと思いついたのが、ImageButtonを格子状に並べて、
それをクリックするとボタンのイメージが置き換わってスタンプを押しているように見せる、というもの。

デフォルトのConstraintLayoutの中にTableLayoutを配置します。

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TableLayout
        android:layout_width="358dp"
        android:layout_height="569dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.493"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <TableRow
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <ImageButton
                android:id="@+id/imageButton1"
                android:layout_width="70dp"
                android:layout_height="70dp"
                android:background="@android:color/transparent"
                android:scaleType="centerInside"
                app:srcCompat="@drawable/kyouryu2" />

            <ImageButton
                android:id="@+id/imageButton2"
                android:layout_width="70dp"
                android:layout_height="70dp"
                android:background="@android:color/transparent"
                android:scaleType="centerInside"
                app:srcCompat="@drawable/kyouryu2" />

            <ImageButton
                android:id="@+id/imageButton3"
                android:layout_width="70dp"
                android:layout_height="70dp"
                android:background="@android:color/transparent"
                android:scaleType="centerInside"
                app:srcCompat="@drawable/kyouryu2" />

            <ImageButton
                android:id="@+id/imageButton4"
                android:layout_width="70dp"
                android:layout_height="70dp"
                android:background="@android:color/transparent"
                android:scaleType="centerInside"
                app:srcCompat="@drawable/kyouryu2" />

            <ImageButton
                android:id="@+id/imageButton5"
                android:layout_width="70dp"
                android:layout_height="70dp"
                android:background="@android:color/transparent"
                android:scaleType="centerInside"
                app:srcCompat="@drawable/kyouryu2" />
        </TableRow>

これで5個のボタンが水平に並びます。
とりあえず5x4くらいにするため、<TableRow></TableRow>のかたまりをあと4つ記述します。
すると以下のようになります。

ボタンのイメージは長男が書いたサイケデリックなトリケラトプスです。(ボタンイメージの設定は後述)
トリケラトプスをクリックするとサイケデリックなブラキオサウルスに変わるように実装します。

スタンプカードなのに最初から絵が入っているのは変なので後々それらしくしようと思いますが、
アプリ完成までの途中経過も長男が楽しんでくれたら…ということで。

ボタンのイメージ設定

上述のトリケラトプスなど、アプリ内で使用する画像はプロジェクトの以下の場所に配置します。
AndroidStudioProjects\OrikouChokin\app\src\main\res\drawable
image.png
ここに配置すると、ImageButtonsrcCompatプロパティに"@"を入力した際にAndroidStudioが補完してくれます。
image.png
これでボタンがトリケラトプスになりました。

大量のボタンにsetOnClickListenerする

(続きは明日書く)

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

Strategyパターン

Strategyパターンとは

どんなプログラムも問題を解くために書かれている。
そして問題を解くために特定のアルゴリズムが実装されている。
Strategyパターンでは、そのアルゴリズムを実装した部分が交換できるようになっている。
アルゴリズムを切り替え、同じ問題を別の方法で解くのを容易にするパターン。

Strategy(戦略)の役

戦略を利用するためのインターフェースを定める役。

package strategy;

public interface Strategy {
    // 次回出す手を決定する
    public abstract Hand nexHand();
    // 前回出した手で勝ったかどうかを学習する
    public abstract void study(boolean win);
}

ConcreteStrategy(具体的戦略)の役

Strategy役のインターフェースを実際に実装する役。
ここで具体的に戦略(作戦、方法、方策、アルゴリズム)を実際にプログラミングする。

package strategy;

import java.util.Random;

public class WinningStrategy implements Strategy{
    private Random random;
    private boolean won = false;
    private Hand prevHand;

    public WinningStrategy(int seed) {
        random = new Random(seed);
    }

    /**
     * 前回勝っていれば同じ手を出し、負けていればランダムで手を決定する
     */
    @Override
    public Hand nextHand() {
        if (!won) {
            prevHand = Hand.getHand(random.nextInt(3));
        }
        return prevHand;
    }

    @Override
    public void study(boolean win) {
        won = win;
    }
}
package strategy;

import java.util.Random;

public class ProbStrategy implements Strategy {
    private Random random;
    private int prevHandValue = 0;
    private int currentHandValue = 0;
    // 過去の勝敗を反映した確率計算のための表
    //[前回に出した手][今回出す手]
    private int[][] history = {
            {1,1,1,},
            {1,1,1,},
            {1,1,1,},
    };

    public ProbStrategy(int seed) {
        random = new Random(seed);
    }

    /**
     * historyの値から確率で計算を行う
     * 例えば、history[0][0]の値が3、history[0][1]の値が5、history[0][2]の値が7の場合
     * グー、チョキ、パーを出す割合を3:5:7として次の手を決定する。
     * 0以上15未満(15は3+5+7の合計値)の乱数値を得て、
     * 0以上3未満ならグー
     * 3以上8未満ならチョキ
     * 8以上15未満ならパー
     * とする
     */
    @Override
    public Hand nextHand() {
        int bet = random.nextInt(getSum(currentHandValue));
        int handValue = 0;
        if (bet < history[currentHandValue][0]) {
            handValue = 0;
        } else if (bet < history[currentHandValue][0] + history[currentHandValue][1]) {
            handValue = 1;
        } else {
            handValue = 2;
        }

        prevHandValue = currentHandValue;
        currentHandValue = handValue;
        return Hand.getHand(handValue);
    }

    private int getSum(int hv) {
        int sum = 0;
        for (int i = 0; i < 3; i++) {
            sum += history[hv][i];
        }
        return sum;
    }

    /**
     * nextHandメソッドで返した手の勝敗を元に、historyフィールドの内容を更新する
     *
     */
    @Override
    public void study(boolean win) {
        if (win) {
            history[prevHandValue][currentHandValue]++;
        } else {
            history[prevHandValue][(currentHandValue + 1) % 3]++;
            history[prevHandValue][(currentHandValue + 2) % 3]++;
        }
    }
}

Context(文脈)の役

Strategy役を利用する役。
ConcreateStrategy役のインスタンスを持っていて、必要に応じてそれを利用する。あくまで呼び出すのはStrategy役のインターフェース。

package strategy;

public class Player {
    private String name;
    private Strategy strategy;
    private int winCount;
    private int loseCount;
    private int gameCount;

    public Player(String name, Strategy strategy) {
        this.name = name;
        this.strategy = strategy;
    }

    public Hand nextHand() {
        // 実際に次の手を決定するのは自分の「戦略」
        return strategy.nextHand();
    }

    public void win() {
        // 戦略の内部状態を変化させる
        strategy.study(true);
        winCount++;
        gameCount++;
    }

    public void lose() {
        // 戦略の内部状態を変化させる
        strategy.study(false);
        loseCount++;
        gameCount++;
    }

    public void even() {
        gameCount++;
    }

    public String toString() {
        return "[" + name + ":" + gameCount + " games, " + winCount + " win, " +
    loseCount + " lose" + "]";
    }
}

呼び出し元

package strategy;

public class Main {
    public static void main(String[] args) {
        if (args.length != 2) {
            System.out.println("Usage: java Main randmeseed1 randomeseed2");
            System.out.println("Example: java Main 314 15");
            System.exit(0);
        }

        int seed1 = Integer.parseInt(args[0]);
        int seed2 = Integer.parseInt(args[1]);
        Player p1 = new Player("Jiro", new WinningStrategy(seed1));
        Player p2 = new Player("Taro", new ProbStrategy(seed2));
        for (int i = 0; i < 10000; i++) {
            Hand nexHand1 = p1.nextHand();
            Hand nexHand2 = p2.nextHand();
            if (nexHand1.isStrongerThan(nexHand2)) {
                System.out.println("Winner:" + p1);
                p1.win();
                p2.lose();
            } else if (nexHand2.isStrongerThan(nexHand1)) {
                System.out.println("Winner:" + p2);
                p1.lose();
                p2.win();
            } else {
                System.out.println("Even…");
                p1.even();
                p2.even();
            }
        }

        System.out.println("Total result:");
        System.out.println(p1);
        System.out.println(p2);

    }
}

実行結果

スクリーンショット 2020-09-08 13.43.45.png

https://github.com/aki0207/strategy

こちらを参考にさせていただきました。
増補改訂版Java言語で学ぶデザインパターン入門

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

AoneFlowは新しい自動化・ブランチ管理ツールを導入

AoneFlowはTrunkBasedとGitFlowの両方の長所を取り入れながら、高品質なソフトウェア製品をタイムリーにリリースするための新しい自動化ツールとブランチ管理ツールを導入しています。

本ブログは英語版からの翻訳です。オリジナルはこちらからご確認いただけます。一部機械翻訳を使用しております。翻訳の間違いがありましたら、ご指摘いただけると幸いです。

自動化と新しいブランチ管理プロセスは、ソフトウェア開発に革命をもたらしています。
image.png

TrunkBasedとGitFlowは、簡単な統合と管理可能な要件機能という点で、ほとんどの開発者のブランチ管理ニーズを満たすことができますが、アリババは、面倒な手動操作をせずに、より効率的なシステムと、改善されたブランチ管理ツールを必要としていました。これを受けて、アリババの技術チームは新しいブランチ管理モードAoneFlowを開発しました。AoneFlowはTrunkBasedとGitFlowの両方の長所を取り入れながら、新しい自動化とブランチ管理ツールを導入し、高品質のソフトウェア製品をタイムリーにリリースすることを保証しています。

ブランチ管理の基盤:TrunkBasedとGitFlow

AoneFlowは、ソフトウェア開発者が最もよく使うブランチ管理手法であるTrunkBasedとGitFlowの構造と機能を部分的にベースにしています。どちらのシステムも便利な機能を提供しており、既存のシステムをベースにしたAoneFlowは、ソフトウェア開発者にとって親しみやすいという利点があります。

TrunkBased

TrunkBasedの利点は継続的なインテグレーションです。その構造は、単一のトランクブランチと多数のリリースブランチで構成されています。各リリースブランチは、オンラインデプロイやHotfixのために、特定のバージョンのコミットポイントでトランクから作成されます。明示的な機能ブランチはありません。GitFlowでは各開発者がローカルブランチを持つことができるのに対し、TrunkBasedは、存続期間の短い機能ブランチを除外しません。しかし、この機能はTrunkBasedの開発者には広く使われていません。

TrunkBasedは、いくつかの欠点があるため、近年GitFlowに人気を落としています。まず、同じトランクでコーディングしているチームの数が多すぎると、リリース時にエラーが発生する可能性があります。FeatureToggleはその解決策として実装されましたが、頻繁な統合と適切なテストカバレッジが要求されるため、開発チームの能力に過度の負担がかかります。その結果、TrunkBased は、複数の過去のバージョンを同時に管理する必要がない SaaS プロジェクトで使用されることがほとんどです。これらのプロジェクトは、主にマイクロサービスの変革を経た小規模なサービスです。

TrunkBasedモードには2つの進化があります。OneFlowとMulti-Trunkです。OneFlow は TrunkBased と同じ機能を多く使用しますが、動作フローの定義がより厳密になり、Hotfix ブランチのような機能が追加されています。Multi-Trunk は、デュアルトランクと固定開発ブランチに加えて、固定リリースブランチを使用します。

GitFlow

GitFlow は TrunkBased とは構造が異なります。トランクブランチと多数の機能ブランチ、リリースブランチ、Hotfixブランチで構成されています。GitFlowは、各段階の操作定義が明確であることから、以前はプロセス指向の企業では最も利用されていました。しかし、GitFlowはユーザーフレンドリーではなく、面倒なマージルールが多数含まれています。マージの競合や統合テストの問題は、しばしば批判の的となっています。

あまり知られていないブランチ管理方法にGithubFlowがありますが、これは基本的にTrunkBasedに個人のウェアハウスとPull Requestコードのマージ操作を追加したものです。このプロセスは、同じ倉庫に個々のブランチを追加するのと似ているため、分散したチームに最適です。GithubFlowにも独自の進化があり、GitlabFlowは複数の環境でのデプロイを利用し、その環境内で倉庫とブランチを関連付けています。

新たな進化:AoneFlow

AoneFlowの構造、機能、プロセスは、いくつかの重要な分野でTrunkBasedやGitHubとは異なります。

前身と同様に、AoneFlowはシンプルな継続的インテグレーション(TrunkBased)と管理可能な要件機能(GitFlow)の実現を目指しています。また、トランクブランチ、機能ブランチ、リリースブランチという3つのブランチタイプの基本的な構造も同様に維持しています。

しかし、AoneFlowは、ブランチ管理のワークフローのための3つのコアルールを確立することで、前任者から出発します。

1、作業を始める前に、トランクから機能ブランチを作成します。
2、フィーチャーブランチを組み合わせてリリースブランチを形成します。
3、オンラインフォーマル環境にリリースした後、対応するリリースブランチをトランクにマージし、トランクにタグを追加し、リリースブランチに関連付けられた機能ブランチを削除します。

ルール1. フィーチャーブランチの作成

AoneFlowのfeatureブランチはGitFlowから採用されました。新しいジョブが開始されると(新しい関数を作成したり、エラーを解決したりなど)、最新のリリースバージョンを表すトランクから「feature/」という接頭辞を持つfeatureブランチが作成されます。コードの変更はこのブランチでコミットされます。各ジョブは1つのfeatureブランチに対応しており、以下の図のように、変更を直接トランクにコミットすることはできません。

image.png
機能ブランチの作成

ルール2. ブランチの形成を解除する

このルールはAoneFlowの最も革新的な成果です。

既存のブランチ管理方法では、GitFlowは完成した機能ブランチを共通のメインライン(開発ブランチ)に戻してマージし、共通のメインラインからリリースブランチを引っ張ってきます。TrunkBasedも同様に、必要な機能がトランクブランチ上で開発されるのを待ち、トランクブランチ上の特定の場所からリリースブランチを引っ張ってきます。

対照的に、AoneFlow はトランクから新しいブランチを引っ張ってきて、リリースや統合を待っているすべての機能ブランチをマージしてリリースブランチを作成します(通常は "release/"という接頭辞で示されます)。

これはシンプルでありながら、その用途は多岐にわたります。リリースブランチは柔軟に使用でき、アセンブリラインツールを使用して特定の環境に関連付けられます(例えば、リリース/テストブランチはデプロイメントテスト環境とペアになっています)。このツールは、コード品質スキャンと自動テストレベルを一連の多様な環境に接続します。 最後に、出力されたデプロイメントパッケージは、適切な環境に直接リリースされます。

image.png

リリースブランチの形成

より高度なレベルでは、リリースブランチは、段階的リリースと正式リリースを連鎖させるようなタスクのために、複数の環境に関連付けることができます。これにより、信頼性を確保するための人間による検査ステップが追加されます。 また、反復計画に従って機能ブランチを関連付け、反復進化によって固定リリースブランチを作成し、一連の環境をリリースブランチのアセンブリラインに紐付けることで、継続的な統合アセンブリラインを作成することもできます。 また、すべての機能ブランチを関連付けて、すべてのコミットで統合テストに専念させることで、TrunkBasedの効果を実装することもできます。

さらに、リリースブランチの機能の構成は動的で、簡単に調整できます。

例えば、「アジャイルオペレーション」を採用している小規模な企業では、市場戦略の変更により機能のリリースを断念したり、エラーのために土壇場で機能を削除しなければならないことがあります。通常、これには手動で「コードをチェックする」か、開発ブランチまたはトランクブランチに1つずつマージされた関連するコミットメントを削除する必要があります。

対照的に、AoneFlow では、リリースブランチを 1 分以内に構築することができます。これは、元のリリースブランチを削除し、トランクブランチから同じ名前の新しいリリースブランチを引っ張ってきて、保持する必要のある機能ブランチをマージすることで行われます。この一連の動作は自動化することができ、クリーンで汚染の心配がありません。

最後に、リリースブランチは疎結合されているため、異なる機能の組み合わせに基づいて複数の統合環境を統合してテストすることができます。また、異なる環境での機能のデプロイタイミングを管理するのにも便利です。疎結合であるにもかかわらず、リリースブランチ間には相関関係があります。 テスト環境、統合環境、プレリリース環境、段階的環境、オンラインフォーマル環境のリリースステップは、通常、順次実行されます。これは、ユーザーが要件を設定できることを意味し、前の環境で検証をパスした特性だけを次の環境に転送してデプロイすることができることを意味します。 その結果、漏斗状の機能リリースストリームが作成される。

さらに、アリババは、次項で説明するリリースブランチ間の機能の組み合わせを自動化して移行するための統一プラットフォームを作成しました。

ルール3. ブランチのオンライン展開をリリースする

リリースブランチのアセンブリラインがオンラインフォーマル環境のデプロイを完了すると、対応する機能がリリースされたことを意味します。この場合、リリースブランチはトランクにマージされなければなりません。コードウェアハウスに過去の機能ブランチが大量に蓄積されるのを避けるために、すでにオンラインになっている機能ブランチもクリーンアップする必要があります。

AoneFlow では、トランクブランチの最新バージョンは常にオンラインバージョンと同じです (GitFlow と似ています)。ユーザーが過去のバージョンをトレースバックしたい場合は、トランクブランチ上で対応するバージョンラベルを見つけるだけで済みます。

image.png

構造:ルール3

追加ルール

AoneFlowでは、ルール1-3と並んで、いくつかの実用的な操作が追加されています。例えば、Hotfix がオンラインになった場合、通常の処理方法ではオンライン環境に対応したリリースブランチ(Hotfix ブランチに相当)を新たに作成します。そして、リリースブランチ用の一時的な組立ラインを作成し、リリース前の検査やスモークテストを自動的に実行できるようにします。

AoneFlowでは、トランクブランチ上のバージョンラベルの場所を見つけ、その場所に直接Hotfixブランチを作成することができます。これにより、ユーザーはいくつかの重要な操作を効果的に行うことができます。

  • 対応するオンラインフォーマル環境に基づいて、リリースブランチの機能ブランチをクリアします。
  • リリースブランチを直接修正します。
  • 既存のアセンブリラインを使用した自動リリース

ユーザーエクスペリエンスの最適化

アリババは、ブランチ管理プロセスの最適化を一貫して模索しています。最近では、Java開発のベストプラクティスを記録し、Alibaba Java開発マニュアルにまとめました。これにより、チーム全体でJava開発を標準化し、作業の品質と効率を向上させることができました。

また、標準化によって、リリースブランチの再構築などのタスクベースの問題を解決することができました。リリースブランチの再構築には合併が必要で、その後、コードをコンパイルして新しいデプロイメントパッケージを生成する必要があります。しかし、ソフトウェアの機能がサードパーティ製のソフトウェアパッケージに依存している場合、ソフトウェアの動作に矛盾が生じる可能性があります。

解決策として、オンラインリリースのコードには、「SNAPSHOT バージョン」や非公式リリースを含む依存パッケージを使用してはならないというルールをコーディングガイドラインに追加しました。このガイドラインにより、私たちのチームは、制作する製品の品質をよりコントロールできるようになりました。

また、開発者の作業を容易にするためのツールを多数追加しました。例えば、AoneFlowプラットフォームでは、開発者が手動でGitコマンドを使ってブランチを作成したり、マージしたり、変更したりする必要がなくなりました。手動での作業はエラーが発生しやすく、時間がかかります。AoneFlowプラットフォームは、研究開発プロセス全体を制御し、研究開発の効率を高めるために多数のビルトインサービスコンポーネントを追加することで、これらの問題を否定します。このプラットフォームは、いくつかの重要な利点を提供します。

  • 完全なプロセス自動化
  • リリースブランチの改善
  • ブランチ関連管理の充実

フルプロセス自動化

AoneFlowプラットフォームは、要件の提案、要件のタスクへの分割、機能ブランチのオンライン作成、機能ブランチの集約によるリリースブランチの生成、テンプレートに基づいたテスト環境の自動作成、後期の運用・保守(O&M)などの機能のためのブランチ管理プロセスの自動化を導入しています。

フロントエンドでは、AoneFlow は機能ブランチの関連付けと監視要件を制御することで、機能ブランチの名前の正規化を保証します。バックエンドでは、リリースブランチの関連付けとデプロイメントを処理し、ソースバージョンの信頼性を確保します。全体的に、AoneFlow は Alibaba グループのオンラインデプロイメントの約 80% を処理しています。

改良されたリリースブランチの組み立てライン

AoneFlow のライフサイクルには多数のコードの分岐があります。これらのブランチの作成と更新には複雑なアクションが含まれています。このプロセスを自動化することで、品質と効率がヒューマンエラーによって失われることがないようにします。

AoneFlowは自動化されたCI/CDアセンブリーラインを使用して、コードのブランチを作成し、更新します。このアセンブリラインは、ライフサイクルの中でコードのすべての独立したブランチをリンクし、それらをより効率的に意図された機能(例えば、統合テストのためのコミットコード)に向けて方向付けるようにします。これは特にリリースブランチに当てはまり、特定のデプロイメント環境に関連付けることができます。ブランチにマージされた新しいコードをチェックし、より効率的にデプロイすることができます。

理想的なブランチ管理のシナリオでは、各ブランチはその役割にマッチしたアセンブリラインとペアになるべきです。AoneFlowのリリースブランチはGitFlowよりも比較的固定されており、統合が容易なため、利用可能なほとんどすべてのアセンブリラインツールがAoneFlowと互換性があります。

また、AoneFlowにはコードレビュー、セキュリティチェック、オンラインアセンブリラインのデプロイメントなどの追加機能も含まれており、開発チームのユーザーエクスペリエンスを向上させ、最適化を実現しています。

改良されたブランチアソシエーションのメンテナンス

フィーチャーブランチとリサーチブランチの関連付けのメンテナンスは、AoneFlow が対処する独自の問題です。

既存の機能の組み合わせに変更を加える際には、リリースブランチがどの機能ブランチから来たのかを記憶しておくことが重要です。例えば、ある機能が特定のリリースブランチから削除された場合、その機能が含まれていないブランチも含めて複数の機能ブランチがマージされ、元のリリースブランチに置き換わります。どのブランチがマージされたかを手動で記録するのは難しく、時間がかかります。

AoneFlowには、このプロセスを改善するいくつかの自動化機能が含まれています。特定の機能の組み合わせが低レベルのリリース環境(統合テストなど)で検証された場合、そのコンテンツを高レベル環境(プレリリースなど)の対応するリリースブランチに直接移行します。これにより、オンライン版がリリース前の検証に合格していること、プレリリース版が統合検証に合格していること、その他の重要なプロセスを確実に実行します。これにより、すべてのリリースブランチが一連の流れで結ばれます。この処理は通常のGitコマンドでも完了しますが、AoneFlowのビジュアルツールを使うとより直感的に処理を行うことができます。

また、AoneFlowでは、対応するブランチのデプロイ環境のマシン情報や運用記録など、コードウェアハウス内のブランチの状態を統一的に表示することができます。

概要

アリババのAoneFlowは、TrunkBasedとGitFlowの強みを活かしたブランチ管理の進化形です。

この新しいブランチ管理方法のメリットは以下の通りです。

  • TrunkBasedをモデルにした簡単な統合
  • GitFlowをモデルにした、管理しやすい要件機能
  • 効率と品質を確保するためのルールとプロセスを定義
  • 自動化された機能
  • リリースブランチ管理の改善
  • 改良されたブランチアソシエーションのメンテナンス

AoneFlowは、ソフトウェア開発者がダイナミックなブランチ管理のライフサイクルに適応し、効率的に作業を行い、高品質なソフトウェア製品をリリースできるようにする柔軟なプラットフォームを提供することで、ソフトウェア開発者が製品をよりコントロールできるようにします。

Alibaba Tech

アリババの最新技術のファーストハンド、詳細、詳細情報 → Facebookで「Alibaba Tech」を検索

アリババクラウドは日本に2つのデータセンターを有し、世界で60を超えるアベラビリティーゾーンを有するアジア太平洋地域No.1(2019ガートナー)のクラウドインフラ事業者です。
アリババクラウドの詳細は、こちらからご覧ください。
アリババクラウドジャパン公式ページ

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

Bridgeパターン

Bridgeパターンとは

「機能のクラス階層」と「実装のクラス階層」の橋渡しを行う。
wikipediaには、「橋渡し」のクラスを用意することによって、クラスを複数の方向に拡張させることを目的とすると書かれている。

Abstraction(抽象化)の役

「機能のクラス階層」の最上位のクラス。
Implementor役のメソッドを使って基本的な機能だけが記述されているクラス。
このクラスはImplementor役を保持する。

package bridge;

public class Display {
    private DisplayImpl impl;

    public Display(DisplayImpl impl) {
        this.impl = impl;
    }

    public void open() {
        impl.rawOpen();
    }

    public void print() {
        impl.rawPrint();
    }

    public void close() {
        impl.rawClose();
    }

    public final void display() {
        open();
        print();
        close();
    }
}

RefinedAbstraction(改善した抽象化)の役

Abstraction役に対して機能を追加した役。

package bridge;

public class CountDisplay extends Display {
    public CountDisplay(DisplayImpl impl) {
        super(impl);
    }

    public void multiDisplay(int times) {
        open();
        for (int i = 0; i < times; i++) {
            print();
        }
        close();
    }
}

Implementor(実装者)の役

「実装のクラス階層」の最上位のクラス。
Abstraction役のインターフェースを実装するためのメソッドを規定する役。

package bridge;

public abstract class DisplayImpl {
    public abstract void rawOpen();
    public abstract void rawPrint();
    public abstract void rawClose();
}

ConcreteImplementor(具体的な実装者)の役

具体的にImplementor役のインターフェースを実装する役。

package bridge;

public class StringDisplayImpl extends DisplayImpl {
    private String string;
    private int width;

    public StringDisplayImpl(String string) {
        this.string = string;
        this.width = string.getBytes().length;
    }

    public void rawOpen() {
        printLine();
    }

    public void rawPrint() {
        System.out.println("|" + string + "|");
    }

    public void rawClose() {
        printLine();
    }

    public void printLine() {
        System.out.print("+");
        for (int i = 0; i < width; i++) {
            System.out.print("-");
        }
        System.out.println("+");
    }
}

呼び出し元

package bridge;

public class Main {
    public static void main(String[] args) {
        Display d1 = new Display(new StringDisplayImpl("Hello Japan"));
        Display d2 = new CountDisplay(new StringDisplayImpl("Hello World"));
        CountDisplay d3 = new CountDisplay(new StringDisplayImpl("Hello Universe"));
        d1.display();
        d2.display();
        d3.display();
        d3.multiDisplay(5);
    }
}

実行結果

スクリーンショット 2020-09-07 18.58.23.png

以下のような模様を表示するクラスを追加する。
<>
<*>
<**>
<***>

これらは初めの文字→飾り文字が複数回→終わりの文字と改行を1行として、それが複数回繰り返される。

上のような動作をするクラスを追加する場合は、「機能」を表すクラスと「実装」を表すクラスに分離する。

だんだん回数を増やして表示するという「機能」を表すクラス

package bridge;

public class IncreaseDisplay extends CountDisplay {
    // 増加数
    private int step;

    public IncreaseDisplay(DisplayImpl impl, int step) {
        super(impl);
        this.step = step;
    }

    public void increaseDisplay(int level) {
        int count = 0;
        for (int i = 0; i < level; i++) {
            multiDisplay(count);
            count += step;
        }
    }
}

文字で表示するという「実装」を表すクラス

package bridge;

public class CharDisplayImpl extends DisplayImpl {
    private String firstLetter;
    private String decoration;
    private String lastLetter;

    public CharDisplayImpl(String firstLetter, String decoration, String lastLetter) {
        this.firstLetter = firstLetter;
        this.decoration = decoration;
        this.lastLetter = lastLetter;
    }

    @Override
    public void rawOpen() {
        System.out.print(firstLetter);
    }

    @Override
    public void rawPrint() {
        System.out.print(decoration);
    }

    @Override
    public void rawClose() {
        System.out.println(lastLetter);
    }
}

呼び出し元

package bridge;

public class Main {
    public static void main(String[] args) {
        Display d1 = new Display(new StringDisplayImpl("Hello Japan"));
        Display d2 = new CountDisplay(new StringDisplayImpl("Hello World"));
        CountDisplay d3 = new CountDisplay(new StringDisplayImpl("Hello Universe"));
        IncreaseDisplay d4 = new IncreaseDisplay(new CharDisplayImpl("<", "*", ">"), 2);
        d1.display();
        d2.display();
        d3.display();
        d3.multiDisplay(5);
        d4.increaseDisplay(3);
    }
}

実行結果

スクリーンショット 2020-09-08 11.38.58.png

https://github.com/aki0207/bridge

こちらを参考にさせていただきました。
増補改訂版Java言語で学ぶデザインパターン入門

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

AoneFlowは新しい自動化・ブランチ管理ツールを導入

AoneFlowはTrunkBasedとGitFlowの両方の長所を取り入れながら、高品質なソフトウェア製品をタイムリーにリリースするための新しい自動化ツールとブランチ管理ツールを導入しています。

本ブログは英語版からの翻訳です。オリジナルはこちらからご確認いただけます。一部機械翻訳を使用しております。翻訳の間違いがありましたら、ご指摘いただけると幸いです。

Javaでは、コレクションと配列は2つの一般的なデータ構造であり、追加、削除、変更、クエリ、集計、統計、フィルタリングなど、多くの操作が定期的に実行されます。これらの操作はリレーショナルデータベースにも存在します。しかし、Java 8以前では、コレクションや配列を処理するのはあまり便利ではありません。

この問題は、Java 8ではStream APIと呼ばれる新しい抽象化を導入することで大幅に緩和され、宣言的な方法でデータを処理することができるようになりました。この記事では、Streamの使い方を紹介します。ストリームの性能や原理は、この記事の中心ではないことに注意してください。

ストリーム紹介

ストリームは、SQL文と同様にデータベースからデータを問い合わせることで、Javaのコレクション操作や式の高レベルな抽象化を提供します。

ストリームAPIは、Javaプログラマの生産性を大幅に向上させ、効果的でクリーンで簡潔なコードを書くことを可能にします。

処理すべき要素の集合は、パイプラインで伝送されるストリームとみなされます。これらの要素は、フィルタ、ソート、集約など、パイプラインのノードで処理することができます。

Java ストリームの特徴と利点

  • ストレージはありません。ストリームはデータ構造ではなく、データソースのビューに過ぎません。
  • ストリームは本質的に機能的なものです。ストリームに変更を加えても、データソースは変更されません。例えば、ストリームをフィルタリングしても、フィルタリングされた要素は削除されませんが、フィルタリングされた要素を含まない新しいストリームが生成されます。
  • 遅延評価。ストリームに対する操作はすぐには実行されません。ユーザーが本当に結果を必要としているときにのみ実行されます。
  • 消費可能。ストリームの要素は、ストリームの寿命の間に一度だけ訪問されます。一度トラバースされると、コンテナのイテレータのように、ストリームは無効になります。再度Streamをトラバースしたい場合は、新しいStreamを再生成する必要があります。 例を使って、Streamが何をすることができるかを見てみましょう。

image.png

先の例では、いくつかのプラスチック製のボールをデータソースとして取得し、赤いボールをフィルタリングし、それらを溶かしてランダムな三角形に変換しています。別のフィルタは小さな三角形を除去します。減力剤は、円周を合計します。

前述の図に示すように、ストリームには、ストリーム作成、中間操作、端末操作の3つの重要な操作が含まれています。

ストリーム作成

Java 8では、多くのメソッドを使用してStreamを作成することができます。

1. 既存のコレクションを使ってストリームを作成する

Java 8では、多くのストリーム関連のクラスに加えて、コレクションクラス自体も強化されています。Java 8のStreamメソッドは、コレクションをStreamに変換することができます。

List<String> strings = Arrays.asList("Hollis", "HollisChuang", "hollis", "Hello", "HelloWorld", "Hollis");
Stream<String> stream = strings.stream();

前述の例では、既存のリストからストリームを作成しています。また、parallelStreamメソッドは、コレクションに対して並列ストリームを作成することができます。

また、コレクションからStreamを作成することもよくあります。

2. ストリーム メソッドを使用してストリームを作成する

Streamが提供するofメソッドは、指定された要素からなるStreamを直接返すために使用することができます。

Stream<String> stream = Stream.of("Hollis", "HollisChuang", "hollis", "Hello", "HelloWorld", "Hollis");

前述のコードでは、of メソッドを使用してストリームを作成し、それを返しています。

ストリームの中間操作

ストリームは多くの中間操作を持ち、それらを組み合わせてパイプラインを形成することができます。各中間操作はパイプライン上のワーカーのようなものです。各ワーカーはStreamを処理することができます。中間操作は新しいStreamを返します。

image.png

以下に、一般的な中間操作の一覧を示します。

image.png

filter
filter メソッドは、指定した条件で要素をフィルタリングするために使用されます。次のコードスニペットは、filter メソッドを使用して空の文字列をフィルタリングします。

List<String> strings = Arrays.asList("Hollis", "", "HollisChuang", "H", "hollis");
strings.stream().filter(string -> ! string.isEmpty()).forEach(System.out::println);
//Hollis, , HollisChuang, H, hollis

map
mapメソッドは、各要素を対応する結果にマッピングします。以下のコードスニペットは、対応する要素の平方数を生成するために map メソッドを使用しています。

List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
numbers.stream().map( i -> i*i).forEach(System.out::println);
//9,4,4,9,49,9,25

limit/skip
Limit は、Stream の最初の N 個の要素を返します。Skip は Stream の最初の N 個の要素を放棄します。次のコードスニペットは、最初の 4 つの要素を保持するために limit メソッドを使用しています。

List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
numbers.stream().limit(4).forEach(System.out::println);
//3,2,2,3

sorted
sorted メソッドは、Stream の要素をソートします。次のコードスニペットは、sorted メソッドを使用して Stream の要素をソートします。

List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
numbers.stream().sorted().forEach(System.out::println);
//2,2,3,3,3,5,7

distinct
重複を削除するには、distinct メソッドを使用します。次のコードスニペットは、distinct メソッドを使用して要素を重複排除します。

List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
numbers.stream().distinct().forEach(System.out::println);
//3,2,7,5

次に、filter, map, sort, limit, distinct の操作を行った後の Stream がどうなるかを例と図を使って説明します。

以下にコードを示します。

List<String> strings = Arrays.asList("Hollis", "HollisChuang", "hollis", "Hello", "HelloWorld", "Hollis");
Stream s = strings.stream().filter(string -> string.length()<= 6).map(String::length).sorted().limit(3)
            .distinct();

次の図は、各ステップとその結果を示しています。

image.png

ストリームターミナル事業

ストリームの端末操作もStreamを返します。ストリームを目的の型に変換するにはどうすればよいのでしょうか?例えば、Stream内の要素をカウントして、そのStreamをコレクションに変換します。これを行うには、ターミナル操作が必要です。

ターミナル操作はStreamを消費し、最終的な結果を生成します。つまり、あるストリームに対して端末操作が実行された後は、そのストリームは再利用できず、そのストリームに対していかなる中間操作も許されません。そうでなければ例外が投げられます。

java.lang.IllegalStateException: stream has already been operated upon or closed

これは、「同じ川に二度は踏み込めない」ということわざの意味と同じです。

以下の表は、一般的な端末操作を示しています。

image.png

forEach
forEach メソッドは、ストリーム内の要素を繰り返し処理します。次のコードスニペットは forEach を使用して 10 個の乱数を返します。

Random random = new Random();
random.ints().limit(10).forEach(System.out::println);

count
countメソッドは、Stream内の要素をカウントします。

List<String> strings = Arrays.asList("Hollis", "HollisChuang", "hollis","Hollis666", "Hello", "HelloWorld", "Hollis");
System.out.println(strings.stream().count());
//7

collect
コレクト操作は、様々なパラメータを受け入れ、ストリーム要素をサマリー結果に蓄積することができるリデュース操作です。

List<String> strings = Arrays.asList("Hollis", "HollisChuang", "hollis","Hollis666", "Hello", "HelloWorld", "Hollis");
strings  = strings.stream().filter(string -> string.startsWith("Hollis")).collect(Collectors.toList());
System.out.println(strings);
//Hollis, HollisChuang, Hollis666, Hollis

次に、前記の例で与えられたStreamに対する異なる端末操作の結果を示すために、フィルタ、マップ、ソート、リミット、および別個の操作が実行されたことを示す図を引き続き使用します。

次の図は、この記事で説明したすべての操作の入力と出力を示すために例を使用しています。

image.png

概要

この記事では、Java 8におけるストリームの使い方と特徴を解説します。また、この記事ではストリームの作成、ストリームの中間操作、端末操作についても解説しています。

ストリームの作成には、コレクションのストリームメソッドを使用する方法と、ストリームのofメソッドを使用する方法の2つの方法があります。

ストリームの中間演算は、ストリームを処理することができます。中間操作の入力と出力の両方がStreamです。中間操作には、フィルタ、マップ、ソートなどがあります。

ストリーム中間操作は、ストリーム内の要素をカウントしたり、ストリームをコレクションに変換したり、ストリーム内の要素を反復処理したりするなど、ストリームを何らかの他のコンテナに変換することができます。

アリババクラウドは日本に2つのデータセンターを有し、世界で60を超えるアベラビリティーゾーンを有するアジア太平洋地域No.1(2019ガートナー)のクラウドインフラ事業者です。
アリババクラウドの詳細は、こちらからご覧ください。
アリババクラウドジャパン公式ページ

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

Java 8からのストリームAPIを使ったデータ処理

このブログ記事では、Java 8からStream APIを使ってデータを宣言的に処理する方法を紹介しています。

本ブログは英語版からの翻訳です。オリジナルはこちらからご確認いただけます。一部機械翻訳を使用しております。翻訳の間違いがありましたら、ご指摘いただけると幸いです。

Javaでは、コレクションと配列は2つの一般的なデータ構造であり、追加、削除、変更、クエリ、集計、統計、フィルタリングなど、多くの操作が定期的に実行されます。これらの操作はリレーショナルデータベースにも存在します。しかし、Java 8以前では、コレクションや配列を処理するのはあまり便利ではありません。

この問題は、Java 8ではStream APIと呼ばれる新しい抽象化を導入することで大幅に緩和され、宣言的な方法でデータを処理することができるようになりました。この記事では、Streamの使い方を紹介します。ストリームの性能や原理は、この記事の中心ではないことに注意してください。

ストリーム紹介

ストリームは、SQL文と同様にデータベースからデータを問い合わせることで、Javaのコレクション操作や式の高レベルな抽象化を提供します。

ストリームAPIは、Javaプログラマの生産性を大幅に向上させ、効果的でクリーンで簡潔なコードを書くことを可能にします。

処理すべき要素の集合は、パイプラインで伝送されるストリームとみなされます。これらの要素は、フィルタ、ソート、集約など、パイプラインのノードで処理することができます。

Java ストリームの特徴と利点

  • ストレージはありません。ストリームはデータ構造ではなく、データソースのビューに過ぎません。
  • ストリームは本質的に機能的なものです。ストリームに変更を加えても、データソースは変更されません。例えば、ストリームをフィルタリングしても、フィルタリングされた要素は削除されませんが、フィルタリングされた要素を含まない新しいストリームが生成されます。
  • 遅延評価。ストリームに対する操作はすぐには実行されません。ユーザーが本当に結果を必要としているときにのみ実行されます。
  • 消費可能。ストリームの要素は、ストリームの寿命の間に一度だけ訪問されます。一度トラバースされると、コンテナのイテレータのように、ストリームは無効になります。再度Streamをトラバースしたい場合は、新しいStreamを再生成する必要があります。 例を使って、Streamが何をすることができるかを見てみましょう。

image.png

先の例では、いくつかのプラスチック製のボールをデータソースとして取得し、赤いボールをフィルタリングし、それらを溶かしてランダムな三角形に変換しています。別のフィルタは小さな三角形を除去します。減力剤は、円周を合計します。

前述の図に示すように、ストリームには、ストリーム作成、中間操作、端末操作の3つの重要な操作が含まれています。

ストリーム作成

Java 8では、多くのメソッドを使用してStreamを作成することができます。

1. 既存のコレクションを使ってストリームを作成する

Java 8では、多くのストリーム関連のクラスに加えて、コレクションクラス自体も強化されています。Java 8のStreamメソッドは、コレクションをStreamに変換することができます。

List<String> strings = Arrays.asList("Hollis", "HollisChuang", "hollis", "Hello", "HelloWorld", "Hollis");
Stream<String> stream = strings.stream();

前述の例では、既存のリストからストリームを作成しています。また、parallelStreamメソッドは、コレクションに対して並列ストリームを作成することができます。

また、コレクションからStreamを作成することもよくあります。

2. ストリーム メソッドを使用してストリームを作成する

Streamが提供するofメソッドは、指定された要素からなるStreamを直接返すために使用することができます。

Stream<String> stream = Stream.of("Hollis", "HollisChuang", "hollis", "Hello", "HelloWorld", "Hollis");

前述のコードでは、of メソッドを使用してストリームを作成し、それを返しています。

ストリームの中間操作

ストリームは多くの中間操作を持ち、それらを組み合わせてパイプラインを形成することができます。各中間操作はパイプライン上のワーカーのようなものです。各ワーカーはStreamを処理することができます。中間操作は新しいStreamを返します。

image.png

以下に、一般的な中間操作の一覧を示します。

image.png

filter
filter メソッドは、指定した条件で要素をフィルタリングするために使用されます。次のコードスニペットは、filter メソッドを使用して空の文字列をフィルタリングします。

List<String> strings = Arrays.asList("Hollis", "", "HollisChuang", "H", "hollis");
strings.stream().filter(string -> ! string.isEmpty()).forEach(System.out::println);
//Hollis, , HollisChuang, H, hollis

map
mapメソッドは、各要素を対応する結果にマッピングします。以下のコードスニペットは、対応する要素の平方数を生成するために map メソッドを使用しています。

List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
numbers.stream().map( i -> i*i).forEach(System.out::println);
//9,4,4,9,49,9,25

limit/skip
Limit は、Stream の最初の N 個の要素を返します。Skip は Stream の最初の N 個の要素を放棄します。次のコードスニペットは、最初の 4 つの要素を保持するために limit メソッドを使用しています。

List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
numbers.stream().limit(4).forEach(System.out::println);
//3,2,2,3

sorted
sorted メソッドは、Stream の要素をソートします。次のコードスニペットは、sorted メソッドを使用して Stream の要素をソートします。

List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
numbers.stream().sorted().forEach(System.out::println);
//2,2,3,3,3,5,7

distinct
重複を削除するには、distinct メソッドを使用します。次のコードスニペットは、distinct メソッドを使用して要素を重複排除します。

List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
numbers.stream().distinct().forEach(System.out::println);
//3,2,7,5

次に、filter, map, sort, limit, distinct の操作を行った後の Stream がどうなるかを例と図を使って説明します。

以下にコードを示します。

List<String> strings = Arrays.asList("Hollis", "HollisChuang", "hollis", "Hello", "HelloWorld", "Hollis");
Stream s = strings.stream().filter(string -> string.length()<= 6).map(String::length).sorted().limit(3)
            .distinct();

次の図は、各ステップとその結果を示しています。

image.png

ストリームターミナル事業

ストリームの端末操作もStreamを返します。ストリームを目的の型に変換するにはどうすればよいのでしょうか?例えば、Stream内の要素をカウントして、そのStreamをコレクションに変換します。これを行うには、ターミナル操作が必要です。

ターミナル操作はStreamを消費し、最終的な結果を生成します。つまり、あるストリームに対して端末操作が実行された後は、そのストリームは再利用できず、そのストリームに対していかなる中間操作も許されません。そうでなければ例外が投げられます。

java.lang.IllegalStateException: stream has already been operated upon or closed

これは、「同じ川に二度は踏み込めない」ということわざの意味と同じです。

以下の表は、一般的な端末操作を示しています。

image.png

forEach
forEach メソッドは、ストリーム内の要素を繰り返し処理します。次のコードスニペットは forEach を使用して 10 個の乱数を返します。

Random random = new Random();
random.ints().limit(10).forEach(System.out::println);

count
countメソッドは、Stream内の要素をカウントします。

List<String> strings = Arrays.asList("Hollis", "HollisChuang", "hollis","Hollis666", "Hello", "HelloWorld", "Hollis");
System.out.println(strings.stream().count());
//7

collect
コレクト操作は、様々なパラメータを受け入れ、ストリーム要素をサマリー結果に蓄積することができるリデュース操作です。

List<String> strings = Arrays.asList("Hollis", "HollisChuang", "hollis","Hollis666", "Hello", "HelloWorld", "Hollis");
strings  = strings.stream().filter(string -> string.startsWith("Hollis")).collect(Collectors.toList());
System.out.println(strings);
//Hollis, HollisChuang, Hollis666, Hollis

次に、前記の例で与えられたStreamに対する異なる端末操作の結果を示すために、フィルタ、マップ、ソート、リミット、および別個の操作が実行されたことを示す図を引き続き使用します。

次の図は、この記事で説明したすべての操作の入力と出力を示すために例を使用しています。

image.png

概要

この記事では、Java 8におけるストリームの使い方と特徴を解説します。また、この記事ではストリームの作成、ストリームの中間操作、端末操作についても解説しています。

ストリームの作成には、コレクションのストリームメソッドを使用する方法と、ストリームのofメソッドを使用する方法の2つの方法があります。

ストリームの中間演算は、ストリームを処理することができます。中間操作の入力と出力の両方がStreamです。中間操作には、フィルタ、マップ、ソートなどがあります。

ストリーム中間操作は、ストリーム内の要素をカウントしたり、ストリームをコレクションに変換したり、ストリーム内の要素を反復処理したりするなど、ストリームを何らかの他のコンテナに変換することができます。

アリババクラウドは日本に2つのデータセンターを有し、世界で60を超えるアベラビリティーゾーンを有するアジア太平洋地域No.1(2019ガートナー)のクラウドインフラ事業者です。
アリババクラウドの詳細は、こちらからご覧ください。
アリババクラウドジャパン公式ページ

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