20190704のJavaに関する記事は13件です。

キラッとプリ☆チャンのコーデ管理アプリを作ってみた話

前置き

Qiitaで記事投稿やってみた!という気持ちで大変恐縮ではあります。


本題

職業訓練校で学んだ程度ではあるのだが、Androidのアプリを作ることはできる。
下記の書籍を参考にしながら、タイトルのアプリを簡単ではあるけど、作った。

基礎&応用力をしっかり育成! Androidアプリ開発の教科書 なんちゃって開発者にならないための実践ハンズオン

とあるフォロワーの要望からすべてが始まった

勉強も兼ねて、先ほど紹介した書籍のサンプルプログラムをサクサクっと作った。
「勉強するわけだから、自分でも楽しく作れるものを作りたいな」という気持ちが出てくる。
最初は、アイカツ!キャラクターのような喋り口で、お天気予報とか、マストドンのインスタンス「キラキラッター」にアクセスするような、簡単なアプリを作ろうとしていた。

…で、言ってみた。すると。

ちゃんぷりのコーデ管理アプリ作って定期的にメンテしてくれたらたぶん首位いくレベルでDLされるよ?

とか言われちゃったので、本気にした。

どう作ったの、おじさん?

コーデ保存ってことは、SharedPreferenceかSQLite?
と思ったので、まずは書籍のサンプルプログラムでSQLiteを軽く理解して制作を始めようと思った。

イメージ画像としてはこんな感じ。メモ書きなので見づらいのはご容赦。
メモ書き。画像大きくてすまんやで…
実際は、画像内の「それか、こうか」以下の実装になりましたとさ。おわり。

ソースコード

なにかの参考になればと思ったので、一部抜粋なテイで載せてみた。

    // ListViewオブジェクトを取得
    ListView lvPrichan = findViewById(R.id.lv_prichan);

    // リストビューに表示するプリチャン稼働弾のリストオブジェクトを作成
    List<String> prichanCorde = new ArrayList<>();

    // リストデータの登録。プリチャン稼働弾が増えたとしても
    // 簡単に追記できるようにした
    prichanCorde.add("キラッとプリ☆チャン ジュエル1弾");
    prichanCorde.add("キラッとプリ☆チャン ジュエル2弾");
    prichanCorde.add("キラッとプリ☆チャン ジュエル3弾");

    // アダプターオブジェクトを作成
    ArrayAdapter<String> adapter = new ArrayAdapter<>(MainActivity.this,
            android.R.layout.simple_list_item_1,prichanCorde);
    lvPrichan.setAdapter(adapter);
    lvPrichan.setOnItemClickListener(new ListItemClickListener());
}

private class ListItemClickListener implements AdapterView.OnItemClickListener {

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        switch (position) {
            case 0:
                // ジュエル1弾のコーデリスト
                Intent priJ1intent = new Intent(MainActivity.this,PrichanJewel1.class);
                startActivity(priJ1intent);
                break;
            case 1:
                // ジュエル2弾のコーデリスト
                Intent priJ2intent = new Intent(MainActivity.this,PrichanJewel2.class);
                startActivity(priJ2intent);
                break;
            case 2:
                // ジュエル3弾のコーデリスト
                Toast.makeText(MainActivity.this,"まだないよ",Toast.LENGTH_SHORT).show();
                break;

上記はメイン画面処理。コーデ管理画面の処理は下記。

// 選択されたコーデの主キーIDを表すフィールド
int _prichanId = -1;
// 選択されたコーデ名を表すフィールド
String _prichanCordeName = "";

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_prichan_jewel1);
    // リストビューを取得
    ListView lvJprichan = findViewById(R.id.lv_J1prichan);
    // リスナーを登録
    lvJprichan.setOnItemClickListener(new ListItemClickListener());
    // 長押しリスナーを登録
    lvJprichan.setOnItemLongClickListener(new ListItemLongClickListener());
}

private class ListItemClickListener implements AdapterView.OnItemClickListener {

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        // タップされた行番号をフィールドの主キーIDに代入
        _prichanId = position;
        // タップされた行のデータを取得してフィールドに代入
        _prichanCordeName = (String) parent.getItemAtPosition(position);
        // データベースヘルパーオブジェクトを作成
        J1DatabaseHelper helper = new J1DatabaseHelper(PrichanJewel1.this);
        // データベース接続オブジェクトを作成
        SQLiteDatabase db = helper.getWritableDatabase();

        String cordeGet = "";
        try {
            String sql = "select * from J1prichancorde where _id = " + _prichanId;
            Cursor cursor = db.rawQuery(sql, null);
            if (cursor.moveToNext()) {
                int idxCorde = cursor.getColumnIndex("cordeget");
                cordeGet = cursor.getString(idxCorde);
            }
        }
        finally {
            db.close();
        }
        if (cordeGet.equals("true")) {
            // 所持データを保持されたことを表示
            Toast.makeText(PrichanJewel1.this,
                    "持っているコーデカードだよ。",
                    Toast.LENGTH_SHORT).show();
        }
        else {
            Toast.makeText(PrichanJewel1.this,
                    "持っていないコーデカードだよ。",
                    Toast.LENGTH_SHORT).show();
        }
    }
}

private class ListItemLongClickListener implements AdapterView.OnItemLongClickListener {

    @Override
    public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {

            // タップされた行番号をフィールドの主キーIDに代入
            _prichanId = position;
            // タップされた行のデータを取得してフィールドに代入
            _prichanCordeName = (String) parent.getItemAtPosition(position);
            // データベースヘルパーオブジェクトを作成
            J1DatabaseHelper helper = new J1DatabaseHelper(PrichanJewel1.this);
            // データベース接続オブジェクトを作成
            SQLiteDatabase db = helper.getWritableDatabase();
            try {
                // インサート文を用意
                String sqlInsert = "insert into J1prichancorde (_id, name, cordeget) values (?,?,?)";
                // SQL文字列を元にプリペアードステートメントを取得
                SQLiteStatement stmt = db.compileStatement(sqlInsert);
                // 変数のバインド
                stmt.bindLong(1, _prichanId);
                stmt.bindString(2, _prichanCordeName);
                stmt.bindString(3,"true");
                stmt.executeInsert();
            }
            finally {
                // データベースを閉じる
                db.close();
            }
            Toast.makeText(PrichanJewel1.this,
                    "登録完了!",Toast.LENGTH_SHORT).show();
        }
        // 通常のクリックイベントを発生させない
        return true;

最初、これで無事動くじゃろ~~~
と思ってたおじさんが馬鹿だった。

Insert処理の問題で、アプリが落ちるという出来事が起きた。

