20191201のJavaに関する記事は19件です。

2年間Javaのプログラムを書いて考えた型との向き合い方

Javaで考えたわたしの思う型との向き合い方

過去にweekend engineerで発表したLTの内容をまとめました.

プログラミングを初めて2年経ったので, 普段どんなことに気をつけながらプログラミングをしているかについてまとめました. 時々更新するかもしれません.

前置き

  • 静的型付け言語に馴染みのない人への発表内容なので, 少々くどいかもしれません.
  • 動的型付けが良い, 静的型付けが良いという話ではないので期待しないでください.
  • わたしが勉強したこと, 経験したことの範疇なので, 誤りなどありましたらコメント欄に是非お願いします.

結論

型に使われたら負け.
型の力を使いこなしてこそ, 静的型付け言語のパワーを活かせていると言えます.

目次

  • 静的型付けとは
  • 静的型付けとの向き合い方
    • データに対する意味付けをする.
    • 契約に基づく安全なプログラミングをする.
    • 振る舞いに制限と意味をもたせる.

静的型付けとは

静的型付けとは、プログラミング言語で書かれたプログラムにおいて、変数や、サブルーチンの引数や返り値などの値について、その型が、コンパイル時など、そのプログラムの実行よりも前にあらかじめ決められている、という型システムの性質のことである。

Wikipedia | 静的型付けより

JavaScriptなどの動的型付け言語は, プログラムを実行する時に型をチェックします.

const str = 10;
const length = (str) => {
  return str.length;
}
length(str);
// => undefinedが返る.

それに比較し, 静的型付け言語は実行するよりも前に型をチェックします.
コードレベルでは, 変数, 関数の仮引数, 戻り値の型を宣言しているところが動的型付け言語とは異なる点ですね.

Integer str = 10;
Integer length(String str) {
  return str.length();
}
System.out.println(length(str));
// => コンパイル時, つまり実行前にエラーが発生する.

最近はJavaScriptなど動的型付け言語でもエディタが警告を出してくれることもありますが, それは静的構文解析の技術によるところが大きいのではないかしらと思います.

静的型付けとの向き合い方

静的型付けにはデメリットがあります. 予め変数の型を明示しないといけないとか面倒くさい. キャスト(変数の型を変換すること)するのも面倒くさい.

全部気持ちわかります. わたしもそうでした. でもそれは, わたし自身が型に使われているということを示していると最近気づきました. 静的型付け言語は, よりアグレッシブに型を定義する, わたしが型を使うことによって初めてメリットが生まれるのだと気づきました.

以下, 具体的にどういうふうに型を使っていくかを書きます.

データに対する意味付けをする.

StringIntなどプリミティブな型を使うよりも, 自分で定義した型を使う方がプログラムでより多くのことを表現することが出来ます.

プリミティブな型をそのまま使う場合

String phoneNumber = "000-0000-0000";

自分で定義した型を使う場合

PhoneNumber phoneNumber = new PhoneNumber("000-0000-0000");

ただのString型を使うと, その変数は文字列であるということしかわかりません.
二つ目の例のようにPhoneNumberという型の変数からは, どんな文字列が格納されているのかが予想できます. また, どのようなメソッドを呼び出すことができるかも想像できるのではないでしょうか. この場合, 何も呼び出せそうにないですが笑

契約に基づく安全なプログラミングをする.

契約プログラミングの詳細は以下の記事のほうが詳しいです.

ここで言いたいのは, 変数に対して「ただの文字列ではなく電話番号の形式の文字列なので, 決められた命令に対しては例外を発生させることなく振る舞える」ということを保証できることにメリットがあるということです。

以下のクラスではPhoneNumberクラスのインスタンスを生成するときに、「ハイフン付きの電話番号形式の文字列」であるかどうかをチェックし, 例外を発生させています. 逆説的にPhoneNumber型の変数は必ず「ハイフン付きの電話番号形式の文字列」をデータとして持つことを型によって保証できます.

/**
 * 電話番号を表すクラス.
 * String
 */
class PhoneNumber {
  private static final Pattern pattern = 
      Pattern.compile("^0\\d{2,3}-\\d{1,4}-\\d{4}$");
  private final String value;

  /**
   * 引数に指定されたハイフン付き電話番号をもとに新しい電話番号型のインスタンスを戻す.
   * @param value 電話番号
   * @return 電話番号型のインスタンス
   * @throws IllegalArgumentException ハイフン付き電話番号以外が引数の場合
   */
  PhoneNumber(String value){
    Objects.requireNonNull(value);
    if (!value.matches(pattern)) {
      throw new IllegalArgumentException();
    }
    this.value = value;
  }

  String getValue() {
    return this.value;
  }
}

振る舞いに制限と意味をもたせる

String型のような言語から提供されている型には様々なメソッドや関数が定義されています. 果たしてその全てが必要でしょうか.

電話番号に対して, 文字コードを取得したり, 文字列の順番を逆にできるのは適切でしょうか. 敢えて電話番号に対して型を定義するのは, 変数に対する操作を限定し, 間違いを防ぐためでもあります.

逆に振る舞いを増やすこともあります. 電話番号の市内局番を取得することを想定します.
String型の変数に電話番号を代入して使用するとき, 以下のようなプログラムになります.

String phoneNumber = "000-0000-0000";
String cityCode = phoneNumber.split("-")[1];

PhoneNumber型に市内局番を戻す振る舞いを定義すると, 以下のようなプログラムになります.

PhoneNumber phoneNumber = new PhoneNumber("000-0000-0000");
String cityCode = phoneNumber.getCityCode();


class PhoneNumber {
  private final String value;
  // 略

  String getCityCode() {
    return value.split("-")[1];
  }
}

余談

ここまでかなり冗長にソースコードを書いてきましたが、実際にはLombokBean Validationのアノテーション使ってコードを簡略化することができます。

/**
 * 電話番号を表すクラス.
 * String
 */
@Value
class PhoneNumber {
  @NotNull
  @Pattern(regexp="^0\\d{2,3}-\\d{1,4}-\\d{4}$");
  private final String value;
}

さいごに

ここまでかなりミクロな単位(電話番号)について書いてきましたが, 必ずしもこの単位で型を作る必要はないと思っています. 整合性の担保が必要な単位で型を作るとメリットが大きいです. この記事では, 画面からの入力値を全部ひっくるめて型として定義し, 計算ロジックの引数にしています. 型やコンストラクタによる制限と保証の手法は今後も活かしていければと.

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

ABCLでマンデルブロ集合

この記事は、 Lisp Advent Calendar 2019の2日目の記事です。

ABCLでマンデルブロ集合

ABCL(Armed Bear Common Lisp)でマンデルブロ集合を表示させてみました。

ABCL(Armed Bear Common Lisp)はJavaで作られたCommonLispです。そのため、Javaが動く環境ならばどこでも動きます。また、Javaの豊富なライブラリを利用することができます。JavaのSwingを使ってマンデルブロ集合を描画してみました。

Mandelbrot_001.png
Mandelbrot_004.png
Mandelbrot_007.png
Mandelbrot_010.png
Mandelbrot_013.png
Mandelbrot_016.png
Mandelbrot_019.png

jnewでJavaのインスタンスの作成、jcallでメソッドの呼び出し、jstaticでクラスメソッドの呼び出し、jfieldでフィールドの参照ができます。詳細はソースを見てください。

test/Mandelbrot.lisp
; Mandelbrot Set
; (load "test/Mandelbrot.lisp")

; Java> new BufferedImage(w, h, BufferedImage.TYPE_4BYTE_ABGR);
(defun createImage (w h)
    (jnew "java.awt.image.BufferedImage" 
        w h (jfield "java.awt.image.BufferedImage" "TYPE_4BYTE_ABGR")))

; Java> image.setRGB(x, y, rgb);
(defun putPixel (image x y rgb)
    (jcall "setRGB" image x y rgb))

; Java> g.setColor(color);
; Java> g.fillRect(x1, y1, x2, y2);
(defun fillRect (g x1 y1 x2 y2 color)
    (progn
        (jcall "setColor" g color)
        (jcall "fillRect" g x1 y1 x2 y2)))

; Java> Color.HSBtoRGB(h, s, b);
(defun convertHSBtoRGB (h s b)
    (jstatic "HSBtoRGB" "java.awt.Color" h s b))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; マンデルブロ集合の漸化式
; z[n+1] = z[n]^2 + c
(defun f (x y a b)
    (cons
        (+ (- (* x x) (* y y)) a)
        (+ (* 2 x y) b)))

; 発散するか? 絶対値が2以上になったか?
(defun isDiverge (x y)
    (> (+ (* x x) (* y y)) 4.0))

; 点(a,b)の発散するまでの回数を計算する。発散しない場合は-1を返す。
(defun calc (a b maxCount)
    (block exit
        (let* ((zx 0) (zy 0) (i 0))
            (loop
                (if (> i maxCount) (return-from exit -1))
                (if (isDiverge zx zy) (return-from exit i))
                (setq zz (f zx zy a b))
                (setq zx (car zz))
                (setq zy (cdr zz))
                (setq i (+ i 1))))))

; 計算した回数により色分けする。
(defun toRgb (count)
    (if (= count -1)
        (convertHSBtoRGB 0 0 0)     ; 黒
        (convertHSBtoRGB (* (mod count 1000) 0.002) 1.0 1.0))) ; グラデーション


; 指定された点を打つ
(defun drawMandelbrot1 (image a0 b0 zoom maxCount x y)
    (setq a (+ a0 (* (- x 100) zoom)))
    (setq b (+ b0 (* (- y 100) zoom)))
    (setq count (calc a b maxCount))
    (setq rgb (toRgb count))
    (putPixel image x y rgb))