            // タップされた行番号をフィールドの主キーIDに代入
            _prichanId = position;
            // タップされた行のデータを取得してフィールドに代入
            _prichanCordeName = (String) parent.getItemAtPosition(position);
            // データベースヘルパーオブジェクトを作成
            J1DatabaseHelper checkhelper = new J1DatabaseHelper(PrichanJewel1.this);
            // データベース接続オブジェクトを作成
            SQLiteDatabase checkdb = checkhelper.getWritableDatabase();
            String cordeGet = "";
            try {
                String sql = "select * from J1prichancorde where _id = " + _prichanId;
                Cursor cursor = checkdb.rawQuery(sql, null);
                if (cursor.moveToNext()) {
                    int idxCorde = cursor.getColumnIndex("cordeget");
                    cordeGet = cursor.getString(idxCorde);
                }
            }
            finally {
                checkdb.close();
            }
            if (cordeGet.equals("true")) {
                Toast.makeText(PrichanJewel1.this,"既に登録しているから、登録できないよ!",
                        Toast.LENGTH_SHORT).show();
            }

既にInsertでデータベースに挿入してて、長押ししてしまった時とかあるじゃろ?
そういう時に
「既に入ってるからInsertはさせんよ。じゃあの。(エラーで終了)」
となるので、こうした。

ここまで作るのにおじさんは一日かかりました。

で、使ってもらった。

十分管理できる!
と嬉しい言葉をいただきましたァん♪
また、こんな言葉も頂いた。

リストでチェックマーク選べて最終確定できて中で保持できてたらそれだけでも違う!

この実装に関しては、調査が必要だが、できないことでもないだろう。と思っているので
やってみようと思う。

以上です。

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

StreamAPIの細かい挙動 ステートフルな中間操作と短絡終端操作

StreamAPIの細かい挙動 ステートフルな中間操作と終端操作

始めに問題です。

以下のコードのコンソール出力はどうなるでしょうか。

//問1
        Stream.of(1, 2, 3, 4, 5).map(i -> {
            System.out.println(i);
            return i * 2;
        }).filter(i -> i > 3).findFirst();
//問2
        Stream.of(1, 2, 3, 4, 5).peek(System.out::println).map(i -> {
            return i * 2;
        }).filter(i -> i > 3).findFirst();
//問3
        Stream.of(1, 2, 3, 4, 5).peek(System.out::println).map(i -> {
            return i * 2;
        }).filter(i -> i > 3).sorted().findFirst();
//問4
        Stream.of(1, 2, 3, 4, 5).peek(System.out::println).map(i -> {
            return i * 2;
        }).filter(i -> i > 3).distinct().findFirst();

答え

//問1
1
2
//問2
1
2
//問3
1
2
3
4
5
//問4
1
2

解説

問1

javaのStreamの処理は、「生成」「中間操作」「終端操作」に分けられます。
基本的に、生成がStreamを作成している部分、終端操作が最終的に結果を返すメソッド、
中間操作がそれ以外のStreamからStreamを返す部分と考えればOKです。

実際の処理は終端操作が実行されたタイミングで実行されます。
そして基本的にStreamに流れる各要素に対し終端操作まで実行します。
すべての要素にmapをかけてから、filterをかけるわけではありません。
(例えば、javascriptの配列で似たようなコードを書いた場合と挙動が異なります。)

そして、今回終端操作として採用しているFindFirstは終端操作の中でも短絡終端操作と呼ばれるものです。
「短絡」のつく操作は、条件さえ満たせばStreamで流れてくるすべての要素が評価される前でも処理を終了します。
(短絡操作が条件を満たせば、無限に値を返すStream(hasNextが常にtrueを返すiteratorから作ったStreamなど)に対しても無限ループに陥らせずに処理を終えることが出来ます。)

forやwhileのループで、一つでも条件を満たしたらbreakして残りは無視する処理はよくありますが、それを自動でやってくれるわけです。中々賢い。

今回のケースでは、2が流れてきた時点でfindFirstが値を返すことが出来るので、1、2まででStream処理が中断されます。

問2

基本的には問1と同じです。
5つの要素が流れてくるStreamの生成の直後にpeekを置くと、5回実行されそうなものですが、
末尾が短絡終端処理のためStreamの処理は2までで止まります。

問3

短絡終端処理を行っていてもすべてのStreamに流れる要素が評価されるケースがあります。
「ステートフルな中間処理」を間に挟んだ場合です。
この場合だとsortedがステートフルな中間処理です。
ソートした上で最初に条件を満たすものを取得するという処理なので、
論理的にもすべての要素を参照する必要があるのは当然ですね。

ちなみに、各中間処理がステートフルなのかどうかは公式リファレンスに書いてあります。

問4

ちなみに、ステートフルな中間操作を挟むと常にすべての要素を参照する必要があるかというとそういうわけではありません。
distinctもステートフルな中間操作ですが、これも1,2で処理が完了します。
というより、実際通常の順序ありStreamで全要素が常に参照されるのはsortedを使ったときくらいです。

まとめ

  • Streamは各要素ごとに終端操作まで処理される
  • 短絡終端処理を使う場合、Streamがよろしく処理を最適化してくれる
  • ステートフルな中間操作(sorted)の場合は例外があるので注意

余談

ParallelStreamの場合はちょっと挙動が異なりますが、
長くなるのでこの辺りで。

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

自己署名証明書を受け付けるRestTemplate

概要

Springフレームワークで提供されているRestTemplateで、自己署名証明書(オレオレ証明書)を許容する。

環境

  • Java 8
  • SpringBoot 2.1.3.RELEASE
  • Spring 5.1.5.RELEASE
  • Apache HttpClient 4.5.7

Bean定義

RestTemplateのBean定義
import org.apache.http.client.HttpClient;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.client.RestTemplate;

import javax.net.ssl.SSLContext;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;

@Configuration
public class RestTemplateBeanConfig {

    @Bean
    RestTemplate restTemplateIgnoreSelfSignedCertification() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
        SSLContext sslContext = SSLContexts.custom()
            .loadTrustMaterial(new TrustSelfSignedStrategy())
            .build();
        HttpClient httpClient = HttpClients.custom()
            .setSSLContext(sslContext)
            .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)
            .build();
        HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
        requestFactory.setHttpClient(httpClient);
        return new RestTemplateBuilder()
            .messageConverters(new MappingJackson2HttpMessageConverter())
            .requestFactory(() -> requestFactory)
            .build();
    }

}

参考

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

へっぽこJavaプログラマがkotlinに挑戦する #2 基礎編①

変数(val)

Javaの変数といえば

String name = "abe";
int i = 0;
Boolean ret = false;

こんな感じですが、これをkotlinで書くとこうなります。

val name : String = "abe"
val i : Int = 0
val ret : Boolean = false

Printlnしてみると、Javaとkotlinで同じ結果が出ることが確認できます。

abe
0
false

なんだかkotlinの方が助長な書き方で面倒だな、と思ったのですが、
kotlinには型推論の機能が備わっているため、以下のように短く記述可能なようです。

val name = "abe"
val i = 0
val ret = false

結果は同じになります。
これなら、Javaから型の指定を無くしたようなシンプルな書き方で良いですね。
勿論、明示的に型を書くことも可能なので、使い分けになるのでしょうか。

文字ではchar型が、数値では基本型が、など型には色々ありますが、
業務で使うのは今までの経験上、Int,Doubleくらいだったので、
他はまぁ流し見で良いかな、くらいの感覚です。(使う時に、あらためて確認)

変数(var)

ここまで変数を「val」で定義してきましたが、
実はこいつ、途中で書き換えられません。
上記変数nameを途中で「tanaka」にしようとしても、コンパイルが通りません。

途中で書き換えたい場合は「var」を使います。

var name = "abe"
name = "tanaka"
println(name)
tanaka

どっちがどっちか覚えづらいなーと思ったのですが、公式によると
valは「value(値)」
varは「variable(変数)」
で、割とそのまんまの意味の略語のようです。

「;」がいらない

ここまで書いてきて、非常に楽だったのが、
kotlinにおいては、Javaでは変数宣言などで文末に必ず必要だった「;」が不要になっています。

JVM系言語の中でもよりJavaをスマートに書けるようにしているkotlinには、
このようなJavaのめんどくさいところを楽にしてくれる箇所が色々あるようです。
print文でSystem.outのようなライブラリ・クラス指定がいらないのもその1つですね。

文字連結

JavaでString型の文字を連結する時は、以下のような感じでやっていました。
(性能面への影響次第で使い分け)

int avg = 300;
int hr = 30;

// +で結合
System.out.println("今日時点での成績は打率." + avg + "、本塁打" + hr + "です。");

// StringBuilderで結合
StringBuilder sb = new StringBuilder();
sb.append("今日時点での成績は打率.");
sb.append(avg);
sb.append("、本塁打");
sb.append(hr);
sb.append("です。");
System.out.println(sb.toString());
今日時点での成績は打率.300、本塁打30です。
今日時点での成績は打率.300、本塁打30です。

これをkotlinでは、JDBCのSQLのバインド変数のように、埋め込み文字列にできます。

val avg = 300
val hr = 30
println("今日時点での成績は打率.${avg}、本塁打${hr}です。")
今日時点での成績は打率.300、本塁打30です。

可読性も上がって良い感じですね。
また、変数のみを出力したい場合は、波括弧{}は省略できるようです。

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

Javaプログラマがkotlinに挑戦する #2 基礎編①

変数(val)

Javaの変数といえば

String name = "abe";
int i = 0;
Boolean ret = false;

こんな感じですが、これをkotlinで書くとこうなります。

val name : String = "abe"
val i : Int = 0
val ret : Boolean = false

Printlnしてみると、Javaとkotlinで同じ結果が出ることが確認できます。

abe
0
false

なんだかkotlinの方が助長な書き方で面倒だな、と思ったのですが、
kotlinには型推論の機能が備わっているため、以下のように短く記述可能なようです。

val name = "abe"
val i = 0
val ret = false

結果は同じになります。
これなら、Javaから型の指定を無くしたようなシンプルな書き方で良いですね。
勿論、明示的に型を書くことも可能なので、使い分けになるのでしょうか。

文字ではchar型が、数値では基本型が、など型には色々ありますが、
業務で使うのは今までの経験上、Int,Doubleくらいだったので、
他はまぁ流し見で良いかな、くらいの感覚です。(使う時に、あらためて確認)

変数(var)

ここまで変数を「val」で定義してきましたが、
実はこいつ、途中で書き換えられません。
上記変数nameを途中で「tanaka」にしようとしても、コンパイルが通りません。

途中で書き換えたい場合は「var」を使います。

var name = "abe"
name = "tanaka"
println(name)
tanaka

どっちがどっちか覚えづらいなーと思ったのですが、公式によると
valは「value(値)」
varは「variable(変数)」
で、割とそのまんまの意味の略語のようです。

「;」がいらない

ここまで書いてきて、非常に楽だったのが、
kotlinにおいては、Javaでは変数宣言などで文末に必ず必要だった「;」が不要になっています。

JVM系言語の中でもよりJavaをスマートに書けるようにしているkotlinには、
このようなJavaのめんどくさいところを楽にしてくれる箇所が色々あるようです。
print文でSystem.outのようなライブラリ・クラス指定がいらないのもその1つですね。

文字連結

JavaでString型の文字を連結する時は、以下のような感じでやっていました。
(性能面への影響次第で使い分け)

int avg = 300;
int hr = 30;

// +で結合
System.out.println("今日時点での成績は打率." + avg + "、本塁打" + hr + "です。");

// StringBuilderで結合
StringBuilder sb = new StringBuilder();
sb.append("今日時点での成績は打率.");
sb.append(avg);
sb.append("、本塁打");
sb.append(hr);
sb.append("です。");
System.out.println(sb.toString());
今日時点での成績は打率.300、本塁打30です。
今日時点での成績は打率.300、本塁打30です。

これをkotlinでは、JDBCのSQLのバインド変数のように、埋め込み文字列にできます。

val avg = 300
val hr = 30
println("今日時点での成績は打率.${avg}、本塁打${hr}です。")
今日時点での成績は打率.300、本塁打30です。

可読性も上がって良い感じですね。
また、変数のみを出力したい場合は、波括弧{}は省略できるようです。

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

java setter getterのエラー解決

目次

  • setterとgetterのコンパイルエラーで1週間ハマった話
  • 前提環境
  • 文法
  • 機能
  • 具体的な動作内容
  • 今回発生したエラー
  • 原因
  • 参考書籍、HP

getterとsetterのコンパイルエラーで1週間ハマった話

 この記事を書くにあたってその理由となります。書き方だけ知りたい方はこの項目はスルー推奨。

 現在Java言語を学習中で、プログラミング自体が初学者の者です。まずは動くものをつくる!という精神で変数はすべてpublicで記述し、オブジェクト指向は後回しにしました。
 今回きっかけがあり、オブジェクト指向プログラミングでコーディングしていくこととなったのですが、setterとgetterの部分でエラーにハマり、解決までに1週間費やしました。

 setterとgetterで調べても出てくるのは必要派といらない派の話ばかりで、あとは文法とか機能とかで自分のエラーについて説明しているものはなく、とても悩んだので、こうして記事を書いています。
 エラー内容については後述。

前提環境

 言語 Java-12.0.1
 テキストエディタで記述

文法

 改めて、まずはsetterとgetterの書き方から。

SetGet.java
// 変数宣言用クラス
class SetGet{

  // privateで変数宣言
  private  変数名;

  // getter
  public  変数名{
    return this.変数名;
  }

  // setter
  public void 変数名( 適当な変数名){
    this.変数名 = 適当な変数名;
  }

}

 次に実行用クラスでの書き方

SetgetRunner.java
// 実行用クラス
class SetGetRunner{
  public static void main(String[] args){
    // クラスの参照変数とインスタンスの作成
    // 今回の場合は クラス名 = SetGet
    クラス名 参照変数 = new クラス名();

    // SetGetクラスでprivate宣言した変数に値を代入
    参照変数.set変数名(代入したい値);

    // 値の取得
     変数名2 = 参照変数.get変数名();
  }
}

 日本語で説明すると上記のような形になります。とはいえとてもわかりにくいので実際に書いてみましょう。

Setget2.java
// getterとsetter用クラス
class SetGet2{
  // privateでint型変数appleを宣言
  private int apple;

  // getter
  public int getApple(){
    return this.apple;
  }

  // setter
  public void setApple(int apple){
    this.apple = apple;
  }
}

// 実行用クラス
class SetGet2Runner{
  public static void main(String[] args){
    // クラスの参照型変数とインスタンスの作成
    SetGet2 setget2 = new SetGet2();

    // privateで宣言した変数に代入
    setget2.setApple(3);

    // 値の取得
    int appleQuantity = setget2.getApple();

    // 出力
    System.out.println("リンゴの個数:" + appleQuantiry + "コ");

    // 出力結果
    // リンゴの個数:3コ
  }
}

となります。

機能

 では次に機能について説明をします。
 getterとsetterは
「オブジェクト指向においてインスタンス内の変数などは内部の状態を表すものであり、外部から直に参照したり操作したりすべきでないという考え方に基づく。変数などへのアクセスに必ず対応するメソッドを経由することにより、メソッド内で適切にチェックや処理を行って破綻の無いように外部からのアクセスをコントロールできるという利点がある。」

getter

「オブジェクト内部のメンバ変数(属性、プロパティ)の値を外部から読み取り・取得・参照するためのメソッド」
 

settter

「オブジェクト内部のメンバ変数(属性、プロパティ)の値を外部から書き込み・操作・設定するためのメソッド」

(以上IT用語辞典 e-wordsより引用)

とある。
 簡単に言うと、オブジェクト指向プログラミングの考え方がつかう理由。外部からのアクセスを制限してエラーやミスを防ぐ、ということです。

 getterは値を外部から得るためのメソッド、setterは外部から操作するためのメソッドです。

 上記の説明から分かる通り、実は外部から操作しない場合はsetterがなくても動きます。
 setterが必要になるのは外部のクラスから操作するためだけになりますので、変数をつかうだけなら必要はないわけです。
 実行用クラスで行っていることも宣言クラスにあるgetterメソッドとsetterメソッドを呼び出しているだけですしね。

具体的な動作内容

 具体的にそれぞれがどのように動いているのかについて前述したSetget2.javaファイルを用いて解説をします。