; 画像に描画する
(defun drawMandelbrot (image a0 b0 zoom maxCount)
    (dotimes (y 200)
        (dotimes (x 200)
            (drawMandelbrot1 image a0 b0 zoom maxCount x y))))


; メイン
(let ((frame (jnew "javax.swing.JFrame" "Mandelbrot set"))
    (image (createImage 200 200)))
    (progn
        (jcall "add" frame (jnew "javax.swing.JLabel" (jnew "javax.swing.ImageIcon" image)))
        (jcall "pack" frame)
;       (drawMandelbrot image -0.745428d0 -0.113009d0 0.02d0 100)   ; Mandelbrot_001.png
;       (drawMandelbrot image -0.745428d0 -0.113009d0 0.01d0 100)   ; Mandelbrot_002.png
;       (drawMandelbrot image -0.745428d0 -0.113009d0 0.005d0 100)  ; Mandelbrot_003.png
;       (drawMandelbrot image -0.745428d0 -0.113009d0 0.002d0 1000) ; Mandelbrot_004.png
;       (drawMandelbrot image -0.745428d0 -0.113009d0 0.001d0 1000) ; Mandelbrot_005.png
;       (drawMandelbrot image -0.745428d0 -0.113009d0 0.0005d0 1000)    ; Mandelbrot_006.png
;       (drawMandelbrot image -0.745428d0 -0.113009d0 0.0002d0 1000)    ; Mandelbrot_007.png
;       (drawMandelbrot image -0.745428d0 -0.113009d0 0.0001d0 1000)    ; Mandelbrot_008.png
;       (drawMandelbrot image -0.745428d0 -0.113009d0 0.00005d0 1000)   ; Mandelbrot_009.png
;       (drawMandelbrot image -0.745428d0 -0.113009d0 0.00002d0 1000)   ; Mandelbrot_010.png
;       (drawMandelbrot image -0.745428d0 -0.113009d0 0.00001d0 1000)   ; Mandelbrot_011.png
;       (drawMandelbrot image -0.745428d0 -0.113009d0 0.000005d0 1000)  ; Mandelbrot_012.png
;       (drawMandelbrot image -0.745428d0 -0.113009d0 0.000002d0 1000)  ; Mandelbrot_013.png
;       (drawMandelbrot image -0.745428d0 -0.113009d0 0.000001d0 1000)  ; Mandelbrot_014.png
;       (drawMandelbrot image -0.745428d0 -0.113009d0 0.0000005d0 1000) ; Mandelbrot_015.png
;       (drawMandelbrot image -0.745428d0 -0.113009d0 0.0000002d0 1000) ; Mandelbrot_016.png
;       (drawMandelbrot image -0.745428d0 -0.113009d0 0.0000001d0 1000) ; Mandelbrot_017.png
;       (drawMandelbrot image -0.745428d0 -0.113009d0 0.00000005d0 1000)    ; Mandelbrot_018.png
;       (drawMandelbrot image -0.745428d0 -0.113009d0 0.00000002d0 2000)    ; Mandelbrot_019.png
        (drawMandelbrot image -0.745428d0 -0.113009d0 0.00000001d0 2000)    ; Mandelbrot_020.png
        (jcall "setVisible" frame T)))

実行方法は以下のとおり。実行には数秒~数十秒かかります。

C:\devel\lisp\abcl-bin-1.6.0>java -jar abcl.jar
Armed Bear Common Lisp 1.6.0
Java 10.0.1 Oracle Corporation
Java HotSpot(TM) 64-Bit Server VM
Low-level initialization completed in 0.23 seconds.
Startup completed in 4.251 seconds.
Type ":help" for a list of available commands.
CL-USER(1): (load "test/Mandelbrot.lisp")
T
CL-USER(2): (exit)

C:\devel\lisp\abcl-bin-1.6.0>

既知のツッコミ

  • CommonLispでは複素数が使えます。知らなかったので、実部と虚部に分解して計算しました。
  • デフォルトで浮動小数点は単精度だったので、数値の末尾にd0をつけて倍精度にしています。*read-default-float-format*で指定する方法もあります。

ではでは

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

Java の TreeMap で Map �の値をキーの昇順にソートする

概要

  • Map インターフェースの実装としてよく使われている HashMap クラスは順序付けを何も保証していない
  • SortedMap インターフェースはキーの順序付けを提供する
  • TreeMap クラスは SortedMap インターフェースを実装しているため、キーによる順序付けが行われる
  • TreeMap クラスを使うとキーの昇順に自動でソートされる

HashMap クラス

HashMap は何もソートしない。

HashMap (Java SE 11 & JDK 11 )

このクラスはマップの順序を保証しません。特に、その順序を常に一定に保つことを保証しません。

SortedMap インターフェース

SortedMap はキーによるソートを実現する。
entrySet、keySet、values メソッドで返されるキーや値は、キーの順序付けに従ってソートされている。

SortedMap (Java SE 11 & JDK 11 )

そのキーに対して全体順序付けを提供するMapです。 マップの順序付けは、キーの自然順序付けに従って行われるか、ソート・マップ構築時に通常提供されるComparatorを使って行われます。 この順序は、ソート・マップのコレクション・ビュー(entrySet、keySet、valuesメソッドによって返される)の反復処理時に反映されます。 その順序付けを利用するために、追加のオペレーションがいくつか提供されています。 (このインタフェースはマップで、SortedSetに類似しています。)

TreeMap クラス

TreeMap は SortedMap インターフェースを実装しているため、キーによるソートが行われる。

TreeMap (Java SE 11 & JDK 11 )

赤 - 黒ツリーに基づくNavigableMap実装です。 マップは、使用するコンストラクタに応じて、そのキーの自然順序付けに従って、またはマップ作成時に提供されるComparatorによってソートされます。

サンプルコード

HashMap と TreeMap の挙動を確認するソースコード。

import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;

public class Sample {

  public static void main(String[] args) {

    // HashMap を構築
    // 順番は保証されない
    Map<String, String> hashMap = new HashMap<String, String>();
    hashMap.put("0001", "Alice");
    hashMap.put("0002", "Bob");
    hashMap.put("0003", "Carol");
    hashMap.put("0004", "Dave");
    hashMap.put("0005", "Ellen");
    System.out.println("HashMap");
    for (String key : hashMap.keySet()) {
      System.out.println(key + ": " + hashMap.get(key));
    }
    System.out.println();

    // TreeMap を構築
    // キーの昇順に自動でソートしてくれる
    Map<String, String> treeMap = new TreeMap<String, String>(hashMap);
    System.out.println("TreeMap");
    for (String key : treeMap.keySet()) {
      System.out.println(key + ": " + treeMap.get(key));
    }
    System.out.println();

    // TreeMap に値を追加するとキーの昇順に自動でソートしてくれる
    treeMap.put("0000", "XXXXX");
    System.out.println("TreeMap");
    for (String key : treeMap.keySet()) {
      System.out.println(key + ": " + treeMap.get(key));
    }
    System.out.println();
  }
}

実行結果。
HashMap は何もソートされない。
TreeMap はキーの昇順でソートされている。

HashMap
0004: Dave
0005: Ellen
0002: Bob
0003: Carol
0001: Alice

TreeMap
0001: Alice
0002: Bob
0003: Carol
0004: Dave
0005: Ellen

TreeMap
0000: XXXXX
0001: Alice
0002: Bob
0003: Carol
0004: Dave
0005: Ellen

今回の環境

$ java --version
openjdk 11.0.2 2019-01-15
OpenJDK Runtime Environment 18.9 (build 11.0.2+9)
OpenJDK 64-Bit Server VM 18.9 (build 11.0.2+9, mixed mode)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Java の TreeMap で Map の値をキーの昇順にソートする

概要

  • Map インターフェースの実装としてよく使われている HashMap クラスは順序付けを何も保証していない
  • SortedMap インターフェースはキーの順序付けを提供する
  • TreeMap クラスは SortedMap インターフェースを実装しているため、キーによる順序付けが行われる
  • TreeMap クラスを使うとキーの昇順に自動でソートされる

HashMap クラス

HashMap は何もソートしない。

HashMap (Java SE 11 & JDK 11 )

このクラスはマップの順序を保証しません。特に、その順序を常に一定に保つことを保証しません。

SortedMap インターフェース

SortedMap はキーによるソートを実現する。
entrySet、keySet、values メソッドで返されるキーや値は、キーの順序付けに従ってソートされている。

SortedMap (Java SE 11 & JDK 11 )

そのキーに対して全体順序付けを提供するMapです。 マップの順序付けは、キーの自然順序付けに従って行われるか、ソート・マップ構築時に通常提供されるComparatorを使って行われます。 この順序は、ソート・マップのコレクション・ビュー(entrySet、keySet、valuesメソッドによって返される)の反復処理時に反映されます。 その順序付けを利用するために、追加のオペレーションがいくつか提供されています。 (このインタフェースはマップで、SortedSetに類似しています。)

TreeMap クラス

TreeMap は SortedMap インターフェースを実装しているため、キーによるソートが行われる。

TreeMap (Java SE 11 & JDK 11 )

赤 - 黒ツリーに基づくNavigableMap実装です。 マップは、使用するコンストラクタに応じて、そのキーの自然順序付けに従って、またはマップ作成時に提供されるComparatorによってソートされます。

サンプルコード

HashMap と TreeMap の挙動を確認するソースコード。

import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;

public class Sample {