Setget2.java
  Setget2 setget2 = new SetGet2();

 参照型変数を宣言し、インスタンスの作成することで、Setget2クラスを扱えるようになります。参照型変数やインスタンスについては、Javaで理解する場合はこちらのHPが大変わかりやすいのでご参考に。

Setget2.java
setGet2.setApple(3);

 
 先ほど扱えるようになった参照型変数を用いてSetGet2クラスのsetterメソッドを呼び出しています。

SetGet2.java
public void setApple(int apple){
  this.apple = apple;
}

  setterメソッドではこう記述されていましたね。
int appleに数値3を引数することで、SetGet2クラスの変数appleに引数した値を代入する。
 というのが実行クラスの値の代入でやっていることになります。

 では次に値の取得側の動きですね。

Setget2.java
int appleQuantity = setget2.getApple();

実行用クラスでは変数appleQuantityにSetGet2クラスのgetterメソッドを呼び出して代入しています。

SetGet2.java
public int getApple(){
  return this.apple;
}

 ここでは戻り値があります。先ほどsetterで値を代入したappleをメソッドを用いて実行クラスに返しているんですね。その戻り値を扱う箱としてappleQuantityが宣言されて、やっと別クラスの変数を扱うことができるわけです。

 ちなみに実行用クラスで再度新しい変数を宣言して別クラスの変数を用いているわけですが、式の条件にすることで変数を用いないで別クラスの変数をつかうことができます。

 例えば別クラスで

Mass.java
private int colum;
private int row;

を宣言しているとします。setterで値を代入後、

MassRunner.java
ArrayList<Integer> data;

for(int i = 0; i < mass.getColum() * mass.getRow; i++){
  this.data = new ArrayList<Integer>;
  this.data.add(i);
}

とすることでそれぞれの値を変数にいれることなく用いることができます。式の結果を代入する変数は結局必要となるわけですが...。

今回発生したエラー

 と、ここまで説明してやっと今回1週間ハマっていたエラー内容にたどり着きました。
 エラーが発生したコードは以下になります。

Game.java
class Card{
  // 変数の宣言
  private int mark;
  private int number;

  // getter setter
  public int getMark(){
    return this.mark;
  }

  public void setMark(int mark){
    this. mark = mark;
  }

  public int getNumber(){
    return this.number;
  }

  public void setNumber(int number){
    this.number = number;
  }
}

class Deck{
  Card card = new Card();
  card.setMark(4);
  card.setNumber(13);

  // 空の山札の宣言
  ArrayList<Integer> deckCard = new ArrayList<Integer>();

  // 山札の作成
  public Create(){
    for(int i = 1; i < card.getMark() * card.getNumber(); i++){
    this.deckCard.add(i);
    }
  }
}

class Game{
  public static void main(String[] args){
    Deck deck = new Deck();
    deck.Create();
  }  
}


エラー文

エラー: 型の開始が不正です
  card.setMark(4);
  ^
エラー: <identifier>がありません
  card.setMark(1);
               ^
エラー: 型の開始が不正です
  card.setNumber(13);
  ^
エラー: <identifier>がありません
  card.setNumber(13);
               ^

原因

 例文と見比べればわかると思うのですが、setを
クラスにmainメソッドがないんですね。値の取得はmainメソッドのないクラスでできたのですが、値の操作はmain関数がないとできないようです。

 値をあとからsetする場合はmain関数内でしかできないということを知らず、setterとgetterの文法だけをみたことが原因で1週間もの期間ハマってしまっていました。
 自分のコードと参考のコードを見比べれば一発だったのにみている範囲が狭かったためのエラーですね。

 みなさんもお気を付けください。
 なお、なぜmainメソッドでしか実行できないかは、言語化して説明できるほと理解していないので、ほかの方に説明していただくのを待ちます。

 そもそも必要あるのかないのかについても、博識な方々にお願いしようと思います。

 以上、ご拝読ありがとうございます。

参考

2014年8月11日初版 中山清喬/国本大悟 著 株式会社インプレス 発行
スッキリわかるJava入門第2版
ISBN 9784-8443-3638-9

2019年7月4日アクセス 一番かんたんなJava入門

2019年7月4日アクセス Javaのオブジェクト指向入門

2019年7月4日アクセス IT用語辞典 e-word

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

へっぽこJavaプログラマがkotlinに挑戦する #1 HalloWorld編

開発環境

今回は、JetBrains社のIntelliJを使っていきます。
AndroidStudioという手もあったのですが、
サーバサイドkotlinを志すなら、IntelliJのほうが将来的にも良いかな、と。
eclipseにもプラグインが用意されており、使えるようです。

ちょっと動かしたい、という方は公式サイトの「Try Kotlin」がおすすめです。
Webページ上で、IDEを使っているような感覚でコードが試せます。
https://try.kotlinlang.org/#/Examples/Hello,%20world!/Simplest%20version/Simplest%20version.kt

Hallo World

なにはともあれ、新しい言語を学ぶ時はまずハローワールドから。

以下はJavaのハローワールドです。

HalloWorld.java
public class HelloWorld{
   public static void main(String[] args){
     System.out.println("Hello World!");
   }
}

続いて、kotlinのハローワールドです。
TryKotlinからコードを拝借しています。

HalloWorld.kt
fun main(args: Array<String>) {
    println("Hello World!")
}

できました。kotlinのほうが、シンプルに書けますね。
パッと見、kotlinで大きく違うのは
①クラス宣言がない
②パラメータの指定方法が違う
③print文がシンプル

といったところでしょうか。細かい部分もありますが、それは今後見ていこうと思います。

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

Javaプログラマがkotlinに挑戦する #1 HalloWorld編

開発環境

今回は、JetBrains社のIntelliJを使っていきます。
AndroidStudioという手もあったのですが、
サーバサイドkotlinを志すなら、IntelliJのほうが将来的にも良いかな、と。
eclipseにもプラグインが用意されており、使えるようです。

ちょっと動かしたい、という方は公式サイトの「Try Kotlin」がおすすめです。
Webページ上で、IDEを使っているような感覚でコードが試せます。
https://try.kotlinlang.org/#/Examples/Hello,%20world!/Simplest%20version/Simplest%20version.kt

Hallo World

なにはともあれ、新しい言語を学ぶ時はまずハローワールドから。

以下はJavaのハローワールドです。

HalloWorld.java
public class HelloWorld{
   public static void main(String[] args){
     System.out.println("Hello World!");
   }
}

続いて、kotlinのハローワールドです。
TryKotlinからコードを拝借しています。

HalloWorld.kt
fun main(args: Array<String>) {
    println("Hello World!")
}

できました。kotlinのほうが、シンプルに書けますね。
パッと見、kotlinで大きく違うのは
①クラス宣言がない
②パラメータの指定方法が違う
③print文がシンプル

といったところでしょうか。細かい部分もありますが、それは今後見ていこうと思います。

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

パフォーマンス計測②

ここは?

パフォーマンス計測①の続きです。

事前条件

パフォーマンス計測①をご参照願います。

文字列結合

区切り文字なし

インデックス変数系

順位 種類 10万回
平均
100万回
平均
1000万回
平均
5000万回
平均
1億回
平均
1 for文 & StringBuilder 9 21 75 299 577
2 IntStream & Collectors 45 67 224 913 1782
3 for文 & +演算子 2328

数行なら+演算子、ループ処理を行う場合はfor文とStringBuilderを併用しておく方が良さそうです(ストリームによる恩恵を受ける必要がない限り)。

for文 & +演算子

実験
String str = "";
for (int i = 0; i < loopCount; i++) str += "a";
ループ回数 1回目 2回目 3回目 4回目 5回目 6回目 7回目 8回目 9回目 10回目 平均
10万回 2367 2256 2403 2332 2357 2335 2320 2316 2333 2265 2328

10万回でこの速度か・・・。
試しに1回だけ100万回ループを試したら、199760ミリ秒となりました。

for文 & StringBuilder#append

「+演算子」とは雲泥の差です。

実験()
StringBuilder sb = new StringBuilder();
for (int i = 0; i < loopCount; i++) sb.append("a");
ループ回数 1回目 2回目 3回目 4回目 5回目 6回目 7回目 8回目 9回目 10回目 平均
10万回 8 10 9 9 10 9 9 8 9 9 9
100万回 24 23 20 21 21 24 22 20 22 21 21
1000万回 81 74 75 77 74 73 75 74 76 76 75
5000万回 308 299 296 294 299 310 290 300 303 293 299
1億回 564 569 568 601 583 593 575 564 578 577 577

IntStream & Collectors#joining

実験
String str = IntStream.range(0, loopCount)
  .mapToObj(i -> "a")
  .collect(Collectors.joining());
ループ回数 1回目 2回目 3回目 4回目 5回目 6回目 7回目 8回目 9回目 10回目 平均
10万回 52 48 41 46 41 50 43 40 47 43 45
100万回 66 65 70 63 65 73 73 57 67 71 67
1000万回 212 218 234 221 217 233 218 232 236 221 224
5000万回 874 878 874 870 1093 857 1080 859 885 860 913
1億回 1677 1692 1714 1701 2150 2103 1682 1729 1675 1705 1782

イテレータ変数系

順位 種類 10万回
平均
100万回
平均
1000万回
平均
5000万回
平均
1億回
平均
1 拡張for文 & StringBuilder 7 34 194 1495 2949
2 Stream & Collectors 41 68 365 2376 5004
3 拡張for文 & +演算子 2270

ストリームによる恩恵を受ける必要がない限り、拡張for文とStringBuilderを併用しておく方が良さそうです。

拡張for文 & +演算子

実験(拡張for文)
String str = "";
for (String s : array) str += s;
ループ回数 1回目 2回目 3回目 4回目 5回目 6回目 7回目 8回目 9回目 10回目 平均
10万回 2238 2300 2223 2296 2253 2367 2266 2254 2249 2261 2270

こちらも1回だけ100万回ループを試したら、227815ミリ秒となりました。

拡張for文 & StringBuilder#append

実験
StringBuilder sb = new StringBuilder();
for (String s : array) sb.append(s);
ループ回数 1回目 2回目 3回目 4回目 5回目 6回目 7回目 8回目 9回目 10回目 平均
10万回 7 7 7 8 8 8 6 8 7 8 7
100万回 37 34 39 31 29 36 36 32 29 38 34
1000万回 190 191 196 190 190 199 203 199 194 188 194
5000万回 1595 1428 1391 1438 1571 1443 1429 1550 1571 1535 1495
1億回 3064 2843 2904 3205 3108 2931 2850 2907 2870 2809 2949

Stream & Collectors

実験
String str = Arrays.stream(array).collect(Collectors.joining());
ループ回数 1回目 2回目 3回目 4回目 5回目 6回目 7回目 8回目 9回目 10回目 平均
10万回 41 44 40 37 42 38 40 50 41 38 41
100万回 71 64 69 69 69 67 68 72 61 72 68
1000万回 385 365 383 340 356 338 394 353 364 377 365
5000万回 2717 2685 2172 2122 2123 2171 2116 2201 2739 2716 2376
1億回 4349 4195 5363 5184 5231 5528 4168 5210 5442 5372 5004

Stream#ofについては内部でArrays#streamを呼び出しているだけなので、試行は行っていません。

区切り文字あり

インデックス変数系

順位 種類 10万回
平均
100万回
平均
1000万回
平均
5000万回
平均
1億回
平均
1 for文 & StringBuilder 16 34 135 535 1031
2 for文 & StringJoiner 7 25 426 1719 2972
3 IntStream & Collectors 62 127 831 3824 8101

StringJoinerとStringBuilderについては、差の開きが1000万回から出てきました。
1000万回を超えるようなループ処理なら、StringJoinerではなくStringBuilderを使ったほうが良さそうです。

なお接頭辞と接尾辞を指定したケースでも同様に各種試行を行いましたが、大して結果は変わらなかったので載せていません。

for文 & StringJoiner#add

実験
StringJoiner sj = new StringJoiner(" ");
for (int i = 0; i < loopCount; i++) sj.add("a");
ループ回数 1回目 2回目 3回目 4回目 5回目 6回目 7回目 8回目 9回目 10回目 平均
10万回 7 8 8 8 8 8 7 8 8 8 7
100万回 25 25 26 25 23 26 25 24 27 24 25
1000万回 384 438 427 451 414 427 419 427 435 445 426
5000万回 1895 1861 1847 1229 1835 1840 1856 1210 1827 1793 1719
1億回 3143 2849 2869 3108 3026 2971 2901 2923 2799 3135 2972

String#joinについては内部でStringJoinerを呼び出しているだけなので、試行は行っていません。

for文 & StringBuilder#append

実験
sb.append("a");
for (int i = 0; i < loopCount - 1; i++) {
  sb.append(" ");
  sb.append("a");
}
// デリミタと入力文字列を別にする必要があるため、sb.append(" a"); としていない。
ループ回数 1回目 2回目 3回目 4回目 5回目 6回目 7回目 8回目 9回目 10回目 平均
10万回 14 17 24 14 17 15 16 14 15 15 16
100万回 33 34 33 37 38 35 33 30 35 34 34
1000万回 161 161 134 131 125 127 126 132 126 128 135
5000万回 533 530 542 582 544 515 527 531 524 526 535
1億回 1105 1005 1016 1064 1023 1015 1013 1029 1008 1032 1031

IntStream & Collectors#joining

実験
String str = IntStream.range(0, loopCount)
  .mapToObj(i -> "a")
  .collect(Collectors.joining(" "));
ループ回数 1回目 2回目 3回目 4回目 5回目 6回目 7回目 8回目 9回目 10回目 平均
10万回 60 62 59 62 61 69 61 65 62 64 62
100万回 124 129 131 130 127 127 127 128 128 126 127
1000万回 860 799 818 844 836 792 852 854 809 847 831
5000万回 3639 4219 4142 3597 4265 3481 3615 3559 4190 3536 3824
1億回 7165 7080 8160 8296 8311 8245 8336 8353 8916 8156 8101

同じCollectors#joiningでも、引数ありとなしだけでこの違いです。

イテレータ変数系

順位 種類 10万回
平均
100万回
平均
1000万回
平均
5000万回
平均
1億回
平均
1 拡張for文 & StringJoiner 6 28 492 1768 3557
2 拡張for文 & StringBuilder 12 52 377 2282 4497
3 Stream & Collectors 57 126 992 4489 8426

イテレートさせるならStringJoinerでしょうか。
1000万回だけStringBuilderの方が速いのが少々気になりますが。

拡張for文 & StringJoiner#add

実験
StringJoiner sj = new StringJoiner(" ");
for (String s : array) sj.add(s);
ループ回数 1回目 2回目 3回目 4回目 5回目 6回目 7回目 8回目 9回目 10回目 平均
10万回 9 6 6 7 6 6 7 7 6 6 6
100万回 28 27 28 30 27 28 28 32 28 33 28
1000万回 507 490 493 546 481 497 470 479 472 487 492
5000万回 1782 1757 1708 1730 1684 1861 1741 2009 1703 1711 1768
1億回 3413 3668 3506 3430 3619 3643 3533 3537 3604 3626 3557

拡張for文 & StringBuilder#append