  public static void main(String[] args) {

    // HashMap を構築
    // 順番は保証されない
    Map<String, String> hashMap = new HashMap<String, String>();
    hashMap.put("0001", "Alice");
    hashMap.put("0002", "Bob");
    hashMap.put("0003", "Carol");
    hashMap.put("0004", "Dave");
    hashMap.put("0005", "Ellen");
    System.out.println("HashMap");
    for (String key : hashMap.keySet()) {
      System.out.println(key + ": " + hashMap.get(key));
    }
    System.out.println();

    // TreeMap を構築
    // キーの昇順に自動でソートしてくれる
    Map<String, String> treeMap = new TreeMap<String, String>(hashMap);
    System.out.println("TreeMap");
    for (String key : treeMap.keySet()) {
      System.out.println(key + ": " + treeMap.get(key));
    }
    System.out.println();

    // TreeMap に値を追加するとキーの昇順に自動でソートしてくれる
    treeMap.put("0000", "XXXXX");
    System.out.println("TreeMap");
    for (String key : treeMap.keySet()) {
      System.out.println(key + ": " + treeMap.get(key));
    }
    System.out.println();
  }
}

実行結果。
HashMap は何もソートされない。
TreeMap はキーの昇順でソートされている。

HashMap
0004: Dave
0005: Ellen
0002: Bob
0003: Carol
0001: Alice

TreeMap
0001: Alice
0002: Bob
0003: Carol
0004: Dave
0005: Ellen

TreeMap
0000: XXXXX
0001: Alice
0002: Bob
0003: Carol
0004: Dave
0005: Ellen

今回の環境

$ java --version
openjdk 11.0.2 2019-01-15
OpenJDK Runtime Environment 18.9 (build 11.0.2+9)
OpenJDK 64-Bit Server VM 18.9 (build 11.0.2+9, mixed mode)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RocksDBのColumn Familiesについてのメモ

RocksDBのColumn Familiesとは

同じデータベース内でキーが同じでも、Column Familiesが異なると、登録・参照・削除できるようになります。
公式のドキュメントには「データベースを論理的にパーティション分割する方法を提供します」とのことでした。
名前空間と同じような概念と思って良いと思います。
以下が特徴です(公式をGoogle翻訳ですので、正しくは公式ドキュメントを参照してください)。

  • 異なるColumn Familiesへのアトミックな操作が可能
    Write({cf1, key1, value1}, {cf2, key2, value2}) がアトミックに行えるということです。cf1とcf2がColumn Familiesの指定です。

  • データベース内のColumn Familiesに渡った一貫的なビュー
    よくわかりません

  • 異なるColumn Familiesを個別に構成する機能
    よくわかりません

  • 新しいColumn Familiesの追加、ドロップができます。どちらの操作もかなり高速です
    記載のとおりです

公式ドキュメント
https://github.com/facebook/rocksdb/wiki/Column-Families

どんなときに使う?

正直わかりません。ただこんな機能があることを知っていると、いつか役立つかも。。。

使い方

public class SampleCulmunFamily {