実験
StringBuilder sb = new StringBuilder();
for (String s : array) {
  if (sb.length() > 0) sb.append(" ");
  sb.append(s);
}
ループ回数 1回目 2回目 3回目 4回目 5回目 6回目 7回目 8回目 9回目 10回目 平均
10万回 14 12 12 13 11 13 12 12 11 12 12
100万回 55 48 54 45 52 55 59 64 48 49 52
1000万回 355 352 381 392 364 366 396 359 421 390 377
5000万回 2408 2373 2271 2304 2319 2294 2223 2197 2194 2245 2282
1億回 4557 4456 4581 4432 4482 4457 4333 4418 4578 4679 4497

Stream & Collectors

実験
String str = Arrays.stream(array).collect(Collectors.joining(" "));
ループ回数 1回目 2回目 3回目 4回目 5回目 6回目 7回目 8回目 9回目 10回目 平均
10万回 58 58 56 59 55 56 60 56 61 55 57
100万回 132 130 128 122 119 125 128 123 126 129 126
1000万回 1032 990 976 997 1014 976 966 955 969 1054 992
5000万回 4614 4404 4546 4459 4435 4527 4473 4574 4345 4522 4489
1億回 9026 8679 8618 7748 9044 7979 9088 7633 7703 8745 8426

もくじに戻る

https://qiita.com/k73i55no5/items/9e0825cee4cc1cb078a6

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

へっぽこJavaプログラマがkotlinに挑戦する #0 導入編

はじめに

私はSIerで8年ほど、Javaプログラマとして仕事をしてきました。
狭い視野でものを見ていたので、同期や協力会社の同年代の方々よりは、
プログラミングスキルはある方かな、と思っていたのですが、
ここ最近、新しい技術に目を向け、多くのエンジニアの方々を目にし、
自分の実力のなさを痛感した次第です。

そんな私が、近いうちにkotlinの案件に携われるチャンスが巡ってきました。
量産型SIerプログラマから脱却すべく、見る専だったQiitaを初めて書きながら、
勉強していきたいと思います。

同じような境遇の方もたくさんいらっしゃると思うので、何かの参考になると幸いです。
また、素人が書くkotlinの記事であり、
ツッコミどころも多々あると思いますので、指摘等あれば大歓迎です。

環境

OS:MacOS
IDE:IntelliJ

開発歴

Java 8年強(主に〜1.8)
フレームワーク Apache Struts2、Spring Framework
IDE Eclipse(Eclipse以外は1度もなし!)
DB Oracle、PostgreSQL

設計をやることも多く、がっつり開発をやれていた、というほどではないです。
お堅いプロジェクトが多く、技術的にはかなりレガシーです。
(1.8だけどStreamやラムダ式を使いたがらない、
 ジェネリクスや拡張for文くらいまでなら・・・といった雰囲気)

kotlinって何?

IntelliJを提供しているJetBrains(ジェットブレインズ)社が開発したJVM系言語です。
詳しくはWikipediaあたりを見た方が無難かなと思います。
特筆すべきは100%を自負するJavaとの互換性にあり、
私のようなJavaプログラマが多くの学習コストをかけずに習得できる、とあります。
ホントかよ?と思いますが・・・

Androidアプリ開発の正式言語として採用されてから、一気に知名度を上げたようです。
また、最近はサーバサイドでの活用も注目されており、
私が今後やっていきたいのが、このサーバサイドkotlinになります。

日本語の参考書も充実しており、私も何冊か購入しています。(いずれ、紹介します)

最後に

余談ですが、kotlinを選択した理由のひとつが、名前がかわいい(笑)

次回から、具体的にkotlinの仕組みに踏み込んでいこうと思います!

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

aapt.exeのエラーが出たときの備忘

androidアプリをビルドしていたら、見慣れないエラーが出た。

Process 'command 'C:\Users\XXXXXXXXXX\AppData\Local\Android\Sdk\build-tools\24.0.2\aapt.exe'' finished with non-zero exit value 1

aapt.exeを実行したときの終了コードが0じゃないってことは分かったけど、じゃぁどこでエラーになっているのか探してみても、これ以上のログは出ていない。

Gradleをインストールする

調べてみたところ、Gradleなるものを使えば詳細なログが出るらしい。
(Gradleとは、Groovyで記述するビルド自動化システム)
ということで、以下を参考にしてGradleのインストールをしてみた。

参考にした記事:
【Java】Gradleのインストールと基本的な使い方(画像付きで解説)

インストールできているか確認する。

C:\Users\XXXXXXXX>gradle -v

 ------------------------------------------------------------
Gradle 3.2.1
------------------------------------------------------------

Build time:   2016-11-22 15:19:54 UTC
Revision:     83b485b914fd4f335ad0e66af9d14aad458d2cc5

Groovy:       2.4.7
Ant:          Apache Ant(TM) version 1.9.6 compiled on June 29 2015
JVM:          1.7.0_80 (Oracle Corporation 24.80-b11)
OS:           Windows 8.1 6.3 amd64

ビルドログを確認する

Gradleのインストールができたため、さっそく以下を実行してログを確認する。

C:\Users\XXXXXX\AndroidStudioProjects\DataBaseApp2>gradlew assembleDebug --info

……………