    public void execute() throws Exception {

        // DBの作成
        try (final RocksDB db = RocksDB.open("sample")) {
            // column familyの作成(先にColumnFamilyDescriptorを複
            // columnfamilyを複数作成する場合は、ColumnFamilyDescriptorのListを作成して、db.openに渡せばOKです
            try (final ColumnFamilyHandle columnFamilyHandle = db.createColumnFamily(
                    new ColumnFamilyDescriptor("new_cf".getBytes(),
                            new ColumnFamilyOptions()))) {
            }
        }

        // DBと2つのCulmunFamilyの作成
        final List<ColumnFamilyDescriptor> columnFamilyDescriptors = new ArrayList<>();
        // CulmunFamilyを指定しない場合でも、defaultというCulmunFamilyが裏で作成されています。
        // したがって、今回は、defaultとnew_cfという2つのCulmunFamilyを作成するということになります
        columnFamilyDescriptors.add(new ColumnFamilyDescriptor(
                RocksDB.DEFAULT_COLUMN_FAMILY, new ColumnFamilyOptions()));
        columnFamilyDescriptors.add(new ColumnFamilyDescriptor(
                "new_cf".getBytes(), new ColumnFamilyOptions()));
        final List<ColumnFamilyHandle> columnFamilyHandles = new ArrayList<>();
        try (final DBOptions options = new DBOptions();
                final RocksDB db = RocksDB.open("sample",
                        columnFamilyDescriptors, columnFamilyHandles)) {

            // CulumnFamilyの識別子を取得
            ColumnFamilyHandle defaultColumnFamily = columnFamilyHandles.get(0);
            ColumnFamilyHandle newColumnFamily = columnFamilyHandles.get(1);

            try {
                // 同一キーで登録します。識別子を使用して、登録先を振り分けます。
                db.put(defaultColumnFamily, new WriteOptions(),
                        "key".getBytes(), "default_value".getBytes());
                db.put(newColumnFamily, new WriteOptions(),
                        "key".getBytes(), "new_cf_value".getBytes());

                // 参照
                System.out.println(new String(db.get(defaultColumnFamily, "key".getBytes())));
                System.out.println(new String(db.get(newColumnFamily, "key".getBytes())));

                // 削除
                db.delete(defaultColumnFamily, "key".getBytes());
                db.delete(newColumnFamily, "key".getBytes());

                // column familyの削除
                db.dropColumnFamily(newColumnFamily);
            } finally {
                for (final ColumnFamilyHandle handle : columnFamilyHandles) {
                    handle.close();
                }
            }
        }
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

人生で初めて意図的にtry catchを使った話

こんにちは、ヨースケです。
qiita1.PNG
記事を出して3つ目の僕がAndroidアプリ開発をする上で気を付けていることが300以上のviewsにいいねが4も付きました!ありがとうございます!励みになりますなります。m(v _ v)m

  • 以前は..
  • 初めてのアプリで

以前は..

 意図的じゃなかったらどうやってtry catchしてんだwって話なんですが、今までは参考書とかに書いてある通りコードを打って「あぁ、例外処理なんだな」と思って何にも気にしていませんでした。なので、try catchをする意味も理解できていませんでした。食わず嫌いも少しあったかもしれません。

初めてのアプリで

qiita1-1.png

 初めて作ったアプリで2進数、16進数変換をしたいときに入力された数字が整数じゃないと計算できない、そもそも整数以外で変換ボタンを押すとNumberFormatExceptionのエラーが発生してしまいます。何とかそのエラーを防ぎたいと目につけたのが今まで避けていたtry catchです。(一部抜粋です↓)

MainActivity.java
 //int型以外の数値が入ったら2進数変換ができないのでエラー処理
                    try{
                        int error = Integer.parseInt(calc_text.getText().toString());
                    }catch (NumberFormatException e){
                        calc_text.setTextColor(Color.RED);
                        Toast.makeText(MainActivity.this,"変換は整数でしか行えません!または範囲外の数値です",Toast.LENGTH_SHORT).show();
                        break;
                    }

これは入力された数字がInt型以外、つまりNumberFormatExceptionのエラーが発生するなら文字を赤くしてトーストでエラー内容を表示するといった内容です。(switch内なのでbreakしてます)
 何て便利なんだ!とそのときは感動を覚え、ちょっとは成長できたかなーと思ったものですが、やっぱり何でもやってみないと分からないものですね(当たり前)。

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

JMockitでテスト対象クラスから呼ばれるクラスの部分Mock方法メモ

必要な状況

テスト対象クラスから呼ばれるクラスの一部のメソッドのみMock化したい場合

JMockit

1.46

結論

  1. ExpectationsにMock化したいインスタンスを渡して、Mock化したいメソッドのみExpectations内に定義する
  2. テスト対象クラスに(無理やり)設定する

テスト対象クラス

public class Sample {

    @Inject
    Hoge hoge;

    public String getHoge() {
        return hoge.getHoge() + hoge.getHogeUpper();
    }
}

テスト対象クラスからInjectしたHogeのメソッドを使用しています。
そのHogeについて部分Mockしたい場合の対処法となります。

テスト対象クラスから呼ばれるクラス

public class Hoge {

    public String getHoge() {
        return "hoge";
    }

    public String getHogeUpper() {
        return null;
    }
}

getHogeは実装完了しており、getHogeUpperはまだ未実装という状況としてください。

テスト失敗

public class SampleTest {

    @Tested
    Sample sample;

    @Injectable
    Hoge hoge;

    /**
     * テスト対象クラスから呼ばれるクラスの一部のメソッドのみMock化したいケース。
     * テストは失敗する。
     */
    @Test
    public void test() {

        new Expectations(hoge) {{
            hoge.getHogeUpper();
            result = "HOGE";
        }};

        String actual = sample.getHoge();
        assertThat(actual, is("hogeHOGE")); // actualはnullHOGEとなる
    }
}

テスト対象クラスはInjectを使用しているため、通常、JMockitの@Tested@Injectableでインスタンスを注入するようにかくと思いますが、それではテストは失敗します。
理由は、@InjectableでHogeのすべてのメソッドが空になるためです。

テスト成功

public class SampleTest2 {

    Sample sample = new Sample();

    /**
     * テスト対象クラスから呼ばれるクラスの一部のメソッドのみMock化したいケース。
     * テストは成功する。
     * @throws Exception
     * @throws NoSuchFieldException
     */
    @Test
    public void test() throws NoSuchFieldException, Exception {

        Hoge hoge = new Hoge();
        new Expectations(hoge) {
            {
                hoge.getHogeUpper();
                result = "HOGE";
            }
        };

        // Sampleのprivateフィールドに部分Mockしたhogeを設定
        Field field = sample.getClass().getDeclaredField("hoge");
        field.setAccessible(true);
        field.set(sample, hoge);

        String actual = sample.getHoge();
        assertThat(actual, is("hogeHOGE"));
    }
}

部分Mockの詳細は以下を参照してください。
https://jmockit-ja.nyamikan.net/tutorial/Mocking.html#partial
部分MockしたMockインスタンスを、リフレクションでテスト対象クラスに設定することでテストを成功させています。

最後に

JMockitの古いバージョンでは、MockUpで一部のみMock化し、getMockInstanceで取得したインスタンスをDeencapsulation.setFieldでテストクラスに設定していましたが、1.46ではどちらのメソッドも消えていました。。。
JMockitはAPIの削除が頻繁に行われているので、バージョンを新しくする際はご注意ください。
他に良い方法などあればぜひ教えて下さい。

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

【Gang of Four】デザインパターン学習 - State

State - 状態

目次

よくXXXStatusのような状態を管理するプロパティを持たせ、switch(XXXStatus){ // caseにより色々な処理を行う }ような実装をすることがあると思います。
状態が増えるとswitch文が長々と続くことになり読む側も大変になっていくのを、状態1つ1つをオブジェクトにすることによりスマートにしようというのが本パターンの主旨だと考えています。
Commandパターンに似ている気がします。

目的

オブジェクトの内部状態が変化したときに、オブジェクトが振る舞いを変えるようにする。クラス内では、振る舞いの変化を記述せず、状態を表すオブジェクトを導入することでこれを実現する。

構成要素

・Context 色々な状態があるクラス
・State 状態の抽象クラス
・ConcreteState 状態の具象クラス

実装

自動販売機を実装することにします。
実装する自動販売機の状態としては以下の2つの状態と2つの操作があることと、商品の価格は一律100円とします。
状態
・商品の価格未満の金額が入金されている。
・商品の価格以上の金額が入金されている。

操作
・入金する。
・商品のボタンを押す。

State 状態の抽象クラス

State.kt
package state

interface State {
    fun coin(v: VendingMachine)
    fun push(v: VendingMachine)
}

ConcreteState 状態の具象クラス

商品の価格未満の金額が入金されている状態を表すクラス

LackState.kt
package state

class LackState: State {
    override fun coin(v: VendingMachine) {
        v.insertMoney(50)
    }

    override fun push(v: VendingMachine) {
        v.errorMessage()
    }
}

商品の価格以上の金額が入金されているクラス

SufficientState.kt
package state

class SufficientState: State {
    override fun coin(v: VendingMachine) {
        v.warningMessage()
    }

    override fun push(v: VendingMachine) {
        v.buy()
    }

}

Context 色々な状態があるクラス

抽象クラス

Context.kt
package state

interface Context {
    fun coin()
    fun push()
}

自動販売機具象クラス

VendingMachine.kt
package state

class VendingMachine: Context {
    companion object {
        private val lack = LackState()
        private val sufficient = SufficientState()
    }

    private var state: State = lack
    private var money = 0

    override fun coin() {
        state.coin(this)
    }

    override fun push() {
        state.push(this)
    }

    fun warningMessage() {
        println("これ以上の入金は不要です。")
    }

    fun errorMessage() {
        println("必要な金額が投入されていません。")
    }

    fun insertMoney(m: Int) {
        println("お金を投入しました。")
        money += m
        if (money >= 100) {
            state = sufficient
        }
    }

    fun buy() {
        println("飲み物を取り出し口からお取りください。")
        state = lack
    }
}

使う人

Client.kt
package state

class Client {
    init {
        val v: Context = VendingMachine()
        v.coin()
        v.push()
        v.coin()
        v.coin()
        v.push()
    }
}

出力結果

[out-put]
お金を投入しました。
必要な金額が投入されていません。
お金を投入しました。
これ以上の入金は不要です。
飲み物を取り出し口からお取りください。

Context型のVendingMachineクラスのインスタンスを生成するFactoryクラスを実装するとよりよくなると思います。

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

SpringでCookieを取得する

よくあるやり方は、HttpServletRequestからgetCookiesしてそこから取り出すというやり方でした。
以下のような形です。

CookieController.java
    @GetMapping("cookies3")
    public String showCookies3(HttpServletRequest httpServletRequest) {
        Cookie[] cookies = httpServletRequest.getCookies();
        Arrays.stream(cookies).forEach(cookie -> System.out.println(cookie.getValue()));
        return VIEW_NAME;
    }

ただ、Springを利用するともう少し簡単に取得できます。

Cookieの生成

適当にCookieをセットします。

CookieController.java
    private final String VIEW_NAME = "demo/cookie/index";

    @GetMapping("/")
    public String index(HttpServletResponse httpServletResponse) {
        httpServletResponse.addCookie(new Cookie("key1", "value1"));
        return VIEW_NAME;
   }

Cookieの取得

以下のようにCookieValueアノテーションを利用することで簡単に取得できます。引数はCookieのキー値です。

CookieController.java
    private final String VIEW_NAME = "demo/cookie/index";

    @GetMapping("cookies1")
    public String showCookies1(@CookieValue("key1") String cookieValue) {
        System.out.println(cookieValue);
        return VIEW_NAME;
    }

ただし、上記の書き方だとCookieが削除されるなどして取得できない場合は実行時エラーとなります。
※Resolved [org.springframework.web.bind.MissingRequestCookieException: Missing cookie 'key1' for method parameter of type String])

そのため、以下のように@CookieValueのrequiredをfalseにする(デフォルトはrequired = true)ことで回避可能となります。

ただしこの場合は引数にはnullがセットされるため、defaultValue属性を指定することで、Cookieから取得できない場合はデフォルト値を指定することが可能となります。

CookieController.java
    private final String VIEW_NAME = "demo/cookie/index";

    @GetMapping("cookies2")
    public String showCookies2(
            @CookieValue(name = "key1", required = false, defaultValue = "default value1") String cookieValue) {
        System.out.println(cookieValue);
        return VIEW_NAME;
    }

以上です。

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

C言語の asctime 関数がフォーマットする日時文字列を Java でパースする

概要

  • C言語の asctime 関数がフォーマットする日時文字列を Java でパースする
  • java.time.format.DateTimeFormatter クラスを使用する

asctime 関数とは

asctime 関数は、時刻情報を人が読みやすい形式の日時文字列に変換する関数。

asctime() — 時間から文字ストリングへの変換

asctime() 関数は 24 時間クロック形式を使用します。曜日は、Sun、Mon、Tue、Wed、Thu、Fri、および Sat に省略されます。月は、Jan、Feb、Mar、Apr、May、Jun、Jul、Aug、Sep、Oct、Nov、および Dec に省略されます。すべてのフィールドには固定幅があります。一桁しかない日付は、ゼロまたはブランク・スペースが その前に置かれます。改行文字 (\n) および NULL 文字 (\0) がストリングの最後の位置を占めます。

asctime 関数は以下のようなアルゴリズムで文字列をフォーマットしている。

asctime

The asctime() function converts the broken-down time in the structure pointed to by timeptr into a string in the form:

Sun Sep 16 01:03:52 1973\n\0

using the equivalent of the following algorithm:

char *asctime(const struct tm *timeptr)
{
    static char wday_name[7][3] = {
        "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
    };
    static char mon_name[12][3] = {
        "Jan", "Feb", "Mar", "Apr", "May", "Jun",
        "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
    };
    static char result[26];

    sprintf(result, "%.3s %.3s%3d %.2d:%.2d:%.2d %d\n",
        wday_name[timeptr->tm_wday],
        mon_name[timeptr->tm_mon],
        timeptr->tm_mday, timeptr->tm_hour,
        timeptr->tm_min, timeptr->tm_sec,
        1900 + timeptr->tm_year);
    return result;
}

C言語による asctime 関数を使用して日時文字列を出力するサンプルコード

ソースコード。

#include <time.h>
#include <stdio.h>

int main(void) {

  time_t my_time;
  struct tm *my_local_time;
  char *str;

  /* 現在時刻(1970年1月1日00:00:00から数えた秒数)を取得 */
  time(&my_time);

  /* ローカル日時に変換 */
  my_local_time = localtime(&my_time);

  /* 人が読める形式の日時文字列に変換 */
  str = asctime(my_local_time);

  printf("%s", str);
}

ソースコードをコンパイル。

$ gcc mytime.c 

実行結果。

$ ./a.out 
Sun Dec  1 15:27:08 2019

Java による asctime 関数フォーマット日時文字列をパースするサンプルコード

ソースコード。

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
import java.util.Date;
import java.util.Locale;

public class Asctime {

  // ANSI C asctime 関数が出力する日時文字列を解析するフォーマッター
  public static final DateTimeFormatter ASCTIME_DATE_TIME
    = DateTimeFormatter.ofPattern("EEE MMM ppd HH:mm:ss yyyy", new Locale("en", "US"));

  public static void main(String[] args) {

    // asctime 関数が出力する日時文字列と同様のフォーマット文字列を用意
    String[] list = {
      "Sun Nov 18 12:34:56 2007",
      "Sun Dec  1 15:27:08 2019"
    };

    for (String text : list) {
      System.out.println("asctime: " + text);

      // 日時文字列をパースして日時情報に変換
      TemporalAccessor ta = ASCTIME_DATE_TIME.parse(text);
      System.out.println("TemporalAccessor: " + ta);

      LocalDateTime ldt = LocalDateTime.from(ta);
      System.out.println("LocalDateTime: " + ldt);

      // 日本のローカル時間として処理
      ZonedDateTime zdt = ZonedDateTime.of(ldt, ZoneId.of("Asia/Tokyo"));
      System.out.println("ZonedDateTime: " + zdt);

      Instant instant = zdt.toInstant();
      System.out.println("Instant: " + instant);

      Date date = Date.from(instant);
      System.out.println("Date: " + date);

      System.out.println();
    }
  }
}

ソースコードをコンパイル。

$ javac Asctime.java 

実行結果。

$ java Asctime
asctime: Sun Nov 18 12:34:56 2007
TemporalAccessor: {},ISO resolved to 2007-11-18T12:34:56
LocalDateTime: 2007-11-18T12:34:56
ZonedDateTime: 2007-11-18T12:34:56+09:00[Asia/Tokyo]
Instant: 2007-11-18T03:34:56Z
Date: Sun Nov 18 12:34:56 JST 2007

asctime: Sun Dec  1 15:27:08 2019
TemporalAccessor: {},ISO resolved to 2019-12-01T15:27:08
LocalDateTime: 2019-12-01T15:27:08
ZonedDateTime: 2019-12-01T15:27:08+09:00[Asia/Tokyo]
Instant: 2019-12-01T06:27:08Z
Date: Sun Dec 01 15:27:08 JST 2019

参考資料

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

Junit5の整理した書き方

過去に実践していたJunit5のテストクラスの書き方を備忘録として書き起こします。
皆さんの参考になれば幸いです。

テストクラス置き場

まず、はじめにテストクラスの置き場所です。
スクリーンショット 2019-12-01 12.36.08.png

テストクラスは、プロダクトのクラスとフォルダを分けて置きましょう。
Gradleを使用する場合、src/test/javaが標準フォルダです。

パッケージ名

テストクラスのパッケージ名は、プロダクトのクラスと同じ名称は避けましょう。
しかし、テストクラスとの紐付けは絶対にしたいところです。
なので、「test + テスト対象のパッケージ名」という形が頭使わずにすむかなと思います。

パッケージ名を別にしている理由は、Springを使用した場合、
テストクラスでのDI重複とか対策です。(確か)
同じパッケージ名だと、testフォルダをDIしてテストしたいクラスのDIができなかったので、
パッケージ名の頭から区別できるようにしています。

クラス名

これも頭使わずに、「テスト対象のクラス名 + Test」にしましょう。
同じクラス名だと補完の時にめんどくさいです。

テスト書き方

テスト対象クラス

Greeting.java
public class Greeting {

  public String hello() {    
    return "Hello";
  }

  public String helloWorld() {
    return "Hello World";
  }
}

テストクラス

GreetingTest.java
public class GreetingTest {

  @Nested
  @DisplayName("method : hello")
  public class Hello {

    private Greeting test = new Greeting();

    @Test
    @DisplayName("正常に動作するテストケース")
    public void case1() {
      Assertions.assertEquals("Hello", test.hello());
    }
  }

  @Nested
  @DisplayName("method : helloWorld")
  public class HelloWold {

    private Greeting test = new Greeting();

    @Test
    @DisplayName("正常に動作するテストケース")
    public void case1() {
      Assertions.assertEquals("Hello World", test.helloWorld());
    }

  }
}

メソッドごとにクラスを分ける

テストクラスでは見やすいように、メソッドごとにテストクラスを分けています。
テストケースを追加する時に、この形がテストケースを追加しやすいです。

インナークラスの名前は、メソッド名がわかりやすいです。

DisplayNameでしっかりテストケースをかく

経験上、テストケースのメソッド名を長くなったりするか、適当になったりして
ひどく読みにくくなります。
そのため、長ったらしいメソッド名をつけるぐらいなら、DisplayNameを使ってしっかりと
テスト内容を書きましょう。

Eclipseのテスト結果確認サンプル

Displayをかくと、カバレッジの画面に反映されるので、よりわかりやすくなります。
スクリーンショット 2019-12-01 12.57.44.png

まとめ

テストクラスは、内容も大事ですけど、同じぐらい整理して書くことも大事です。
形式立てて書くことで、開発の時だけでなく、保守の時も変更しやすいテストクラスを書いて行きましょう。

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

IntelliJ IDEAとGradleでのJavaFXアプリケーション開発 〜環境構築からサンプルコードまで〜

0.はじめに

いま、筆者は絶賛GUIツールを使ったソフトウェアを作る必要に迫られています。
ずっと放置していて最近着手したのですが、思いの外環境構築に手間取ったので備忘録を兼ねてここに書いておきます。

1.JavaFXって何ぞ?

「JavaでGUIと言ったらAWT!Swing!」と思っている方はいませんか?
実は最近、JavaFXというフレームワークが出てきてかなりGUIツール開発のハードルが下がったと聞いています(聞いているだけ)。

筆者はたまたま、「あー、GUIかー、どうやって作るんだろうなー」とネットをさまよっていたら見つけました。
今日はそんな便利そうなJavaFXの入り口を少しだけご紹介します。

2.筆者の環境

  • MacBook Pro (Early 2015)
  • macOS Mojave (10.14.6)
  • IntelliJ IDEA Community Edition (2018.3.1)
  • OpenJDK (13.0.1)

3.環境構築

3.1.OpenJDKのインストール

3.1.1.ダウンロード

OpenJDKのサイトから自分が使いたいバージョンのJDKをダウンロードします。
ただし、JavaFX側がJDK11以上を必要とするので注意。

■上記のサイト
191121-0001.png
この画像の真ん中あたり、macOS/x64の右にあるtar.gzのリンクからダウンロードします。
私は13.0.1を使いました。

3.1.2.インストール

ダウンロードしたtar.gzファイルを展開し、以下のディレクトリに移動します。
/Library/Java/JavaVirtualMachines

/
└── Library
    └── Java
        └── JavaVirtualMachines
            └── jdk-13.0.1.jdk
                └── Contents
                    └── ...

確認として以下のコマンドを実行します。

$ /usr/libexec/java_home -V
Matching Java Virtual Machines (2):
    13.0.1, x86_64: "OpenJDK 13.0.1"    /Library/Java/JavaVirtualMachines/jdk-13.0.1.jdk/Contents/Home
    11.0.2, x86_64: "OpenJDK 11.0.2"    /Library/Java/JavaVirtualMachines/jdk-11.0.2.jdk/Contents/Home

/Library/Java/JavaVirtualMachines/jdk-13.0.1.jdk/Contents/Home

実行結果が全く同じでなくても構いません。
今インストールしたバージョンが認識されているか確認してください。

3.2.プロジェクトのセットアップ

3.2.1.プロジェクトの作成

IntelliJ IDEAを立ち上げ、Create New Projectから新しいプロジェクトを作ります。
今回はGradleを使ってJavaFXを利用するので、もちろんGradleのプロジェクトを作りましょう。

■プロジェクト作成画面
191121-0002.png
Project SDKは13になるように設定をしておいてください。

次の画面でGroupId, ArtifactIdを指定します。

さらに次の画面はプロジェクトの設定をする場所ですが、私はここは基本的にいじりません。
Gradle JVMが13になっていることを確認するだけでいいと思います。

その次の画面で使用するフォルダを指定したらプロジェクトが作成されます。
諸々の処理が終わるまで少し待ちましょう。

3.2.2.build.gradleの設定

※以下、私のプロジェクトでのフォルダ構成で書きます。
フォルダ構成によって記述が変わる箇所は下の方にまとめて書きます。

build.gradleに以下の部分を追記します。
書いていないところは何も編集していません。

build.gradle
plugins {
    id 'application'
    id 'org.openjfx.javafxplugin' version '0.0.8'
}

dependencies {
    //このruntimeOnly群はクロスプラットフォーム対応のjarを作るためのものです。
    //不必要なものは消すとjarファイルのサイズを小さくできます。
    runtimeOnly "org.openjfx:javafx-base:$javafx.version:win"
    runtimeOnly "org.openjfx:javafx-base:$javafx.version:linux"
    runtimeOnly "org.openjfx:javafx-base:$javafx.version:mac"
    runtimeOnly "org.openjfx:javafx-controls:$javafx.version:win"
    runtimeOnly "org.openjfx:javafx-controls:$javafx.version:linux"
    runtimeOnly "org.openjfx:javafx-controls:$javafx.version:mac"
    runtimeOnly "org.openjfx:javafx-fxml:$javafx.version:win"
    runtimeOnly "org.openjfx:javafx-fxml:$javafx.version:linux"
    runtimeOnly "org.openjfx:javafx-fxml:$javafx.version:mac"
    runtimeOnly "org.openjfx:javafx-graphics:$javafx.version:win"
    runtimeOnly "org.openjfx:javafx-graphics:$javafx.version:linux"
    runtimeOnly "org.openjfx:javafx-graphics:$javafx.version:mac"
}

javafx {
    version = "13"
    modules = [ 'javafx.controls', 'javafx.fxml' ]
}

mainClassName = 'maru.test.Launcher'
jar {
    manifest {
        attributes 'Main-Class': 'maru.test.Launcher'
    }
    from {
        configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
    }
}

■今こんな感じ(下の方ちょっと切れてます)
191122-0003.png

3.2.3.サンプルコードの実装

サンプルコードはOpenJFXの公式さんからお借りしました。

src/main/java/maru/test/Launcher.java
package maru.test;

import javafx.application.Application;

public class Launcher {
    public static void main(String... args){
        Application.launch(MainApp.class);
    }
}
src/main/java/maru/test/MainApp.java
package maru.test;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;


public class MainApp extends Application {

    @Override
    public void start(Stage stage) throws Exception {
        Parent root = FXMLLoader.load(getClass().getResource("scene.fxml"));

        Scene scene = new Scene(root);
        scene.getStylesheets().add(getClass().getResource("styles.css").toExternalForm());

        stage.setTitle("JavaFX and Gradle");
        stage.setScene(scene);
        stage.show();
    }
}
src/main/java/maru/test/FXMLController.java
package maru.test;

import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;

import java.net.URL;
import java.util.ResourceBundle;

public class FXMLController implements Initializable {

    @FXML
    private Label label;

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        String javaVersion = System.getProperty("java.version");
        String javafxVersion = System.getProperty("javafx.version");
        label.setText("Hello, JavaFX " + javafxVersion + "\nRunning on Java " + javaVersion + ".");
    }
}
src/main/resources/maru/test/scene.fxml
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.StackPane?>


<StackPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="maru.test.FXMLController">
    <children>
        <Label fx:id="label" text="Label" />
    </children>
</StackPane>
src/main/resources/maru/test/styles.css
.label {
    -fx-text-fill: blue;
}

■こんな感じ
191122-0004.png

3.2.4.コンパイル、実行

右上の方にあるGradleをクリック、Tasks>build>buildを実行します。
それが終わったら少し上のTasks>application>runを実行します。

■これです
191122-0005.png
こんな感じのが出てきたら成功です!

■「こんな感じの」
191121-0006.png
build/libs/にjarファイルができていると思います。
このjarファイルはfatjar(ライブラリとかも含んでいるjarファイル)なので、誰のPCでも動くはずです。

3.2.5.フォルダ構成によって記述が変わる場所

<>で囲まれている場所をよしなに変えてください。
フォルダ構成に左右される箇所だけ抜き出しています。

ファイル名
build.gradle 2箇所
Launcher.java 1箇所
MainApp.java 2箇所
scene.fxml 1箇所
build.gradle
mainClassName = '<Applicationクラスを継承していないランチャー用のクラス>'
jar {
    manifest {
        attributes 'Main-Class': '<上のmainClasNameと同じ>'
    }
}
Launcher.java
public static void main(String... args){
    Application.launch(<Applicationクラスを継承しているクラス>.class);
}
MainApp.java
@Override
public void start(Stage stage) throws Exception {
    Parent root = FXMLLoader.load(getClass().getResource("<fxmlファイル>"));
    scene.getStylesheets().add(getClass().getResource("<cssファイル>").toExternalForm());
}
scene.fxml
<StackPane (中略) fx:controller="<Controllerクラス>">

おそらく以上です。見落としがあったらごめんなさい。。。

4.最後に

どうでしたか?思ったより簡単だったのではないでしょうか?

ちなみに、画面の構築にはSceneBuilder(Oracleサイト)が便利です。
IntelliJ IDEAの設定をすればこんなこともできちゃいます。

■こんなこと
191122-0001.png
これでGUIアプリケーション開発のハードルがグッと下がったのではないでしょうか。
皆さんもよいGUIアプリケーションライフをお楽しみください!

5.参考文献

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

thymeleafでenumを扱うためのtips

今年になってSpring bootを使う案件に関わることがあり、thymeleafやenumについての得られた知見を整理してみました。

「Thymeleaf enum」でググるとよくでてくるもの

SpringやSpring bootを使うと、テンプレートエンジンでThymeleafを使うのが通例でしょう。
入力フォームのセレクトボックスの値や表示名を、enumを使って管理する、というのも、よくあることでしょう。

https://www.baeldung.com/thymeleaf-enums より抜粋ですが、ググると下記のような例がそこそこ見かけます。

<select name="color">
    <option th:each="colorOpt : ${T(com.baeldung.thymeleaf.model.Color).values()}"
        th:value="${colorOpt}" th:text="${colorOpt.displayValue}"></option>
</select>

初期値の判定などで、特定のenumかどうかを判定する場合の例として、こういうのもあります。

<div th:if="${widget.color == T(com.baeldung.thymeleaf.model.Color).RED}">

このやり方で難がある点

よく見かけるのですが、これがベストかと言われると、なんともいえないです。

  1. 型のFQDNの部分が読みづらい (長い割に情報量がない)
  2. パッケージ構成を見直そうとすると、テンプレートまで改修しなければならない
  3. enumのリネームでも、テンプレートまで改修しなければならない

要は、テンプレートとは関係ないものが入ってくるため、可読性や保守性に悪影響がでています。
とりあえず動くものを作るなら、このやり方で問題ないですが、少々つらいです。

どうするとよいか

基本的には、テンプレートで使うものをenumに実装するのがよいです。
また、テンプレートから直接呼び出すよりも、必要なインスタンスをControllerから渡す形に変えるとよいです。

enumの全要素を使う場合(セレクトボックスなど)

Javaのenumには、暗黙的なpublic static T[] values()というメソッドがあります。
全要素を列挙するには、これを利用するといいです。(定番といえば、定番ネタです。)

public enum UserStatus {
   ACTIVE(1, "有効"),
   INACTIVE(0, "無効"),
   PENDING(2, "保留"),
   FORBIDDEN(3, "強制停止")
   ;

   private final int value;

   private final String viewName;

   private UserStatus(int value, String viewName) {
       this.value = value;
       this.viewName = viewName;
   }

   public int getValue() {
       return this.value;
   }

   public String getViewName() {
       return this.viewName;
   }

}
@Controller
public class SampleController

@RequestGetMapping
public ModelAndView index() {
   ModelAndView model = new ModelAndView();
   model.setAttribute("statusList", UserStatus.values());
   return model;
}

<select name="status">
    <option th:each="status : statusList}" 
            th:value="${status.value}" th:text="${status.viewName}">
    </option>
</select>

特定のenumの判定

これはenumにisXXXというメソッドを足すやり方があります。

public enum UserStatus {
   // ...