Successfully started process 'command 'C:\Users\XXXXXXX\AppData\Local\Android\Sdk\build-tools\24.0.2\aapt.exe''
C:\Users\XXXXXXX\AndroidStudioProjects\DataBaseApp2\app\src\main\res\layout\activity_main.xml:25:32-40: AAPT: String types not allowed (at 't
extColor' with value ' #F5F5F5').

C:\Users\XXXXXXX\AndroidStudioProjects\DataBaseApp2\app\build\intermediates\res\merged\debug\layout\activity_main.xml:18: error: Error: Strin
g types not allowed (at 'textColor' with value ' #F5F5F5').

カラーコードにスペース入ってる…!!!
ということで削除し再ビルドしたら解決した。

参考にした記事:
エラー Unsupported major.minor version 52.0 の対処方法

Android StudioでJavaのバージョン

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

Spring Retry を利用して宣言型のリトライ処理を実装する

概要

  • サンプルプログラムを書いて Spring Boot + Spring Retry の挙動を確認する
  • Spring Retry を使うと処理の再試行を判断・制御するコードを書く必要がなくなる
  • 試行する回数やインターバル時間は @Retryable アノテーションに記述する

サンプルプログラムのソースコード一覧

├── build.gradle
├── settings.gradle
└── src
    └── main
        └── java
            └── info
                └── maigo
                    └── lab
                        └── sample
                            └── springretry
                                ├── Application.java
                                ├── HogeController.java
                                ├── HogeService.java
                                ├── KotsuException.java
                                ├── PonException.java
                                ├── PonKotsuRepository.java
                                └── PonKotsuRetryListener.java

サンプルプログラム概要

  • HTTPリクエストに対してJSONでメッセージを返す。
  • PonKotsuRepository はポンコツなリポジトリ。一定の確率でメッセージを返すがほとんどは失敗する。
  • HogeService は PonKotsuRepository が失敗したら再度 PonKotsuRepository を呼び出してリトライする。
  • 試行する回数やインターバル時間は @Retryable アノテーションに記述する。
  • 一定回数失敗したらあきらめる。
  • PonKotsuRepository は失敗の際に PonException または KotsuException を返す。
  • 最後の失敗が PonException の場合は、 Spring Retry の @Recover アノテーションを指定したメソッドで処理する。
  • 最後の失敗が KotsuException の場合は、 Spring Retry の @ExceptionHandler アノテーションを指定したメソッドで処理する。
  • RetryListener インターフェースを実装したクラスで、リトライ処理の状況を標準出力に出力する。

動作確認環境

  • macOS Mojave
  • OpenJDK 11.0.2
  • spring-boot-starter-web:2.2.0.M4
  • spring-retry:1.2.4.RELEASE

Gradle のビルド用ファイル

build.gradle

Spring Retry を使用するため dependencies に implementation 'org.springframework.retry:spring-retry:1.2.4.RELEASE' を追加する。
また、実行時に AOP クラスが必要なため runtime 'org.springframework.boot:spring-boot-starter-aop' を追加する。
依存ライブラリの指定方法は GitHub - spring-projects/spring-retry に載っている。

plugins {

  // The Java Plugin
  // https://docs.gradle.org/current/userguide/java_plugin.html
  id 'java'

  // Gradle - Plugin: org.springframework.boot
  // https://plugins.gradle.org/plugin/org.springframework.boot
  id 'org.springframework.boot' version '2.2.0.M4'

  // Gradle - Plugin: io.spring.dependency-management
  /// https://plugins.gradle.org/plugin/io.spring.dependency-management
  id 'io.spring.dependency-management' version '1.0.8.RELEASE'  
}

group = 'info.maigo.lab'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 11

repositories {
  mavenCentral()
  maven { url 'https://repo.spring.io/snapshot' }
  maven { url 'https://repo.spring.io/milestone' }
}

dependencies {
  implementation 'org.springframework.boot:spring-boot-starter-web:2.2.0.M4'
  implementation 'org.springframework.retry:spring-retry:1.2.4.RELEASE'
  runtime 'org.springframework.boot:spring-boot-starter-aop'
}

settings.gradle

pluginManagement {
  repositories {
    maven { url 'https://repo.spring.io/snapshot' }
    maven { url 'https://repo.spring.io/milestone' }
    gradlePluginPortal()
  }
  resolutionStrategy {
    eachPlugin {
      if (requested.id.id == 'org.springframework.boot') {
        useModule("org.springframework.boot:spring-boot-gradle-plugin:${requested.version}")
      }
    }
  }
}

rootProject.name = 'sample.springretry'

ビルドとサーバ起動

Gradle の Java プラグインにある build タスクで JAR ファイルを生成する。

$ gradle build

BUILD SUCCESSFUL in 3s
3 actionable tasks: 3 executed

生成された jar ファイルを java コマンドで実行する。これで Spring Boot による Web サーバが起動する。

$ java -jar build/libs/sample.springretry-0.0.1-SNAPSHOT.jar

ソースコード

Application.java

Spring Boot による起動エントリクラス。
Spring Retry を使用するため @EnableRetry アノテーションを指定している。

package info.maigo.lab.sample.springretry;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.retry.annotation.EnableRetry;

@SpringBootApplication
@EnableRetry
public class Application {

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

}

HogeController.java

HTTPリクエストを受け取ってレスポンスを返す Controller クラス。
例外が発生した際は @ExceptionHandler アノテーションを指定したメソッドにて処理した結果を返す。

package info.maigo.lab.sample.springretry;

import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HogeController {

    @Autowired
    private HogeService hogeService;

    @RequestMapping("/{message}")
    public Map<String, Object> index(@PathVariable("message") String message) {
        return hogeService.send(message);
    }

    @ExceptionHandler(Exception.class)
    public Map<String, Object> handleException(HttpServletRequest req, Exception e) {
        return new HashMap<String, Object>() {
            {
                put("handleException", e.getMessage());
            }
        };
    }
}

PonKotsuRepository.java

ポンコツな Repository クラス。
一定の確率で PonException や KotsuException を発生させる。

package info.maigo.lab.sample.springretry;

import java.util.Random;
import org.springframework.stereotype.Repository;

/**
 * ポンコツなリポジトリ。
 */
@Repository
public class PonKotsuRepository {

    private static final Random r = new Random(System.currentTimeMillis());

    /**
     * うまくいけばメッセージを返すが、ほとんど失敗する。
     * @param message
     * @return メッセージ
     * @throws PonException   ポン例外
     * @throws KotsuException コツ例外
     */
    public String send(String message) {
        int i = r.nextInt(5);
        if (i < 2) {
            System.out.println("PonKotsuRepository: Throws PonException.");
            throw new PonException();
        } else if (i < 4) {
            System.out.println("PonKotsuRepository: Throws KotsuException.");
            throw new KotsuException();
        } else {
            System.out.println("PonKotsuRepository: Returns a message.");
            return "1/3も伝わらないメッセージ: " + message;
        }
    }
}

HogeService.java

ポンコツな Repository クラスを利用する Service クラス。
@Retryable アノテーションを使用して、リトライ回数、リトライまでの待ち時間を指定している。
@Recover アノテーションを指定したメソッドは、指定回数試行しても例外が発生してしまった場合にコールされる。このメソッドは、最後の試行にて発生した例外を第1引数とし、 @Retryable アノテーションを付けたメソッドの引数を第2引数以降に取る。
今回は @Recover アノテーションを指定したメソッドを2つ用意している。ひとつは PonException が発生したときに使うもので、結果を文字列で返す。もうひとつは KotsuException が発生したときに使うもので、RuntimeException を投げる。
Spring Retry で使うアノテーションの情報は Spring-Retry - シンプルで本質的なコードを汚さないリトライ処理フレームワークorg.springframework.retry.annotation (Spring Retry 1.2.4.RELEASE API) が参考になる。

package info.maigo.lab.sample.springretry;

import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;
import org.springframework.retry.annotation.Recover;

@Service
public class HogeService {

    @Autowired
    private PonKotsuRepository repository;

    @Retryable(value = {PonException.class, KotsuException.class}, maxAttempts = 3, backoff = @Backoff(delay = 1000))
    public Map<String, Object> send(String message) {
        String result = repository.send(message);
        return new HashMap<String, Object>() {
            {
                put("result", result);
            }
        };
    }

    @Recover
    public Map<String, Object> recoverSend(PonException e, String message) {
        System.out.println("recoverSend: PonException");
        return new HashMap<String, Object>() {
            {
                put("error", "最後に発生したのは PonException");
            }
        };
    }

    @Recover
    public Map<String, Object> recoverSend(KotsuException e, String message) {
        System.out.println("recoverSend: KotsuException");
        throw new RuntimeException("最後に発生したのは KotsuException");
    }
}

PonException.java

ポン例外。RuntimeException を継承しているだけ。

package info.maigo.lab.sample.springretry;

public class PonException extends RuntimeException {
}

KotsuException.java

コツ例外。RuntimeException を継承しているだけ。

package info.maigo.lab.sample.springretry;

public class KotsuException extends RuntimeException {
}

PonKotsuRetryListener.java

RetryListener インターフェースを実装したクラス。
リトライ処理中に呼び出されるコールバックで構成されている。
今回の実装ではリトライ処理の状況を標準出力に出力している。

package info.maigo.lab.sample.springretry;

import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext;
import org.springframework.retry.listener.RetryListenerSupport;
import org.springframework.stereotype.Component;

@Component
public class PonKotsuRetryListener extends RetryListenerSupport {

    @Override
    public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
        System.out.println("PonKotsuRetryListener#close: " + getThrowableString(throwable));
        super.close(context, callback, throwable);
    }

    @Override
    public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
        System.out.println("PonKotsuRetryListener#onError: " + getThrowableString(throwable));
        super.onError(context, callback, throwable);
    }

    @Override
    public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
        System.out.println("PonKotsuRetryListener#open");
        return super.open(context, callback);
    }

    private static String getThrowableString(Throwable throwable) {
        return throwable == null ? "null" : throwable.getClass().getSimpleName();
    }
}

実行例

実行例1

curl で起動したサーバにアクセスすると JSON が出力される。

$ curl http://localhost:8080/hello
{"result":"1/3も伝わらないメッセージ: hello"}

サーバ側の標準出力を見る。
Pon例外発生後にリトライして、正常にメッセージを返すことができている。

PonKotsuRetryListener#open
PonKotsuRepository: Throws PonException.
PonKotsuRetryListener#onError: PonException
PonKotsuRepository: Returns a message.
PonKotsuRetryListener#close: null

実行例2