   public boolean isActive() {
       return this == ACTIVE;
   }

   public boolean isForbidden() {
       return this == FORBIDDEN;
   }

}
<div th:if="${status.isForbidden()}">
    This user is forbidden.
</div>

このisXXXメソッドは、テンプレートだけでなく、ビジネスロジックを書く場面でも有効です。
たとえば、有効なユーザだけに絞り込むなら、こんな風になります。

public class User {

  //...

  private final UserStatus status;

  public UserStatus getStatus() {
     return this.status
  }

}
   List<User> users = ...
   List<User> activeUsers = users.stream()
                                 .filter(x -> x.getStatus().isActive())
                                 .collect(Collectors.toList());

まとめ

ぐぐって、ベストプラクティスが見つかることもあれば、そうでないこともあります。
また、検索して見つかった方法が自分の現状にあっているか、その判断基準も検索してみつかることもあまりないです。
テンプレートはいろんな関心事が入りやすいので、実装するときにはいろんな判断に迫られます。

参考リンク

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

SpringBootでWebアプリケーション開発を始めてみる

概要

  • SpringBootで簡易的なWebアプリをつくってみる
  • この記事では、まずはHello World的なものを
  • 開発未経験の人でも気軽に始められる

そもそもSpringBootって何?

  • Java開発におけるフレームワークの1つ
  • 必要な初期設定が少なく、使い始めやすいことが特徴

開発環境

環境 サービス/バージョン
OS Mac
言語 Java8
IDE(統合開発環境) IntelliJ IDEA
フレームワーク SpringBoot 2.2.1
  • 開発環境には必ずJavaのインストールが必要
  • IDEはなんでもOK(Eclipseとか)

雛形作成

Spring Initializrを利用する

  • SpringBootの雛形アプリケーションを作成することができるWebサービス
  • ビルドツール(Maven/Gradle)や言語(Java/Kotlin/Groovy)を選択することが可能
  • https://start.spring.io/

手順

以下を指定する

  • ビルドツール
    • どちらもよく利用されているのでどちらでも良いが、今回はGradleにする
  • 言語
  • SpringBootのバージョン
  • プロジェクト名
    • なんでも良い。今回はspring-studyとする
  • 利用ライブラリ
    • 後からでも追加可能
    • (今回は不要だが)試しにSpring Webを追加している

01-initializr.png

  • Generateボタンを押下すると、雛形アプリがzipでダウンロードできる

雛形アプリを確認してみる

02-app.png

  • SpringStudyApplication.javaapplication.propertiesといったファイルがデフォルトで作成されている
  • SpringStudyApplicationTests.javaは不要なので削除する

Hello Worldをやってみる

  • 雛形のままだと動かないので、まずはHello World
  • http://localhost:8080/hello にアクセスすると、Hello World!と表示されるようにする

手順

  • リクエストを受け付けて、文字列を返却するクラスをつくる

HelloRestController.java

  • com.examle.springstudyHelloRestController.javaを作成する
com.examle.springstudy.HelloRestController.java
package com.example.springstudy;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController // ①
public class HelloRestController {
    @GetMapping("/hello") // ②
    public String hello() {
        return "Hello World!";
    }
}
  • 2つのアノテーションがポイント(@XXX)
  • ①:リクエストを受け付け、レスポンスを返却するコントローラであることを示す
  • ②:リクエストURLとロジックを紐づける

動作確認

  • 起点となるクラス(SpringStudyApplication)を選択して、実行(⌃+⌥+R)する

02-exec.png

  • 起動すると以下のような内容がコンソールに出力される
11:46:44: Executing task 'bootRun'...

> Task :compileJava
> Task :processResources
> Task :classes

> Task :bootRun

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.2.1.RELEASE)

2019-12-01 11:46:59.590  INFO 43520 --- [           main] c.e.springstudy.SpringStudyApplication   : Starting SpringStudyApplication on MacBookPro with PID 43520 (/Users/tanibuchi/Desktop/spring-study/spring-study/build/classes/java/main started by tanibuchi.kosuke in /Users/tanibuchi/Desktop/spring-study/spring-study)
2019-12-01 11:46:59.595  INFO 43520 --- [           main] c.e.springstudy.SpringStudyApplication   : No active profile set, falling back to default profiles: default
2019-12-01 11:47:00.598  INFO 43520 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2019-12-01 11:47:00.612  INFO 43520 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2019-12-01 11:47:00.612  INFO 43520 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.27]
2019-12-01 11:47:00.680  INFO 43520 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2019-12-01 11:47:00.680  INFO 43520 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1037 ms
2019-12-01 11:47:00.915  INFO 43520 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2019-12-01 11:47:01.089  INFO 43520 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2019-12-01 11:47:01.098  INFO 43520 --- [           main] c.e.springstudy.SpringStudyApplication   : Started SpringStudyApplication in 2.169 seconds (JVM running for 3.005)
  • 最後にStartedが表示されていれば正常に起動できている
  • http://localhost:8080/hello にアクセスすると、以下の画面が表示される

03-helloworld.png

最後に

  • こんな感じでSpringBootを使ったアプリ開発は気軽に始めることができる
  • 今回は概要レベルでの記載なので、次回もう少し丁寧に解説します
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Javaで簡単なソートを一つかけてみる

やりたいこと

ある自作のプログラムにデータを渡し、そのプログラムにソートさせてみて、性能テストをしてみます。その時ソートする関数やライブラリの類は一切使わないで作ります。

なぜやろうと思ったか

理由は単純、塾の友達とソートの数学的なアルゴリズムについて語ってたら作ってみたくなってきたから組んでみます!

アルゴリズム

与えられた配列変数に対して、k番目の要素とk+1番目の要素を比較して、もしもk+1番目の要素のほうが小さければ2つの値を入れ替えます。この操作を繰り返すことで、最終的には配列変数の配列の順序で小さい方からデータが並びます。

具体的なコード

データを大量に用意してテストしたいので、今回は現在時刻(ミリ秒)をシードとして乱数を生成し、それを実験用データとして使うことにしました。Javaで実装します。

class
public static int[] sortonce(int[] data){
        int temp=0;
        int number=data.length;
        int tmp0=0;
        int tmp1=0;
        for(int cycle=0;cycle+1<number;){
            tmp0=data[cycle];
            tmp1=data[cycle+1];
            if (tmp0>tmp1){
                temp=data[cycle];
                data[cycle]=data[cycle+1];
                data[cycle+1]=temp;
            }cycle++;
        }
        return data;
    }

上記のコードにある、sortonceという関数にint型配列変数を渡すと、一周分k項目とk+1項目を比較します。
この関数を何度も実行し続けることによって、いつかは1項目が一番小さくなるはずです。
完全にやるべき処理が終了したかは次の関数で判別します。

chksorted
public static int sorted(int[] data){
        int number=data.length;
        for(int cycle=0;cycle<number-1;cycle++){
            if (data[cycle]>data[cycle+1]){
                return 0;
            }
        }return 1;
}

これに乱数の塊を渡し、ソートにかかる時間を計測してみましょう。
全体のプログラムは次のような形になります。

main
import java.util.Random;
import java.util.Scanner;

class sort{


    public static void main(String[] args){
        System.out.println("How many tests?");
        Scanner sc1 = new Scanner(System.in);
        int cycle=sc1.nextInt();
        System.out.println("How large data?");
        Scanner sc2=new Scanner(System.in);
        int large=sc2.nextInt();
        System.out.println();
        long data[]=new long[cycle+1];
        long sum=0;
        for(int cycle0=0;cycle0<cycle;cycle0++){
            data[cycle]=test(large);
            sum=sum+data[cycle];
        }
        long ave=sum/cycle;
        System.out.println();
        System.out.println("System calculated "+large+" size data in "+ave+" average Millis");
    }

    //Main
    public static long test(int large){

        //MainProcess-0;Create Random Data and Print them

        long startTime0 = System.currentTimeMillis();
        int data[]=data(large);
        long endTime0=System.currentTimeMillis();
        long Time0=endTime0-startTime0;
        System.out.println("Generated in "+Time0+"ms");

        //MainProcess-1;check if sorted
        if (sorted(data)==1){
            System.out.println("Already Sorted...");
        }

        //MainProcess-2;Sort and Evaluate
        long startTime1=System.currentTimeMillis();
        while(true){
            if(sorted(data) == 1){
                long endTime1=System.currentTimeMillis();
                long Time1=endTime1-startTime1;
                System.out.print("Sorted in "+Time1+"ms");
                System.out.println();
                System.out.println();
                return Time1;
            }
            data=sortonce(data);
        }
    }


    //Data-Generator
    public static int[] data(int number){
        int data[] = new int[number];
        long seed=System.currentTimeMillis();
        Random random=new Random(seed);
        for(int cycle=0;cycle<number;cycle++){
            data[cycle]=random.nextInt();
        }
        return data;
    }


    //Print current data
    public static void print(int[] data){
        for(int cycle=0;cycle<data.length;cycle++){
            System.out.println(data[cycle]+",");
        }
    }



    //sorted-chk
    public static int sorted(int[] data){
        int number=data.length;
        for(int cycle=0;cycle<number-1;cycle++){
            if (data[cycle]>data[cycle+1]){
                return 0;
            }
        }return 1;
    }


    public static int[] sortonce(int[] data){
        int temp=1;
        int number=data.length;
        int tmp0=0;
        int tmp1=0;
        for(int cycle=0;cycle+1<number;){
            tmp0=data[cycle];
            tmp1=data[cycle+1];
            if (tmp0>tmp1){
                temp=data[cycle];
                data[cycle]=data[cycle+1];
                data[cycle+1]=temp;
            }cycle++;
        }
        return data;
    }


}

このJavaを実行すると、テストに使う乱数の塊が入った配列変数の塊の大きさとテストする回数を聞かれますが、ここで大きめの値を指定してみてテストしてみましょう。
全て10回テストし、平均値を算出します。
(測定環境:Xperia XZ3)

乱数の大きさ 所要時間(ms)(平均値)
1000 4
2000 10
3000 25
4000 44
5000 66
6000 93
7000 129
8000 167
9000 201
10000 260
11000 302
12000 358
13000 428
14000 493
15000 560
16000 658
17000 734
18000 826
19000 915
20000 1020
21000 1120
22000 1223
23000 1341
24000 1453
25000 1587
26000 1716
27000 1856
28000 1999
29000 2136
30000 2275

感想

ソートするデータの量が少ないうちはこの方法で十分成り立つが、データの量が増えてくるときはもっと賢い方法を考えたほうがよさそうだ。何らかの方法でマルチスレッド化を目指すのも面白いかもしれない。

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

JAVAで、簡単なソートを一つ組んでみる。

やりたいこと

ある自作のプログラムにデータを渡し、そのプログラムにソートさせてみて、性能テストをしてみます。その時ソートする関数やライブラリの類は一切使わないで作ります。

なぜやろうと思ったか

理由は単純、塾の友達とソートの数学的なアルゴリズムについて語ってたら作ってみたくなってきたから組んでみます!

アルゴリズム

与えられた配列変数に対して、k番目の要素とk+1番目の要素を比較して、もしもk+1番目の要素のほうが小さければ2つの値を入れ替えます。この操作を繰り返すことで、最終的には配列変数の配列の順序で小さい方からデータが並びます。

具体的なコード

データを大量に用意してテストしたいので、今回は現在時刻(ミリ秒)をシードとして乱数を生成し、それを実験用データとして使うことにしました。Javaで実装します。

class
public static int[] sortonce(int[] data){
        int temp=0;
        int number=data.length;
        int tmp0=0;
        int tmp1=0;
        for(int cycle=0;cycle+1<number;){
            tmp0=data[cycle];
            tmp1=data[cycle+1];
            if (tmp0>tmp1){
                temp=data[cycle];
                data[cycle]=data[cycle+1];
                data[cycle+1]=temp;
            }cycle++;
        }
        return data;
    }

上記のコードにある、sortonceという関数にint型配列変数を渡すと、一周分k項目とk+1項目を比較します。
この関数を何度も実行し続けることによって、いつかは1項目が一番小さくなるはずです。
完全にやるべき処理が終了したかは次の関数で判別します。

chksorted
public static int sorted(int[] data){
        int number=data.length;
        for(int cycle=0;cycle<number-1;cycle++){
            if (data[cycle]>data[cycle+1]){
                return 0;
            }
        }return 1;
}

これに乱数の塊を渡し、ソートにかかる時間を計測してみましょう。
全体のプログラムは次のような形になります。

main
import java.util.Random;
import java.util.Scanner;

class sort{


    public static void main(String[] args){
        System.out.println("How many tests?");
        Scanner sc1 = new Scanner(System.in);
        int cycle=sc1.nextInt();
        System.out.println("How large data?");
        Scanner sc2=new Scanner(System.in);
        int large=sc2.nextInt();
        System.out.println();
        long data[]=new long[cycle+1];
        long sum=0;
        for(int cycle0=0;cycle0<cycle;cycle0++){
            data[cycle]=test(large);
            sum=sum+data[cycle];
        }
        long ave=sum/cycle;
        System.out.println();
        System.out.println("System calculated "+large+" size data in "+ave+" average Millis");
    }

    //Main
    public static long test(int large){

        //MainProcess-0;Create Random Data and Print them

        long startTime0 = System.currentTimeMillis();
        int data[]=data(large);
        long endTime0=System.currentTimeMillis();
        long Time0=endTime0-startTime0;
        System.out.println("Generated in "+Time0+"ms");

        //MainProcess-1;check if sorted
        if (sorted(data)==1){
            System.out.println("Already Sorted...");
        }

        //MainProcess-2;Sort and Evaluate
        long startTime1=System.currentTimeMillis();
        while(true){
            if(sorted(data) == 1){
                long endTime1=System.currentTimeMillis();
                long Time1=endTime1-startTime1;
                System.out.print("Sorted in "+Time1+"ms");
                System.out.println();
                System.out.println();
                return Time1;
            }
            data=sortonce(data);
        }
    }


    //Data-Generator
    public static int[] data(int number){
        int data[] = new int[number];
        long seed=System.currentTimeMillis();
        Random random=new Random(seed);
        for(int cycle=0;cycle<number;cycle++){
            data[cycle]=random.nextInt();
        }
        return data;
    }