$ curl http://localhost:8080/foo
{"error":"最後に発生したのは PonException"}

サーバ側の標準出力を見る。
Pon例外 → Kotsu例外 → Pon例外の順番で発生している。
最後のPon例外については PonKotsuRepository#recoverSend にて結果を返している。

PonKotsuRetryListener#open
PonKotsuRepository: Throws PonException.
PonKotsuRetryListener#onError: PonException
PonKotsuRepository: Throws KotsuException.
PonKotsuRetryListener#onError: KotsuException
PonKotsuRepository: Throws PonException.
PonKotsuRetryListener#onError: PonException
recoverSend: PonException
PonKotsuRetryListener#close: PonException

実行例3

$ curl http://localhost:8080/bar
{"handleException":"最後に発生したのは KotsuException"}

サーバ側の標準出力を見る。
Pon例外 → Kotsu例外 → Kotsu例外の順番で発生している。
最後のKotsu例外については HogeController#handleException で結果を返している。

PonKotsuRetryListener#open
PonKotsuRepository: Throws PonException.
PonKotsuRetryListener#onError: PonException
PonKotsuRepository: Throws KotsuException.
PonKotsuRetryListener#onError: KotsuException
PonKotsuRepository: Throws KotsuException.
PonKotsuRetryListener#onError: KotsuException
recoverSend: KotsuException
PonKotsuRetryListener#close: KotsuException

参考資料

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

JUnit JupiterのExtensionのライフサイクルを調べてみた

JUnit 5(JUnit Jupiter)のExtensionを書こうとしてExtensionインスタンスのライフサイクルってどうなってるんだっけ?となったので備忘録的にメモ。

環境

  • AdoptOpenJDK 11
  • JUnit 5.5.0

TL;TR

  • @Extensionで登録したインスタンスはテストクラスごとに共有され、テストクラスが異なればExtensionインスタンスも異なる。
  • @RegisterExtensionで登録したインスタンス
    • staticフィールドの場合はテストクラスごとに共有される。
    • non-staticフィールドの場合はテストクラスのライフサイクルに依存する。

Extension Modelのおさらい

JUnit 5のExtension Modelは各テストに対する前処理・後処理・例外処理などを共通化するための仕組み。Junit 4のRuleなどに比べて自由度は下がるが、拡張ポイントに対応したインタフェースを実装するだけで手軽にExtensionを作成できる。

Extensionを使うには3つの方法が用意されている。

  1. @ExtensionでExtensionを登録する。
  2. @RegisterExtensionでプログラマティックにExtensionを登録する。
  3. ServiceLoaderで自動的に登録する。

今回は1と2の方法でそれぞれExtensionインスタンスの生成がどのタイミングで行われるかを確認する。

確認に用いるExtensionは以下の通り。

FooExtension.java
import org.junit.jupiter.api.extension.*;

public class FooExtension implements BeforeEachCallback, AfterEachCallback, BeforeAllCallback, AfterAllCallback {

    {
        System.out.println(String.format("[%s] created", this.toString()));
    }

    @Override
    public void beforeAll(ExtensionContext extensionContext) throws Exception {
        System.out.println(String.format("[%s] before all", this.toString()));
    }

    @Override
    public void beforeEach(ExtensionContext context) throws Exception {
        System.out.println(String.format("[%s] before each", this.toString()));
    }

    @Override
    public void afterEach(ExtensionContext extensionContext) throws Exception {
        System.out.println(String.format("[%s] after each", this.toString()));
    }

    @Override
    public void afterAll(ExtensionContext extensionContext) throws Exception {
        System.out.println(String.format("[%s] after all", this.toString()));
    }
}

@ExtensionでExtensionを登録する

ソース

@ExtendWith(FooExtension.class)
public class ExtensionTest {

    @Test
    void test1() { System.out.println("test 1"); }

    @Test
    void test2() { System.out.println("test 2"); }

    @Test
    void test3() { System.out.println("test 3"); }
}

結果

[example.FooExtension@70325e14] created
[example.FooExtension@70325e14] before all
[example.FooExtension@70325e14] before each
test 1
[example.FooExtension@70325e14] after each
[example.FooExtension@70325e14] before each
test 2
[example.FooExtension@70325e14] after each
[example.FooExtension@70325e14] before each
test 3
[example.FooExtension@70325e14] after each
[example.FooExtension@70325e14] after all

各テストで1つExtensionのインタンスが共有されていることがわかります。
なお、ExtensionTestはテストごとにインスタンスが生成されますが、@TestInstance(Lifecycle.PER_CLASS)をつけてExtensionTestのインスタンスも共有するように変更した場合も同じ結果になりました。

@RegisterExtension + staticフィールドでExtensionを登録する

ソース

SecondExtensionTest.java
public class SecondExtensionTest {

    @RegisterExtension
    static FooExtension fooExtension = new FooExtension();

    @Test
    void test1() { System.out.println("test 1"); }

    @Test
    void test2() { System.out.println("test 2"); }

    @Test
    void test3() { System.out.println("test 3"); }
}

結果

[example.FooExtension@55183b20] created
[example.FooExtension@55183b20] before all
[example.FooExtension@55183b20] before each
test 1
[example.FooExtension@55183b20] after each
[example.FooExtension@55183b20] before each
test 2
[example.FooExtension@55183b20] after each
[example.FooExtension@55183b20] before each
test 3
[example.FooExtension@55183b20] after each
[example.FooExtension@55183b20] after all

当然ですが、staticフィールドなので同じインスタンスが共有されます。

@RegisterExtension + non-staticフィールドでExtensionを登録する

先程のSecondExtensionTestのfooExtensionstaticを外してみます。

Lifecycle.PER_METHODの場合

ソース

SecondExtensionTest.java
public class SecondExtensionTest {

    @RegisterExtension
-   static FooExtension fooExtension = new FooExtension();
+   FooExtension fooExtension = new FooExtension();

    //後略
}

結果

[example.FooExtension@40a4337a] created
[example.FooExtension@40a4337a] before each
test 1
[example.FooExtension@40a4337a] after each
[example.FooExtension@fa4c865] created
[example.FooExtension@fa4c865] before each
test 2
[example.FooExtension@fa4c865] after each
[example.FooExtension@3bd82cf5] created
[example.FooExtension@3bd82cf5] before each
test 3
[example.FooExtension@3bd82cf5] after each

テストごとにExtensionのインスタンスが異なっていることが分かります。これはSecondExtensionTest自体のインスタンスがテストごとに毎回生成されていることが原因です。
なお、BeforeAllとAfterAllが動いていませんが、メソッドに@BeforeAllをつけるときと同様Lifecycle.PER_METHODの場合はstaticにしなければいけません。

Lifecycle.PER_CLASSの場合

ということでSecondExtensionTestのライフサイクルを、1回だけインスタンスが生成されるかたちにしてみます。

ソース

SecondExtensionTest.java
+ @TestInstance(Lifecycle.PER_CLASS)
public class SecondExtensionTest {

    @RegisterExtension
    FooExtension fooExtension = new FooExtension();

    //後略
}

結果

[example.FooExtension@c730b35] created
[example.FooExtension@c730b35] before all
[example.FooExtension@c730b35] before each
test 1
[example.FooExtension@c730b35] after each
[example.FooExtension@c730b35] before each
test 2
[example.FooExtension@c730b35] after each
[example.FooExtension@c730b35] before each
test 3
[example.FooExtension@c730b35] after each
[example.FooExtension@c730b35] after all

Extensionインスタンスが共有されています。また、BeforeAllとAfterAllが動くようになりました。このようにして、non-staticフィールドでExtensionを登録した場合は、当然ながら元のテストクラスのライフサイクルに依存するわけです。

ちゃんと読めば書いてある

https://junit.org/junit5/docs/current/user-guide/#extensions

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