    //Print current data
    public static void print(int[] data){
        for(int cycle=0;cycle<data.length;cycle++){
            System.out.println(data[cycle]+",");
        }
    }



    //sorted-chk
    public static int sorted(int[] data){
        int number=data.length;
        for(int cycle=0;cycle<number-1;cycle++){
            if (data[cycle]>data[cycle+1]){
                return 0;
            }
        }return 1;
    }


    public static int[] sortonce(int[] data){
        int temp=1;
        int number=data.length;
        int tmp0=0;
        int tmp1=0;
        for(int cycle=0;cycle+1<number;){
            tmp0=data[cycle];
            tmp1=data[cycle+1];
            if (tmp0>tmp1){
                temp=data[cycle];
                data[cycle]=data[cycle+1];
                data[cycle+1]=temp;
            }cycle++;
        }
        return data;
    }


}

このJavaを実行すると、テストに使う乱数の塊が入った配列変数の塊の大きさとテストする回数を聞かれますが、ここで大きめの値を指定してみてテストしてみましょう。
全て10回テストし、平均値を算出します。
(測定環境:Xperia XZ3)

乱数の大きさ 所要時間(ms)(平均値)
1000 4
2000 10
3000 25
4000 44
5000 66
6000 93
7000 129
8000 167
9000 201
10000 260
11000 302
12000 358
13000 428
14000 493
15000 560
16000 658
17000 734
18000 826
19000 915
20000 1020
21000 1120
22000 1223
23000 1341
24000 1453
25000 1587
26000 1716
27000 1856
28000 1999
29000 2136
30000 2275

感想

ソートするデータの量が少ないうちはこの方法で十分成り立つが、データの量が増えてくるときはもっと賢い方法を考えたほうがよさそうだ。何らかの方法でマルチスレッド化を目指すのも面白いかもしれない。

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

IDEやMavenはすべてのデバッグ情報を含めてコンパイルしてくれている

javacには-gというオプションがあります。これはクラスファイルへデバッグ情報を含めたり、逆に含めない設定をするためのオプションです。

  • -g:linesとすればソースコードの行番号を含めます
  • -g:varsとすればローカル変数名の情報を含めます
  • -g:sourceとすればソースファイル名を含めます
  • -gとすればすべてのデバッグ情報を含めます
  • -g:noneとすればデバッグ情報を何も含めません

次のようなコードをjavac -g Hello.javaしたとします。

public class Hello {
    public String say(String name) {
        return "hello ".concat(name);
    }
}

それからjavap -l Helloすると次のようになります(-lは行番号とローカル変数表を出力するオプションです)。

Compiled from "Hello.java"
public class Hello {
  public Hello();
    LineNumberTable:
      line 1: 0
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0       5     0  this   LHello;

  public java.lang.String say(java.lang.String);
    LineNumberTable:
      line 3: 0
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0       7     0  this   LHello;
          0       7     1  name   Ljava/lang/String;
}

LineNumberTableに行番号が、LocalVariableTableにローカル変数名が、そしてCompiled from 〜という箇所にソースファイル名が含まれています。

javac -g:none Hello.javaの場合はどうなるか。javap -l Helloしたものがこちらです。

public class Hello {
  public Hello();

  public java.lang.String say(java.lang.String);
}

デバッグ情報は何も含まれていません。

我々はふだん明示的にjavacをすることはなくEclipseやIntelliJ IDEAのようなIDEや、Mavenを使用してコンパイルしています。でも特に気にすることなくデバッグ時には対応するソースコードが勝手に開いて変数名を見ながらステップ実行ができるはずです。それはつまりIDEやMavenがデフォルトでデバッグ情報を含めてくれているということです。

Eclipseだとデバッグ情報を含めるかどうかを設定できます。デフォルトの状態をスクショしてみました。varslinessourceも含めるようになっていますね。

eclipse.png

IntelliJ IDEAは使用していないので知らないのですが、おそらく似たような設定ができるんじゃないかなと思っています。

Mavenもデバッグ情報を含めるかどうかをmaven-compiler-pluginのdebugで設定できます

ちなみにjavac Hello.javaとした場合はソースファイル名と行番号が含まれます。つまりデフォルトでもjavac -g:source,lines Hello.javaと同じということですね。

以上をまとめると、

  • javac-gオプションでクラスファイルにデバッグ情報を含める
  • デフォルトではソースファイル名と行番号が含まれる
  • IDEやMavenはデフォルトでソースファイル名と行番号に加えてローカル変数名も含んでくれる

ということになります。

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

[Java] Core Java Reading Memo

Keep result of mod be positive.

Math.floorMod(-1, 10); // 9

Quick-and-dirty list of the elements of a two-dimensional array

System.out.println(Arrays.deepToString(arrays));
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

授業で習ったJavaの復習をつらつらと

はじめに

これは自分が授業で習った内容の復習、またクラスメイトの手助けや改善策をつらつらと書いていくノートのような投稿です。Javaを用いた実践的なアプリを作ったり、すごい人たちのように「この関数は挙動が~だ!」みたいな知識は出てきません。悪しからず。あと100%鵜呑みにしないように。

  • 推奨環境:Visual Studio Code
    だってEclipse重いじゃん。

    拡張機能で「terminal」と「Language Support for Java(TM) by Red Hat」を入れるといいよ。それ以外は好み。

最初の一歩

まずは慣習のHello World!を。

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

Javaの最初、「public class Hello」は必ず必要な構文。おまじないだと思って。
今回大事なのは「System.out.println("Hello World!");」だ。このprintln関数を使うとコンソールに文字を出力できる。文字列を扱う場合は「""」で囲む必要がある。
ちなみに、print関数もある。構文を変えて実行しなおすとわかるが、Hello World!を出力したあとに改行をするかどうかの違いである。
また、Javaでは構文の終わりに「;」を入力する必要がある。入力しないと実行ファイル変換できずにエラーを吐いて終了するだけなので打って。

上の構文を入力し終わったら、terminalを開いて(VScodeなら右クリックで開けるよ)ターミナル上で「javac Hello.java」と入力。ファイルがある場所で打ってね。
Hello.classができたら、「java Hello」と入力。.classは打たなくてOK。無事ターミナルで挨拶出来たら次に進もう。

ここのまとめ

  • println関数
  • print関数

変数

Javaに限らず、コードを書くときはまず最初に変数を記述する(宣言)。だいたいどの言語でも同じようなものがでてくる。

java/Dec.java
public class Dec
{
    public static void main(String[] args)
    {
        int num; //整数型4バイトの変数
        num = 3; //値を代入
        short num2 = 15; //整数型2バイトの変数(同時に値を入れている)

        boolean aaaa; //trueもしくはfalse
        double bbbb; //8バイト倍精度浮動小数点数
        float cccc; //4バイト倍精度浮動小数点数
        char str; //2バイト文字
    }
}

まだあるけど割愛。授業であんまり使わないし...
宣言と同時に値を代入できるので分ける必要はない。

ここのまとめ

  • 基本的な型の宣言

型同士の計算/変換

ここでは単純な計算、int型をdouble型に変換したりする。
まずは計算

java/Math.java
public class Math
{
    public static void main(String[] args)
    {
        int num1 = 12;
        int num2 = 34;

        answer = num1 + num2; //プログラミングでの計算は順序が逆!
                                                   //計算した結果を代入しているため

        System.out.println(answer); //変数を出力する場合「""」はつけない
    }
}

実行すると当然46になる。
今回はint型同士の計算なので楽だったが、double型が混ざっていると実行できない。Javaでは違う型同士の計算はできないのだ。
これを回避する方法はint型をdouble型に変換するしかない。

java/Cast.java
public class Cast
{
    public static void main(String[] args)
    {
        int inum = 1;
        double dnum = 2.5;

        //下の行は実行できない
        //answer = inum + dnum;

        answer = (double)inum + dnum; //カッコで変換したい型を入力、計算すると実行できる!

        System.out.println(answer);
    }
}

実行結果は当然3.5だ。小数点以下の0がたくさん出るが仕様。
int型をshort型等、小さい型への代入は注意が必要。意図しない数字になるかもしれないので注意。

ここのまとめ

  • 型同士の計算
  • 型変換

if文/switch文

このあたりからプログラミングらしくなる。
if文はズバリ、「〜でなかったら...する」という文だ。自由に条件を設定できるので目的に沿った条件を書けるかがポイント。

java/If.java
public class If
{
    public static void main(String[] args)
    {
        int num = 4;

        if(num <= 5) //numが5以下だったら中の文を実行する
        {
            System.out.println("numの値は" + num + "なので5以下でした。");
            //文と型の値を同時に出力するときは+でつなぐ
        }
        else //elseで上のとき以外を実行する文が書ける
        {
            System.out.println("5より大きい数字だった");
        }
    }
}

if文は上から順番に条件にあっているか見ていくのに対し、switch文ではリストを作って見やすくできる。

java/Switch.java
public class Switch
{
    public static void main(String[] args)
    {
        int num = 2;

        switch(num)
        {
            case 0:
                System.out.println("numは0");
                break; //switch文の処理を終わらせる構文
            case 1:
            case 2: //break文を打たないと順番に実行してしまう仕様を逆手に取っている
            case 3:
                System.out.println("0 < num < 4");
                break;
            default:
                System.out.println("3より大きい(適当)"); //default文でどれにも当てはまらない条件の実行方法を記述できる
        }
    }
}

switch文とif文では「見やすい」くらいの違いしかないです。多分。
if文を長ったらしく書くならswitch文のほうがいいし、2つしかない条件ならわざわざswitch文を選ぶ必要はない。頭の中で追いかけやすい方の関数を使うのが妥当だろう。

ここのまとめ

  • if文
  • switch文

長くなると見るのも書くのもきついから分けます。part2とかにして順々に...

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