GitHubとクラスメソッドの勉強会に行ってきた #github_method

GitHubとクラスメソッドの勉強会に行ってきました。簡単に所感をまとめます。

classmethod.connpass.com

今回のテーマは「GitHub x AWS の最新 DevOps 事情」です。

  • DevOps はインフラ自動化やリリース自動化など、ひとによって捉え方は違う
  • 価値を無駄なく遅滞なく届けることが重要
  • かつてはスコープに対してスケジュールやリソースを決めていた
  • 近年はこの関係が逆転して、スケジュールとリソースがある上でスコープを決める
  • VCSによるコラボレーションをやりやすくする
    • 中央集権型から分散型へ
  • CI は必須のベストプラクティス
  • Continuous Delivery : 継続的にデリバリーする
  • Continuous Deployment : 継続的にデプロイする
  • GitHub の開発者は世界中にいる
    • それでも1日のデプロイは100回に及ぶことも
    • それを支えるのは GitHub FlowHubot (ChatOps)
  • master を常にデプロイ可能に保つ (本番とイコール)
    • 本番にデプロイして問題がないものを master にマージする
      1. master から feature を作ってコミットを加える
      2. PR を作って開発者みんなでレビューする
      3. レビューで問題がなければ PR ブランチを本番にデプロイする
      4. 本番で問題がなければ master にマージする
    • 本番にデプロイした PR ブランチに問題があれば master をデプロイする
  • master にマージしてからデプロイすると問題があった場合に master が不安定な状態になってしまう
  • 1回本番にデプロイされると、問題ないことが保証されるまでは次以降のデプロイはロックされてキューイングされる
  • Chat は Shared Console である
    • オープンなコンソール (みんなで見ることができる)
    • 誰がやっても同じ結果になる
    • ゆくゆくは AI が絡んでくるかな?

GitHub Flow は2011年頃に出てきたものですが、中のひとたちは割と早い段階で今の運用に変えていたようです。当時、日本にはそのあたりの情報があまりなかったため、池田さん自身もチーム開発実践入門を執筆した段階ではご存知なかったらしいです。
また、1回でデプロイされる変更ボリュームは数十行程度。敢えて小さくしている。1日のトータルは3000行くらいだが、一気にデプロイするのではなく、30行を100回に分けてデプロイするような感じ。とのこと。

www.slideshare.net

  • DevOps の目的はビジネスに使う時間を増やすこと
    • 決まりきった運用や手運用を自動化する
    • 障害対応の時間を減らす
  • CodeDeploy ではデプロイとプロビジョニングを混ぜないように注意
    • プロビジョニングは OS やミドルウェアのための設定
    • プロビジョニングとデプロイではライフサイクルが違う
    • CodeDeploy ではプロビジョニングはしない
  • AWS を使うならマネージドサービスを使うことを第一に検討する
    • 安定性やサービス連携、運用負荷を減らす観点から
    • 標準のサービスでは要件に合わない場合には自前で作る
  • GitHub Enterprise を使う場合は標準で提供している設定や機能を使う
    • アップデートで /data/user 以外が上書きされるため
    • 運用負荷を減らす

AWS については圧倒的経験不足のため、CodeStar をはじめ、多くの DevOps 関連のサービスが提供されていることを初めて知りました...。

正規表現メモ

CODEPREP で正規表現を学びつつ、練習問題の3パターンをメモる。

codeprep.jp

電話番号にマッチさせる

  • 電話番号は3ブロックの数字の並びで構成される
  • 各ブロックの桁数は 3桁、3桁 or 4桁、4桁
  • 各ブロックは - または ( or ) で区切られる
  • 先頭のブロックの数字は0で始まる
  • 先頭または末尾に空白や数字以外があることは許容しない
/^0\d{2}[-(]\d{3,4}[-)]\d{4}$/

URLからホスト名を抽出する

  • URLは http:// または https:// で始まる
  • ホスト名の後ろに / がない場合もある
/https?.{3}([^/]*)/

http:// または https:// で始まるということは、http の後ろに s があってもなくてもよいということと同義である。正規表現では https? で表す。

ホスト名は、http(s):// 以降で最初に現れる / より前の部分を抽出する。つまり、http(s):// 以降で / 以外の文字が続く限りキャプチャする。正規表現では ([^/]*) で表す。

HTML要素の属性値を抽出する

  • a 要素から href 属性の値をキャプチャする
  • = の前後には空白が入ることがある
  • 属性値は " または ' で括られる
  • href 以外の属性が定義されることがあり、順序は不定である
/<a\s*.*href\s*=\s*(["'])(.*?)\1.*/

空白があるかないかは、正規表現では \s* で表す。

シングルクォートとダブルクォートの両方に対応する場合は ["'] で表す。また、後ろのクォートは前のクォートと同一である必要があるため、前のクォートをキャプチャして \1 で後方参照する。キャプチャを複数定義するため、抽出結果は配列の2番目(\2)を取る。

href 以外の属性が存在し得るため、同じクォートが複数回現れることがある。キャプチャは (.*?) で最短マッチにする。

Cookpad Tech Kitchen #7 に行ってきた #cookpad_tech_kitchen

Cookpad Tech Kitchen #7 に行ってきました。簡単に所感をまとめます。

cookpad.connpass.com

今回のテーマは『理想の開発現場の「ふつう」のお話』です。資料はこちら。

  • 開発者もテストエンジニアも品質を向上するという共通の目的を持つ
    • テストの現場の理想形のひとつ
  • 日々の小さな会話がよいチームを作るのではないか
    • フレーズ→振る舞い→価値観
  • 「うまくいったらどうなるの?」
    • ゴールまでの仮説を立てる
    • うまく迷える (ゴールまでのステップが分かっていれば迷っても安心)
  • 「なんでやるんだっけ?」
    • 作業の目的や作業自体の意味を考える
    • やりすぎを抑える
  • 「やりたくないの?」

    • 「やりたいくない」がチームに認められる
    • やりたくないことをチームで共有する
  • 「なんでやるの?」という問答は1対1かもしれない

    • その場にいるみんなが考えるきっかけになる
    • それぞれの認識にズレがあるのかないのかを知ることができる

「なんでやるの?」はやりすぎを抑えられる、というのはよく理解できます。何かに没頭したり集中するあまり物事の優先度の意識が薄れて、つい本質から外れたり、ときには手段が目的になっていたりすることがあります。そういうときに「なぜやるのか?」を自身に問いかけることで、目的を再認識して軌道修正をかけることができると思います。

今回、「明日から使える」というのが登壇者のテーマだったようです。で、実際にすぐに現場に適用できるかどうかというと、当然現場の状況にもよりますが、大半は難しいかも、というのが正直な印象です。

どなたかの質問でも話が挙がりましたが、今回の話のような内容がチームのメンバーに対して効果を発揮するのは、ある程度完成されているチームに限るような気がします。

事業会社では必然的に似たような目的意識を持ったメンバーが集まるのかもしれませんが、受託開発を行う会社では多様なメンバーでチームが構成されることが多いかと思います。(偏見かもしれませんが...)
当然、メンバー全員が当事者意識を持っているかというと決してそうではないだろうし、そのようなチームに対してどのような振る舞いがよい作用をもたらすか、ということを一様に述べることはなかなか難しいのではないかと思います。そのようなチームでは、まずは目的意識を共有できるようにチームの「風土作り」から始める必要があると考えます。しかし、今回はそのあたりは前提から外れているように感じました。

理想の開発現場の「ふつう」のお話というテーマでしたが、そこで「ふつう」に行われていることは、多くの「理想とは程遠い開発現場」にとっては他でもない『理想』そのものであるわけです。

ただまぁ、こういう話をきちんと自分の中で腹落ちさせてチームに共有するところから始めることが、よいチームの風土作りの第一歩になるんだろうと思います。そういう意味でいろいろな気付きが得られたとてもいい機会でした。

Haskell を使ってみる 7 (ガード)

前回の続き

Haskell を使ってみる 6 (パターンマッチ) - kntmr-blog

ガード

ガードは引数の値が満たす性質で処理を分岐させるときに使う。パイプ文字 (|) と条件式と関数本体を組み合わせて記述する。(パターンは引数の構造で条件を分岐させるもの)

-- 階乗を求める関数
fact :: Integer -> Integer
fact n
    | n < 0 = error "error!!"
    | n == 0 = 1
    | otherwise = n * fact(n - 1)

*Main> fact 3
6

where

where キーワードは計算結果を変数に束縛するときに使う。where で定義した変数のスコープはその関数内のみ。

-- where キーワードを使わない場合
totalCheck :: Integer -> Integer -> String
totalCheck x y
    | x + y < 10 = "less than 10"
    | x + y < 100 = "less than 100"
    | otherwise = "a large number"

-- where キーワードを使う場合
totalCheck' :: Integer -> Integer -> String
totalCheck' x y
    | total < 10 = "less than 10"
    | total < 100 = "less than 100"
    | otherwise = "a large number"
    where total = x + y

where キーワードの中でパターンマッチを使うことができる。

initials :: String -> String -> String
initials firstname lastname = [f] ++ ". " ++ [l] ++ "."
    where (f:_) = firstname
          (l:_) = lastname

let

let 式では関数のどこでも変数を束縛することができる。let 式自身が式であり、変数の束縛は局所的でガード間で共有されない。(where キーワードは関数の終わりで変数を束縛する)

let で変数に束縛し、続く in に式を記述する。

-- ローカルスコープに関数を作ることができる
Prelude> [let square x = x * x in (square 2, square 3, square 4)]
[(4,9,16)]

-- セミコロン区切りで複数の変数に束縛できる
Prelude> (let a = 10; b = 20; c = 30 in a * b * c, let foo = "Hello"; bar = "Haskell" in foo ++ " " ++ bar)
(6000,"Hello Haskell")

-- let 式とパターンマッチでタプルを要素に分解して変数に束縛できる
Prelude> (let (a, b, c) = (1, 2, 3) in a + b + c) * 100
600

リスト内包表記と let 式の組み合わせ。述語のように使っているが、フィルタしているのではなく計算結果を変数に束縛している。

-- リストから値を受け取り、計算結果を area に束縛する
calcCircleArea :: [Double] -> [Double]
calcCircleArea xs = [area | r <- xs, let area = r * r * 3.14]

case

case 式ではコード中のどこでもパターンマッチを使うことができる。変数で指定した値に基づいてコードブロックを評価する。case 式は関数の引数に対するパターンマッチと似ている。(実際、case 式の糖衣構文になっている)

case 式に合致するパターンが見つからない場合はランタイムエラーが発生する。

-- 関数の引数に対するパターンマッチ
head' :: [a] -> a
head' [] = error "empty list"
head' (x:_) = x

-- case 式
head'' :: [a] -> a
head'' xs = case xs of [] -> error "empty list"
                       (x:_) -> x

引数によるパターンマッチは関数定義のときしか使えない。case 式では、式の途中でパターンマッチを使うことができる。

checkList :: [a] -> String
checkList list = "This list is "
               ++ case list of [] -> "empty."
                               [x] -> "a singleton list."
                               xs -> "a longer list."

-- case 式の代わりに where を使う場合
checkList' :: [a] -> String
checkList' list = "This list is " ++ what list
    where what [] = "empty."
          what [x] = "a singleton list."
          what xs = "a longer list."

今回はガードのあれこれについて。

Haskell を使ってみる 6 (パターンマッチ)

前回の続き

Haskell を使ってみる 5 (型) - kntmr-blog

パターンマッチ

パターンマッチは、データ型が従うべきパターンを指定してそのパターンに従ってデータを分解するために使う。

-- パターンマッチと再帰で n の階乗を求める関数
factorial :: Int -> Int
factorial 0 = 1
factorial n = n * factorial (n - 1)

*Main> factorial 5
120

パターンマッチでパターンに合致しない値を指定するとエラーになる。パターンマッチの最後にすべてに合致するパターンを入れる。

showNumber :: Int -> String
showNumber 1 = "One"
showNumber 2 = "Two"
showNumber 3 = "Three"

*Main> showNumber 1
"One"
*Main> showNumber 5 -- これはパターンにない
"*** Exception: pattern-matching.hs:(6,1)-(8,22): Non-exhaustive patterns in function showNumber
タプルのパターンマッチ

タプルの要素を分解して処理できる。

-- ペアを受け取って足し合わせる関数
addVectors :: (Int, Int) -> (Int, Int) -> (Int, Int)
addVectors (x1, y1) (x2, y2) = (x1 + x2, y1 + y2)

*Main> addVectors (1, 2) (2, 3)
(3,5)
リストのパターンマッチ

リスト内包表記でパターンマッチを使う場合、パターンに合致しないものは処理されない。

Prelude> let xs = [(1,2), (2,3), (2,4), (1,5), (3,6)]
Prelude> [x * 100 | (1, x) <- xs] -- 1つ目の要素が1のペアのみ処理される
[200,500]
リストのパターンマッチ

リストの先頭3要素を足し合わせる関数。

sumThree :: (Num a) => [a] -> a
sumThree [] = 0
sumThree (x:[]) = x
sumThree (x:y:[]) = x + y
sumThree (x:y:z:[]) = x + y + z
sumThree (x:y:z:_) = x + y + z -- この行がないと4要素のリストを指定した場合にエラーになる

*Main> sumThree []
0
*Main> sumThree [1]
1
*Main> sumThree [1, 2]
3
*Main> sumThree [1, 2, 3]
6
*Main> sumThree [1, 2, 3, 4] -- 4つ目の要素は足し合わされない
6
as パターン

as パターンでは、パターンマッチの対象になる値自体を参照することができる。パターンの前に名前と @ を付ける。

firstLetter :: String -> String
firstLetter "" = "Emptry string."
firstLetter all@(x:xs) = "The first letter of " ++ all ++ " is " ++ [x]

*Main> firstLetter ""
"Emptry string."
*Main> firstLetter "Hello"
"The first letter of Hello is H"

今回はパターンマッチについて。

Oracle Certified Java Programmer, Gold SE 8 認定資格

1Z0-809-JPN - Java SE 8 Programmer II を受験しました。

結果: 合格 / 正解率 72% (合格ライン: 65%)

という訳で『Oracle Certified Java Programmer, Gold SE 8 認定資格』を取得しました。

「中上級者向け資格」とあるので、Java の開発案件を数年程度やっていれば問題の内容は十分理解できると思います。
とは言っても、出題範囲は広く覚えることが多いため、ある程度の勉強は必要かと思います。今回はこの問題集で勉強しました。

徹底攻略 Java SE 8 Gold 問題集[1Z0-809]対応

ひと通り目を通して模擬試験2回分をやってみましたが、実際の試験は問題集の内容より少し難易度が高いように感じました。
出題範囲も広く全体的にバランスよく出題されていると思います。また、問題集には載っていない引数違いのオーバーロードメソッドが出題されていたりするため、勉強するときは API ドキュメントを参考にひと通りの用途はおさえておくとよいです。

本当は正解率90%以上を叩き出してドヤァしたかったところですが、割とギリギリの合格になってしまいました...。

1Z0-808-JPN - Java SE 8 Programmer I を受験したときのメモはこちら。

kntmr.hatenablog.com


以下、個人的なメモ。

Javaクラスの設計

継承は is-a 関係 (A は B である) あるいは kind-of-a 関係 (A は B の一種である) を構成する。has-a 関係 (A は B を持っている) は、ある型のフィールドで別の型を参照する関係であり、集約 (Aggregation) や構成集約 (Composition) と呼ばれる。

ポリモーフィズムは、コンパイル時に決定する単一の型 (静的な型) で実行時に複数の型 (動的な型) を扱う仕組み。ポリモーフィックな型は is-a 関係で構成される。is-a 関係は、インタフェースの実装やクラス/インタフェースの継承で実現する。

静的ポリモーフィズム (Static Polymorphism) は、単一の型におけるオーバーロードで実現する。動的ポリモーフィズム (Dynamic Polymorphism) は、複数の型におけるオーバーライドで実現する。

以下の場合、左辺の Foo はコンパイル時に決定する静的な型、右辺の Foo, Bar は実行時に決定する動的な型。

class Foo {}
class Bar extends Foo {}

Foo foo = new Foo();
Foo foo = new Bar();

スーパークラスのメソッドをオーバーライドする場合、アクセス修飾子を制限が厳しいものに変更することはできない

インタフェースは型や振る舞いの仕様を規定する。インタフェースで宣言するメソッドは暗黙的に public となる。フィールドは public static final となる。

抽象クラスはインタフェースの役割に加えて実装を提供することができる。継承関係において関連するクラス間の共通の実装をまとめる。

クラスの多重継承はできない。ただし、インタフェースの多重継承と default メソッドを組み合わせることで、複数のクラスが提供する実装を再利用することができる。

hashCode メソッドはオブジェクトの識別に使用する int 型のハッシュ値を生成して返す。ハッシュ値はオブジェクト検索のパフォーマンス向上の目的で使用される。Object クラスの hashCode メソッドはメモリアドレスをもとに値を生成して返す。

hashCodeequals の関係は以下の通り。

  • equals メソッドで等しいとされるオブジェクトは必ず同じハッシュ値を返す
  • equals メソッドで等しくないとされるオブジェクトであっても異なるハッシュ値を返す必要はない (可能な限り異なるハッシュ値を返すことが望ましい)
  • equals メソッドの判定で使われるフィールドの値が変更されない限りは必ず同じハッシュ値を返す

インタフェースの static メソッドは、インタフェース型参照からのみ呼び出し可能。default メソッドは、インタフェースのみで宣言可能な具象メソッドであり、実装クラスのインスタンス参照から呼び出し可能

シグニチャが同一の default メソッドを持つ複数のインタフェースを実装した場合はコンパイルエラーになる。コンパイルエラーを回避するには実装クラスで該当のメソッドをオーバーライドする。ただし、型階層の距離が異なる場合はコンパイルエラーにならず、階層の距離が近い方の default メソッドが呼ばれる。

入れ子クラスには、クラスのメンバーとして宣言されるメンバークラス、static メンバークラス、メソッド内で宣言されるローカルクラスがある。メンバークラスとローカルクラスは内部クラスと呼ばれる。内部クラスは匿名クラスとして利用できる。メンバークラスにはアクセス修飾子を付けることができる。

インタフェース内に宣言されたメンバークラスは暗黙的に static メンバークラスとなる。

内部クラスのインスタンスを static メソッドから利用するには、外部クラスのインスタンス参照が必要。外部クラスのインスタンスを生成して、その参照を用いて内部クラスのインスタンスを生成する。(new Outer().new Inner())

内部クラスの private フィールドには、その内部クラスが宣言されているクラスのメンバーメソッドから直接アクセスできる。

サブクラスのコンストラクタの先頭には、スーパークラスのデフォルトコンストラクタを呼び出すコード (super()) がコンパイラによって挿入されるスーパークラスにデフォルトコンストラクタが存在しない場合、スーパークラスのコンストラクタを呼び出すコード (super(args) など) を明示的に記述する必要がある。

列挙型は暗黙的に Enum クラスのサブクラスとしてコンパイルされる。列挙型のコンストラクタは暗黙的に private となる。

コレクションとジェネリクス

型パラメータは、アノテーション型変数型境界の要素から構成される。(アノテーションと型境界は省略可)

総称型を利用する場合には、型変数に対して実際に利用する型を割り当てる必要がある。割り当てる型を型引数 (Type Arguments) と呼ぶ。型引数に指定できるのは配列型を含む参照型のみ。

型引数が割り当てられた総称型をパラメータ化された型 (Parameterized Types) と呼ぶ。

  • static 修飾子付きの型変数は宣言不可 (static T t)
  • 型変数のインスタンス生成は不可 (T obj = new T())
  • 型変数を要素の型に指定した配列生成は不可 (T[] array = new T[3])
  • instanceof 演算子では利用不可 (t instanceof T)
  • 型変数に対する .class 参照は不可 (T.class)

ジェネリックメソッドは戻り値の記述の前に型パラメータを宣言する。

// 引数の型宣言に型変数を使用するジェネリックメソッド
public <T> void foo1(T t) {}
// 戻り値の型宣言に型変数を使用するジェネリックメソッド
public <T> T foo2() {}

型パラメータの宣言では、extends キーワードを利用して型変数が取り得る型に制約を課すことができる。これを型境界 (Type Bound) と呼ぶ。

// 型変数 T が取り得る型は Number のサブタイプに制限される
class Foo<T extends Number> {}

型境界に指定する型には宣言している他の型変数が使用できる。

// 型変数 U は T のサブタイプに制限される
class Foo<T, U extends T> {}

総称型をパラメータ化する際の型引数にはワイルドカードを記述することができる。型変数にはワイルドカードは記述できない。

  • 型パラメータリストにおける型変数には記述できない (class Foo<?> {})
  • インスタンス生成のコードには記述できない (Foo<?> obj = new Foo<?>())

ワイルドカードに対しては2種類の型境界を指定することができる。<?><? extends Object> と等価。

  • <? extends T> : 型境界の上限をT型に制限する。? が取り得る型はTかTのサブクラスとなる。(上限境界 / Upper Bound)
  • <? super T> : 型境界の下限をT型に制限する。? が取り得る型はTかTのスーパークラスとなる。(下限境界 / Lower Bound)

総称型を継承または実装する場合は、型引数を指定して総称型をパラメータ化する

class Foo<T> {}

// Foo<T>のTはBar<T>で宣言されている型変数Tによってパラメータ化されている
class Bar<T> extends Foo<T> {}
// BarはFoo<String>型を継承した非ジェネリックな型
class Bar extends Foo<String> {}
// BarはFoo<String>型を継承したジェネリックな型
class Bar<T> extends Foo<String> {}

java.util.List<E> インタフェースは、インデックスによって順序付けられるデータ構造を表すコレクションインタフェース。

ArrayList<E> クラスは、内部で配列を利用した List<E> インタフェースの実装クラス。ランダムアクセスは高速。要素の追加は、追加する位置が先頭に近いか、要素数が多いほどパフォーマンスが劣化する。

LinkedList<E> クラスは、双方向連結リストと呼ばれる。ランダムアクセスでは、先頭または末尾からリンクを辿って目的の要素を探すため、ArrayList に比べるとパフォーマンスは劣る。要素の追加は、要素の前後のリンクを張り直すだけなので、ArrayList に比べてパフォーマンスは優れる。

ArrayList クラスや LinkedList クラスはスレッドアンセーフ。

その他、Vector<E> クラスや CopyOnWriteArrayList<E> クラスなどの実装クラスが存在する。

Arrays クラスの asList メソッドが返す List オブジェクトは、Arrays クラス内部で宣言されている private static な ArrayList クラスのインスタンスであり、java.util.ArrayList型の互換性はない。要素の追加や削除はできない。

java.util.Queue<E> インタフェースは、FIFO のデータ構造を表す。

BlockingQueue インタフェースは、Queue インタフェースのサブインタフェースで、要素の挿入や削除においてブロッキングする機能を提供する。キューが満杯の場合は空きができるまで待機する。BlockingQueue インタフェースの実装クラスには、ArrayBlockingQueue クラス、PriorityBlockingQueue クラス、DelayQueue クラスなどが存在する。

java.util.Deque<E> インタフェースは、Queue インタフェースのサブインタフェースで、両端から要素を挿入/削除できるデータ構造を表す。Deque インタフェースの実装クラスには、ArrayDeque<E> クラス、LinkedList<E> クラスなどが存在する。Deque インタフェースはインデックスによる要素アクセスは非サポート

ArrayDeque<E> クラスは内部で配列を使用する。null は要素として格納できないFIFO として使う場合には LinkedList<E> クラスより高速。FILO として使う場合には java.util.Stack<E> クラスより高速。

ArrayDeque クラスや LinkedList クラスはスレッドアンセーフ。Stack クラスはスレッドセーフ。

java.util.Set<E> インタフェースは、重複する要素を持たないことを保証するコレクションインタフェース。重複する要素とは、equals メソッドで等しいとされる要素を指す。含まれる要素の順序は保証しない。

Set インタフェースの実装クラスである HashSet<E> クラスは、要素の管理にハッシュテーブルを使用する。TreeSet<E> クラスは、木構造に基づく実装であり、要素の順序付けをサポートする。

java.util.Map<K, V> インタフェースは、一意なキーとキーに紐付けられた値のペアを要素として扱うデータ構造を表す。コレクションフレームワークに含まれているが、Collection<E> インタフェースのサブインタフェースではない

Map.Entry<K, V> インタフェースは、Map オブジェクトに格納される1要素を表す。Map インタフェースの entrySet メソッドで取得する。

Map.Entry<K, V> インタフェースが提供する comparingByValue(Comparator<? super V>) メソッドは、Map オブジェクトの値でソートする。値が同じ場合はキーでソートする。String.CASE_INSENSITIVE_ORDER は String クラスが提供する Comparator<String> オブジェクトで、大文字や小文字は区別しない。

java.lang.Comparable<T> インタフェースを実装するクラスのオブジェクトは、自身と他のオブジェクトを比較することができる。Comparable<T> インタフェースでは、compareTo(T o) メソッドが宣言されている。自身が大きい場合は正の int 値、自身が小さい場合は負の int 値、等しい場合は 0 を返す。

java.lang.Comparator<T> インタフェースを実装するクラスのオブジェクトは、他の2つのオブジェクトを比較する機能を実装する。Comparator<T> インタフェースでは、compare(T o1, T o2) メソッドが宣言されている。o1 > o2 の場合は正の int 値、o1 < o2 の場合は負の int 値、o1 == o2 の場合は 0 を返す。

TreeMap<K, V> クラスは、Map<K, V> インタフェースの実装クラスで、デフォルトではキーの自然順序付け (昇順) で要素を並び替える。

TreeSet<E> クラスは、内部で要素を並び替える際に格納された要素を Comparable<T> 型にキャストするため、Comparable<T> を実装していないオブジェクトを TreeSet などのソート機能付きのコレクションに格納すると ClassCastException がスローされる。

List<E> インタフェースの default メソッドで宣言されている replaceAll メソッドは、引数に指定された UnaryOperator<E> オブジェクトの実装に従ってリスト内の要素を計算して置換する。

ラムダ式と組み込み関数型インタフェース

関数型インタフェースの要件は、抽象メソッドが1つ宣言されていること。SAM (Single-Abstract-Method)) と呼ばれる。default メソッドや static メソッドの宣言は関数型インタフェースの要件に関係しない。関数型インタフェースの妥当性は java.lang.@FuntionalInterfaceコンパイル時にチェックすることができる。

すべてのインタフェースの実装クラスは Object クラスのサブクラスであるため、Object クラスで宣言されているメソッドと同一シグニチャの抽象メソッドは関数型インタフェースの抽象メソッドとは見なされない。

@FuntionalInterface
public interface Foo {
    void hoge();
    boolean equals(Object obj); // これは抽象メソッドと見なされない
}

@FuntionalInterface
public interface Bar {
    // 以下は抽象メソッドと見なされないため、これは関数型インタフェースではない
    boolean equals(Object obj);
}

ラムダ式は、匿名内部クラスの実装を簡潔に記述するための構文仕様。(厳密にはラムダ式と匿名クラスの挙動は違う)

関数型プログラミングでは、関数を第1級オブジェクトとして扱うことができる。第1級オブジェクトとはリテラルと同様に扱うことができるオブジェクトを指す。

  1. 変数に対して関数を代入できる
  2. 関数の引数に関数を指定できる
  3. 関数の戻り値に関数を返せる

特に、2, 3 をサポートする関数を高階関数と呼ぶ。

引数を取るラムダ式では引数の型は省略可。引数が1つだけの場合は引数を囲う括弧は省略可。括弧を省略する場合は引数の型は記述不可。引数を2つ以上取るラムダ式では括弧は省略不可。戻り値を返すラムダ式でブロックを省略している場合は return キーワードは記述不可

java.util.function パッケージが提供するインタフェースは基本的に以下の4つ。

  • Supplier<T> : 引数なしでT型の値を返す T get() を宣言する関数型インタフェース
  • Predicate<T> : T型の値を引数に取って評価する boolean test(T) を宣言する関数型インタフェース
  • Consumer<T> : T型の値を引数に取って何らかの処理を実行する void accept(T) を宣言する関数型インタフェース
  • Function<T, R> : T型の値を引数に取ってR型の値を返す R apply(T) を宣言する関数型インタフェース

基本のインタフェースの他に特殊化したインタフェースが存在する。

  • BiFunction<T, U, R> : Function<T, R>特殊化型で、引数を2つ取る R apply(T, U) を宣言する関数型インタフェース
  • UnaryOperator<T> : T型の引数を取ってT型の値を返すインタフェースで、Function<T, T>サブインタフェース (単項演算子)
  • BinaryOperator<T> : T型の値を2つ取ってT型の値を返すインタフェースで、BiFunction<T, T, T>サブインタフェース (二項演算子)

特殊化型を含めて、ひと通りのインタフェースについては以下にまとめる。

java.util.function パッケージのインタフェースサマリー (用途別) - kntmr-blog

Predicate<T> インタフェースの default メソッドとして、論理積 (AND) を取る and メソッド、論理和 (OR) を取る or メソッド、論理否定 (NOT) を取る negate メソッドが提供されている。

Function インタフェースの default メソッドとして、andThen メソッドと compose メソッドが提供されている。andThen メソッドは引数に指定された Function オブジェクトを後に適用する。compose メソッドは引数に指定された Function オブジェクトを先に適用する。

関数型インタフェースの抽象メソッドとラムダ式で呼び出すメソッドのシグニチャが同一である場合はメソッド参照が使える。メソッド参照を使う場合はラムダ式の仮引数宣言とメソッド呼び出しの括弧は記述不可

以下の抽象メソッド void accept(T) メソッドは System.out.println メソッドとシグニチャが同じである。ラムダ式で処理を実装する場合は以下の通り。

Consumer<String> c = s -> System.out.println(s);
c.accept("foo");

これをメソッド参照で記述する場合は以下の通り。

Consumer<String> c = System.out::println;
c.accept("foo");

コンストラクタメソッドの呼び出しをメソッド参照で記述する場合はコンストラクタ参照と呼ばれる。コンストラクタ参照の代入先の変数は、関数型インタフェースである必要がある。

以下の抽象メソッド R apply(T) メソッドはコンストラクタメソッドと同じシグニチャである。ラムダ式で記述する場合は以下の通り。

Function<String, Integer> f = s -> new Integer(s);
Integer i = f.apply("1");

これをコンストラクタ参照で記述する場合は以下の通り。

Function<String, Integer> f = Integer::new;
Integer i = f.apply("1");

コンストラクタ参照である Foo::new() -> new Foo() というラムダ式に置き換えて考えることができる。配列オブジェクトを生成するコンストラクタ参照は、データ型[]::new という構文で記述する。

引数として受け取ったオブジェクトの引数を取らないメソッド呼び出しをメソッド参照で書く場合、クラス名::メソッド名 の形式で記述することができる。以下はラムダ式の引数で受け取った String オブジェクトの引数なしのメソッドを呼び出す場合。

Function<String, Integer> f = s -> s.length();
System.out.println(f.apply("foo")); // => 3

これをメソッド参照で記述する場合は以下の通り。

Function<String, Integer> f = String::length;
System.out.println(f.apply("foo")); // => 3

インスタンス変数で参照するオブジェクトのメソッド呼び出しをメソッド参照で書く場合は インスタンス変数名::メソッド名 の形式で記述できる。

String s1 = "foo";
Function<String, String> f = s2 -> s1.concat(s2);
System.out.println(f.apply("bar"));

これをメソッド参照で記述する場合は以下の通り。

String s = "foo";
Function<String, String> f = s::concat;
System.out.println(f.apply("bar"));

Stream API

ストリームAPIの基本的なステップは以下の通り。

  1. データソースからストリームオブジェクトを取得する
  2. ストリームオブジェクトに対して中間操作を適用する
  3. ストリームオブジェクトに対して終端操作を適用する

データソースは、配列やコレクション、ファイルから読み込んだデータ、数値範囲などが挙げられる。

中間操作では処理関数を保持した新しいストリームオブジェクトが返る。中間操作のメソッド呼び出しの時点ではストリームオブジェクトに対する処理は実行されない。中間操作の処理が実行されるのは終端操作が実行されるタイミング。(遅延評価)

終端操作はストリームオブジェクトに対して1度だけ適用できる。終端操作が適用されたストリームに対して再度ストリーム処理を適用することはできない。

java.util.stream パッケージでは、BaseStream<T, S extends BaseStream<T, S>> インタフェースを基底とした以下のサブインタフェースが提供されている。

  • Stream<T> インタフェース
  • IntStream インタフェース
  • LongStream インタフェース
  • DoubleStream インタフェース

Stream<Integer>IntStream互換性はない

BaseStream<T, S extends BaseStream<T, S>> インタフェースは AutoCloseable インタフェースを継承しているため、try-with-resources 文を使用してリソースを自動的に close できる。特にファイルを処理する場合には try-with-resources 文を使用して必ず close すること。

java.util.Collection インタフェースでは、default メソッドとして stream メソッドが提供されている。

List<String> list = Arrays.asList("A", "B", "C");
Stream<String> stream = list.stream();

java.lang.Iterable インタフェースでは、default メソッドとして forEach メソッドが提供されている。Iterable インタフェースを継承する Collection インタフェースの実装クラスは Stream オブジェクトを取得することなく forEach メソッドが使える。

Arrays.asList("A", "B", "C").forEach(System.out::print);

Map<K, V> インタフェースでは、default メソッドとして forEach メソッドが提供されている。引数には BiConsumer オブジェクトを指定する。Map インタフェースでは stream メソッドは提供していない。代わりに entrySet メソッドで Set オブジェクトを取得して stream メソッドを呼び出す。

map.entrySet().stream() // stream() は Stream<Map.Entry<K, V>> を返す

配列からストリームオブジェクトを取得する場合は java.util.Arrays クラスの static メソッドである stream メソッドを使う。

Stream<String> stream = Arrays.stream(array);

stream には配列の範囲を指定するオーバーロードメソッドが存在する。範囲指定では、開始インデックスの値は含むが、終了インデックスの値は含まない。同様に、range メソッドも開始インデックスの値は含むが、終了インデックスの値は含まない。rangeClosed メソッドは開始インデックスと終了インデックスの値を含む。

IntStream.range(1, 5).forEach(System.out::print); // => 1234
IntStream.rangeClosed(1, 5).forEach(System.out::print); // => 12345

任意の参照型オブジェクトの集合から Stream オブジェクトを取得する場合は Stream インタフェースの static メソッドである of メソッドを使う。

Stream<String> stream = Stream.of("a", "b", "c");

BufferedReader クラスの lines() メソッドと Files クラスの lines(Path) メソッドは、テキストファイルの内容を読み込んで Stream<String> オブジェクトとして返す。

ストリーム内の要素を並べ替える場合は Stream インタフェースの sorted メソッドを使う。

// デフォルトでは自然順序で並べ替える
Arrays.asList(2, 3, 1).sorted().forEach(System.out::print); // => 123
// Comparator を指定して任意の順序で並べ替える
Arrays.asList(2, 3, 1).sorted((i, j) -> j - i).forEach(System.out::print); // => 321

peek メソッドは元のストリームオブジェクトを返す中間操作メソッド

Predicate オブジェクトを引数に取り、boolean 型の戻り値を返す条件判定用の終端メソッドは以下の3つ。

  • anyMatch メソッド (1つでも条件に合致する要素があれば、その時点で true を返す)
  • allMatch メソッド (1つでも条件に合致しない要素があれば、その時点で false を返す)
  • noneMatch メソッド (1つでも条件に合致する要素があれば、その時点で false を返す)

IntStream, LongStream, DoubleStream の 条件判定用のメソッドでは、それぞれ IntPredicate, LongPredicate, DoublePredicate を引数に取る。Predicate オブジェクトは引数に取らない。

引数を取らず、Optional<T> オブジェクトを返す探索用の終端メソッドには、findAny メソッドと findFirst メソッドがある。要素がない場合は空の Optional オブジェクトを返す。

Stream<T> インタフェースでは、入れ子構造のストリームを平坦なストリームに変換する中間操作メソッドとして、flatMap メソッドが提供されている。その他、プリミティブ型に対応するプリミティブバージョンの flatMapToXxx メソッドが提供されている。(IntStream, LongStream, DoubleStream を返す)

Stream オブジェクトをプリミティブバージョンの Stream オブジェクトに変換する場合は、Stream インタフェースの mapToXxx メソッドを使用する。

java.util.Map<K, V> インタフェースでは、default メソッドとして merge(K, V, BiFunction) メソッドが提供されている。指定したキーの値が存在しない場合、第2引数の value が設定される。指定したキーの値が存在する場合、第3引数の BiFunction オブジェクトの処理結果が値として設定される。引数の value や BiFunction の戻り値が null の場合、キーのエントリは削除される。

ストリームオブジェクトの要素を1つに要約する終端操作をリダクションと呼ぶ。一般的なリダクション操作には、合計や平均の算出、最大値、最小値の取得などがある。

java.util.stream.Stream インタフェースの min メソッドおよび max メソッドの引数には Comparator<? super T> 型のオブジェクトを指定する。メソッドの戻り値は Optional 型

System.out.println(Stream.of("a", "b", "c").count()); // => 3
System.out.println(IntStream.of(1, 2, 3).average().getAsDouble()); // average() は OptionalDouble を返す
System.out.println(IntStream.of(1, 2, 3).max().getAsInt()); // IntStream の max() や min() は OptionalInt を返す

任意のリダクション処理を実装する場合は reduce メソッドを使う。引数に BinaryOperator<T> オブジェクトを取る reduce メソッドは Optional 型を返す。

可変リダクション操作とは、何らかの可変コンテナに要素を収集する操作のこと。collect メソッドを使う。可変コンテナには List や Map, StringBuilder などがある。collect メソッドの引数には Collector オブジェクトを指定する。

ユーティリティクラスである java.util.Collectors クラスは、一般的によく使われる可変リダクション操作を実装した Collector オブジェクトを返す static メソッドを提供している。

  • counting メソッド : 要素の数をカウントする Collector オブジェクトを返す
  • groupingBy メソッド : 指定された Function オブジェクトに従って要素をグループ化して Map オブジェクトに格納する Collector オブジェクトを返す
  • joining メソッド : 要素を連結して1つの String オブジェクトにする Collector オブジェクトを返す
  • maxBy メソッド : 指定された Comparator オブジェクトに従って最大要素を返す Collector オブジェクトを返す
  • minBy メソッド : 指定された Comparator オブジェクトに従って最小要素を返す Collector オブジェクトを返す
  • reducing メソッド : 指定された BinaryOperator オブジェクトに従って要素のリダクションを実行する Collector オブジェクトを返す
  • toList メソッド : 要素を新しい List に蓄積する Collector オブジェクトを返す
  • toSet メソッド : 要素を新しい Set に蓄積する Collector オブジェクトを返す

以下はプリミティブ特殊化された関数型インタフェースを引数に取る。Collectors.averagingXxx メソッドや Collectors.summarizingXxx メソッドも同様。

  • Collectors.summingInt(ToIntFunction<? super T>)
  • Collectors.summingLong(ToLongFunction<? super T>)
  • Collectors.summingDouble(ToDoubleFunction<? super T>)

Collectors.partitioningBy(Predicate<? super T>) は引数の Predicate オブジェクトの判定に従って要素を分割して Map<Boolean, List<T>> オブジェクトで返す。

Comparator インタフェースの comparing メソッドで Comparator オブジェクトを簡易的に取得できる。comparing メソッドの引数にはソートに使用するキーを返す Function オブジェクトを指定する。もしくは、Function インタフェースの identity メソッドで入力引数を返す関数を返すメソッドを使う。

List<Integer> list = Arrays.asList(7, 2, 5);
list.stream().min(Comparator.comparing(n -> n)).get(); // => 2
list.stream().min(Comparator.comparing(Function.identity())).get(); // => 2

例外とアサーション

例外の multi-catch 文の catch ブロックに継承関係にある例外型を書くとコンパイルエラーとなる。multi-catch ブロックの引数に宣言した変数は暗黙的に final となる。

try-with-resources 文では catch ブロックや finally ブロックは必須ではない。try-with-resources 文に宣言できるクラスは、AutoCloseable インタフェースか、サブインタフェースである Closeable インタフェースを implements して、抽象メソッドである public void close() メソッドを実装する必要がある。

try ブロックの中で例外が発生した場合は close メソッドが呼ばれてから catch ブロックに入る。try-with-resources 文で close メソッドが呼ばれる順番はインスタンスを生成した逆順

メソッドをオーバーライドする際、throws 宣言する例外クラスを省略したり、元の例外クラスのサブクラスを throws に宣言できる。別の例外クラスや元の例外クラスのスーパークラスを throws に宣言することはできない。

アサーションの基本構文は以下の通り。assert 文の評価が false の場合は AssertionError クラスがスローされる。

assert x == 1;
assert (y == 1);
// AssertionError クラスのコンストラクタに指定する実引数を記述できる。
assert x == 1 : "error message";

アサーション機能はデフォルトでは無効。クラスやパッケージごとに有効無効を指定できる。

java -ea Foo
java -ea com.package
java -da Bar
java -da com.package

日付/時刻API

java.time パッケージが提供する。ISO 8601 をベースに設計されている。

時差情報を含まない現地日時をローカルタイム (Local Time) と呼ぶ。

  • LocalDate クラス : 日付のみを扱う
  • LocalTime クラス : 時刻のみを扱う
  • LocalDateTime クラス : 日付と時刻を扱う

時差情報を含む日時をオフセットタイム (Offset Time) と呼ぶ。

  • OffsetTime クラス : 時刻のみを扱う
  • OffsetDateTime クラス : 日付と時刻を扱う

タイムゾーンによる時差を扱うクラス。

  • ZonedDateTime クラス : 地域情報を含む日付と時刻を扱う

現在日時を表すインスタンスを取得する場合は以下の通り。

LocalDate.now(); // => yyyy-MM-dd
LocalTime.now(); // => hh:mm:ss.SSS
LocalDateTime.now(); // => yyyy-MM-ddThh:mm:ss.SSS

特定の日付を表すインスタンスを取得する場合は以下の通り。

// 年月日をすべて int で指定
LocalDate.of(2016, 12, 30);
// 月に Month 型の列挙定数を指定
LocalDate.of(2016, Month.DECEMBER, 30);

年、年月、月日を個別に扱うクラス。of(int) メソッドや from(TemporalAccessor) メソッドなどのファクトリメソッドで取得する。

  • Year クラス : 年を表す
  • YearMonth クラス : 年月を表す
  • MonthDay クラス : 月日を表す

java.time パッケージには、月を表す Month 列挙型 (Month.JANUARY など) と曜日を表す DayOfWeek 列挙型 (DayOfWeek.MONDAY など) が存在する。

日時を表現するクラスは、java.time.temporal パッケージの TemporalAccessor インタフェースや、そのサブインタフェースである Temporal インタフェースを実装する。

TemporalAccessor は読み取り専用のメソッドを宣言している。Temporal は読み書きのメソッドを宣言している。Date and Time API の日時を表すクラスは不変であるため、書き込みメソッドを呼び出した場合はコピーオブジェクトが返る。

日時を表す TemporalAccessor 型のオブジェクトから別の日時を表すオブジェクトを取得する場合は from メソッドを利用する。

LocalDate.from(LocalDateTime.now()); // 時間の情報は失われる
LocalTime.from(LocalDateTime.now()); // 日付の情報は失われる

以下は、DateTimeException がスローされる。

LocalDateTime.from(LocalDate.now()); // 日付の情報しか持っていないため
LocalDateTime.from(LocalTime.now()); // 時間の情報しか持っていないため

OffsetDateTime や ZonedDateTime から LocalDateTime オブジェクトを取得することは可能。時差情報やタイムゾーンの情報が失われるだけで例外はスローされない

文字列形式の日付から LocalDate インスタンスを生成する場合は parse メソッドを利用する。

LocalDate クラスの withXxx メソッド (withYear(int), withMonth(int) など) は、日時の値を変更した LocalDate オブジェクトを生成して返す。with(TemporalField, long) メソッドは、指定されたフィールドに新しい値を設定した LocalDate オブジェクトを生成して返す。

LocalTime クラスの withXxx メソッド (withHour(int), withMinute(int) など) は、時間の値を変更した LocalTime オブジェクトを生成して返す。with(TemporalField, long) メソッドは、指定されたフィールドに新しい値を設定した LocalTime オブジェクトを生成して返す。

LocalDateTime クラスも同様。

java.time.temporal パッケージの TemporalUnit インタフェースを実装する ChronoUnit 列挙型は、1ヶ月や1年など日時の単位を表す。

  • ChronoUnit.DECADES : 10年
  • ChronoUnit.CENTURIES : 100年
  • ChronoUnit.MILLENNIA : 1000年
  • ChronoUnit.ERAS : 1紀元
  • など

ChronoUnit には簡単な計算メソッドが定義されている。

// 5日後の日付を取得
Temporal t = ChronoUnit.DAYS.addTo(day, 5);
// start から end までの日数を取得
long days = ChronoUnit.DAYS.between(start, end);

java.time.temporal パッケージの TemporalField インタフェースを実装する ChronoField 列挙型は、年や月など日時のフィールドを表す。

  • ChronoField.YEAR : 年
  • ChronoField.MONTH_OF_YEAR : 月
  • など

現在のタイムゾーンjava.time.ZoneId クラスの static メソッドである systemDefault メソッドで取得する。

System.out.println(ZoneId.systemDefault()); // => Asia/Tokyo

java.time.Duration クラスは時間ベースの時間量を表す。java.time.Period クラスは日付ベースの時間量を表す。間隔を取得するための static な between メソッドが宣言されている。

日時の表示形式をフォーマットする場合は java.time.format パッケージの DateTimeFormatter クラスを利用する。DateTimeFormatter クラスはスレッドセーフ。SimpleDateFormat クラスはスレッドアンセーフ。

DateTimeFormatter クラスはコンストラクタを提供していない。基本的なフォーマッタは static フィールドに宣言されている。

  • DateTimeFormatter.BASIC_ISO_DATE : 基本的なISO日付
  • DateTimeFormatter.ISO_DATE : オフセット付きまたはオフセットなしのISO日付
  • DateTimeFormatter.ISO_LOCAL_DATE : ISOローカル日付
  • DateTimeFormatter.ISO_OFFSET_TIME : オフセット付きの時間

ロケール固有のフォーマッタを使う場合はファクトリメソッドを利用する。

  • ofLocalizedDate メソッド : ISO暦に対するロケール固有の日付フォーマット
  • ofLocalizedDateTime メソッド : ISO暦に対するロケール固有の日時フォーマット

フォーマッタを利用してフォーマットする場合は format メソッドを呼び出す。

DateTimeFormatter formatter = DateTimeFormatter.BASIC_ISO_DATE;
formatter.format(LocalDateTime.now());

独自のパターンを指定して DateTimeFormatter インスタンスを取得する場合は static な ofPattern メソッドを利用する。

java.time パッケージの Instant クラスは時系列上の単一時点 (起点からの時間量) を表す。Instant インスタンスを取得する場合はファクトリメソッド now を利用する。

Instant.EPOCH; // => エポック秒の起点 (1970-01-01T00:00:00Z)
Instant.now().getEpochSecond(); // => 秒
Instant.now().toEpochMilli(); // => ミリ秒

Instant.EPOCH の文字列表現では末尾に Z が付いており、タイムゾーンの情報が含まれていることを表す。

java.util.Date クラスや java.util.Calendar クラスが提供しているに toInstant メソッドで、従来のオブジェクトと Date and Time API のオブジェクトを相互変換できる。

LocalDateTime.ofInstant(date.toInstant, ZoneId.systemDefault());
Date.from(localDateTime.atZone(ZoneId.systemDefault())).toInstant();

起点からの秒やミリ秒を指定して Instant インスタンスを取得する場合は ofEpochSecond(long) メソッドや ofEpochMilli(long) メソッドを利用する。

ZonedDateTime クラスは、java.time.chrono.ChronoZonedDateTime インタフェースの default メソッドである toInstant メソッドを継承している。

LocalDateTime クラスは、ChronoLocalDateTime インタフェースの default メソッドとして toInstant メソッドを提供している。ただし、LocalDateTime は時差情報を持たないため、引数に ZoneOffset オブジェクトを指定する必要がある。

現在の協定世界時 (UTC) を取得する場合は、java.time.ZonedDateTime クラスの now メソッドを使う。引数には ZoneId オブジェクトを指定する。ZoneId クラスのサブクラスである ZoneOffset クラスで定数 ZoneOffset.UTC が定義されている。

java.util.Date オブジェクトを取得する場合は、ZonedDateTime オブジェクトの toInstant メソッドでエポックミリ秒を取得し、Date クラスの from メソッドを使用する。

ZonedDateTime utc = ZonedDateTime.now(ZoneOffset.UTC);
Date date = Date.from(utc.toInstant());

Java I/O

java.io.File クラスではシンボリックリンクなどの UNIX 系のファイルシステム固有の機能は利用することができない。ファイルサイズや最終更新日時は取得できるが、オーナーやセキュリティなど詳細なファイル属性を扱うことはできない。

入出力ストリームはデバイスとデータの流れを抽象化したもの。

バイナリファイルを扱う場合は InputStream/OutputStream を使う。テキストファイルを扱う場合は Reader/Writer を使う。厳密にはテキストファイルもバイナリなので、InputStream/OutputStream でテキストファイルを扱うことはできる。逆にReader/Writer でバイナリファイルを扱うことはできない。

java.io パッケージの入出力ストリームでは Decorator パターンが使われている。基本機能を提供する Decorated と、より高度な機能を提供する Decorator で構成される。Decorated は抽象クラスとして提供される。Decorator は Decorated を継承すると同時に Decorated の参照を保持する。

以下の場合、Decorator である BufferedReader/BufferedWriter が、Decorated である FileReader/FileWriter の参照を保持している。

BufferedReader reader = new BufferedReader(new FileReader("input.txt"));
BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"));

java.io.BufferedReader クラスの read メソッドは int 値を返す。readLine メソッドは1行のテキストを読み込んで String 型で返す。ただし、終端の改行コードは含まない

java.io.BufferedWriter クラスの write メソッドは改行コードを書き込まない。改行コードを出力する場合は newLine メソッドを使用する。

InputStream クラスや Reader クラスではデータの読み取り位置を制御するマーク機能が提供されている。

  • void mark(int) : 引数にマーク後に読み取れる文字数を指定して現在の読み取り位置をマークする
  • void reset : マークされている読み取り位置に移動する (マークしていない場合は例外がスローされる)
  • long skip(long) : 引数で指定された文字数だけ読み取り位置をスキップする

Reader クラスの ready メソッドは入力ストリームを読み取ることができる場合に true を返す。try-with-resources で close された入力ストリームに対して ready を呼び出すと例外がスローされる

java.io.PrintStream クラスおよび java.io.PrintWriter クラスでは各プリミティブ型の値を出力する機能を提供する。プラットフォーム固有の改行文字を出力する。

標準入力からデータを読み取るには java.io.Console クラスを使う。readLine メソッドで文字列を読み取る。パスワードを読み取る場合は readPassword メソッドを使う。

オブジェクトをファイルに保存したりネットワーク越しにオブジェクトを転送する場合、Serialize (直列化)Deserialize (非直列化/復元) を使う。

直列化や復元は JVM によって実行される。対象オブジェクトの型であるクラスに java.io.Serializable インタフェースを実装する。Serializable インタフェースを実装していないオブジェクトは直列化できない。java.io.Serializable インタフェースはマーカーインタフェースと呼ばれる。

直列化には java.io.ObjectOutputStream クラスの writeObject メソッドを使う。復元には java.io.ObjectInputStream クラスの readObject メソッドを使う。

オブジェクトを直列化する際、static 変数と transient キーワード付きで宣言された変数の値は除外される。除外された変数を復元した場合はデフォルト値で初期化される。transient キーワードは、オブジェクトがメモリ上に存在する間だけ有効であることを明示するキーワードで、オブジェクトの直列化はしないがフィールドとしては使いたい変数がある場合に使用する。

java.nio.file.Path インタフェースは、java.io.File クラス同様にファイルやディレクトリのパスを表す。クラスではなくインタフェースとして提供されているため、プラットフォーム固有の仕組みを透過的に扱える。

java.nio.file.FileSystem クラスはプラットフォーム固有のファイルシステムを表す抽象クラス。ファクトリメソッドである getDefault メソッドで FileSystem の具象クラスを取得する。

Path オブジェクトはファイルシステムに関連付けられているため、FileSystem オブジェクトの getPath メソッドで取得する。または、java.nio.file.Paths クラスの get メソッドで取得する。

Path インタフェースで宣言されているパス操作関連のメソッドは以下の通り。

  • subpath(int, int) メソッド : パスの一部を抜き出す (endIndex の要素は結果に含まれない)
  • resolve メソッド : 自身のパスと引数の相対パスを結合したパスを返す (引数が絶対パスの場合は引数のパス、引数が空の場合は自身のパスを返す)
  • resolveSibling メソッド : 自身の親パスと引数の相対パスを結合したパスを返す (引数が絶対パスの場合、または呼び出し側の Path オブジェクトが相対パスの場合は引数のパスを返す)
  • normalize メソッド : 現在のパスに含まれる冗長なパス表現を正規化したパスを返す (カレントディレクトや親ディレクトリを表すピリオドを解決する)
  • relativize メソッド : 引数のパスを現在のパスからの相対パスとして解決したパスを返す (解決できない場合は例外をスロー)

java.nio.file.Files クラスはファイルやディレクトリの操作系の機能を提供するユーティリティクラス。

copy メソッドでは、コピー先に同名のファイルが存在する場合、java.nio.file.FileAlreadyExistsException がスローされる。ファイルを上書きする場合は StandardCopyOption.REPLACE_EXISTING オプションを指定する。

デフォルトでは、ファイル属性はコピーされない。ファイル属性をコピーする場合は StandardCopyOption.COPY_ATTRIBUTES オプションを指定する。

copy メソッドでディレクトリをコピーする場合、ディレクトリ内のファイルはコピーされない

シンボリックリンクをコピーした場合、リンク自体はコピーされないシンボリックリンクをコピーする場合は LinkOption.NOFOLLOW_LINKS オプションか StandardCopyOption.REPLACE_EXISTING オプションを指定する。

java.nio.file.attribute パッケージにはファイル属性を扱うインタフェースやクラスが含まれる。

BasicFileAttributes インタフェースは一般的なファイルシステムでサポートされる基本的なファイル属性を表す。サブインタフェースである DosFileAttributes インタフェースは DOS ファイルシステムにおけるファイル属性、PosixFileAttributes インタフェースは UNIX/Linux などの POSIX ファイルシステムにおけるファイル属性を扱う。

ファイル属性のセットを属性ビュー (Attribute View) と呼ぶ。属性ビューは AttributeView インタフェースとそのサブクラスで表される。一般的に使われるものとして、BasicFileAttributeView インタフェースと、サブインタフェースである DosFileAttributeView インタフェースと PosixFileAttributeView インタフェースが存在する。

readAttributes メソッドでファイル属性を表すオブジェクトを取得する。

// 最終アクセス日時を取得する
FileTime time = Files.readAttributes(path, BasicFileAttributes.class).lastAccessTime();

Files クラスの getAttribute メソッドでファイルやディレクトリのファイル属性を取得できる。取得する属性名には属性ビュー名 (basic/dos/posix) を指定する。省略した場合は basic となる。getAttribute メソッドの戻り値は Object 型なので適切な型にキャストする。

// ファイルサイズを取得する
Long size = (Long) Files.getAttributes(path, "size");
// 作成日時を取得する
FileTime time = (FileTime) Files.getAttributes(path, "creationTime");
// ビューを指定してユーザIDを取得する
int uid = (Integer) Files.getAttribute(path, "unix:uid");

ディレクトリ階層を再帰的にトラバースするには、java.nio.file.Files クラスの walkFileTree メソッドを使用する。walkFileTree メソッドに指定する FileVisitor インタフェースの実装クラスで再帰処理を実装する。コールバックされたメソッドでは java.nio.file.FileVisitResult 列挙定数を返す。

Files クラスが提供する Stream オブジェクトを返すメソッドは以下の通り。

  • lines(Path) メソッド : テキストファイルの内容を Stream<String> オブジェクトとして返す
  • list(Path) メソッド : 引数に指定した Path オブジェクトのディレクトリ内のエントリ (ファイルおよびサブディレクトリ) を Stream<Path> オブジェクトとして返す (ファイルを表す Path オブジェクトを指定すると java.nio.file.NotDirectoryException がスローされる)
  • walk メソッド : 深さ優先でファイルツリーを再帰的にトラバースし、結果を Stream<Path> オブジェクトとして返す (デフォルトではシンボリックリンクは辿らない)

Files クラスの readAllLines メソッドは Path オブジェクトを引数に取ってファイル内のすべての行を文字列として格納した List<String> オブジェクトを返す。

java.io.File クラスの list メソッドは自身のディレクトリの直下のファイルおよびディレクトリを String 配列で返す。listFiles メソッドは自身のディレクトリの直下のファイルおよびディレクトリを File 配列で返す。

並行性

並行処理ユーティリティは、java.util.concurrent パッケージとサブパッケージである java.util.concurrent.atomic パッケージおよび java.util.concurrent.locks パッケージから構成される。スレッドプール、並行コレクション、アトミック変数、カウンティングセマフォなどの機能を提供する。

スレッドプールは、あらかじめスレッドを生成して貯蔵することでスレッド生成のオーバーヘッドを軽減し、スレッドの管理性を向上させる。Executor フレームワークがスレッドプールの機能を提供する。

並行コレクションは、複数スレッドから並行アクセスされることを前提に設計されている。従来の同期化コレクションでは、状態へのアクセスを直列化することでスレッドセーフを実現している。

アトミック変数は、一連の処理が完全に終了しているか未着手であるかを保証することで、データの妥当性や整合性を保証する。アトミック (原子性) とは、2つ以上の処理によって構成される一連の処理においてそれぞれの処理が不可分であることを表す概念。java.util.concurrent.atomic パッケージのクラスによって提供される。

カウンティングセマフォは、リソースに対して並行アクセス可能なスレッド数を任意に設定できる仕組み。java.util.concurrent.Semaphore クラスによって提供される。セマフォは、リソースに対して並行アクセスするプロセスやスレッド間における同期や割り込みを制御する仕組み。カウンティングセマフォの他に、リソースに対するアクセスが可能か不可能かいずれかの値を取るバイナリセマフォがある。

コレクションから Iterator オブジェクトを取得した後に元のコレクションの要素を操作した場合、ConcurrentModificationException がスローされる。並行処理ユーティリティに含まれるコレクションを使用した場合は例外はスローされない。

java.util.concurrent.ConcurrentMap インタフェースは従来の Map 実装クラスに加えてアトミック操作をサポートする。ロックストライピング (細粒度のロック方式) によって並行処理における実行性能を最適化している。パフォーマンス向上のトレードオフとして size メソッドや isEmpty メソッドの厳密性要件は弱いため、正確な値を返すとは限らない。設計方針は Weakly Consistent (弱い整合性) である。従来の Hashtable クラスや Collections.synchronizedMap メソッドはマップ全体をロックして排他的アクセスを実現する。

java.util.concurrent.CopyOnWriteArrayList クラスは、リストの要素を変更するメソッドを呼び出すたびに内部の配列のコピーを作成して、コピー作成時に操作を実行する。リストのサイズが大きくなるとコピー処理のオーバーヘッドが大きくなりパフォーマンスが悪くなるため、大量データのリストに対する更新処理には List の同期化ラッパーを返す Collections.synchronizedList メソッドを使う。

AtomicInteger クラスの accumulateAndGet(int, IntBinaryOperator) メソッドや getAndAccumulate(int, IntBinaryOperator) メソッドは値の更新値をカスタマイズすることができる。引数に指定する IntBinaryOperator の getAsInt メソッドは、第1引数に現在値、第2引数に更新値を受け取る。単純に更新値分を増減する場合は、getAndAdd(int) メソッドや addAndGet(int) メソッドを使う。

java.util.concurrent.CyclicBarrier クラスは、スレッドパーティ内のスレッドの動作を協調して動作させる機能を提供する。スレッドはバリアに到達すると他のスレッドが到達するまで待機する。他のスレッドがバリアに到達するとバリアが解除される。この動作をトリップと呼ぶ。トリップ可能とするスレッドパーティ数はコンストラクタで指定する。トリップ時に実行する処理を Runnable オブジェクトで指定することができる。

Thread クラスではタスクごとにスレッドを生成して複数のタスクを非同期に実行できる。スレッド数の上限は設定できない。上限を超えてスレッドを生成すると java.lang.OutOfMemoryError が発生する。スレッドの生成と破棄にはオーバーヘッドが生じる。

Executor フレームワークは主にスレッドプールの実装を提供する。その他、タスク実行におけるライフサイクル管理や監視、統計収集などの機能を提供する。

ScheduledThreadPoolExecutor クラスはタスクのスケジューリング (遅延実行や周期実行) を実現する。

java.util.concurrent.Executor インタフェースはタスクの実行者と実行方法 (スレッドの生成と実行) を分離する。

Executor インタフェースのサブインタフェースである ExecutorService インタフェースはタスクの終了管理と進行状況を管理する機能を定義する。ExecutorService インタフェースのサブインタフェースである ScheduledExecutorService インタフェースはタスクのスケジュール管理の機能を定義する。

java.util.concurrent.Executor インタフェースは Runnable オブジェクトを引数に取って戻り値を返さない execute メソッドを定義している。Executor の実装を取得する場合は java.util.concurrent.Executors のファクトリメソッドを利用する。execute メソッドではなく、ExecutorService の submit メソッドを使用することも可。

Executor フレームワークの実装は以下の通り。

public class MyExecutor implements Executor {
    @Override
    public void execute(Runnable command) {
        new Thread(command).run();
    }
}

タスクを実行する場合は以下の通り。

Runnable task = () -> System.out.println("OK");
Executor executor = new MyExecutor();
executor.execute(task); // => OK

Runnable インタフェースの run メソッドでタスクを実行する場合、戻り値を返したりチェック例外をスローすることはできない

戻り値を返したりチェック例外をスローする場合は、java.util.concurrent.Callable<T> インタフェースを使用する。Callable インタフェースの call メソッドでタスクを実行するときは ExecutorService の submit メソッドを使用する。(submit メソッドの内部で call メソッドを呼び出す)

submit メソッドは非同期タスクの実行結果を表す Future オブジェクトを返す。Future オブジェクトの get メソッドで実行結果を取得する。

Future<T> インタフェースは非同期で実行したタスクの実行結果を取得できる。その他、タスク完了の判定やキャンセルなどの機能を提供する。Runnable インタフェースで実装したタスクを実行した場合は Future オブジェクトの get メソッドは null を返す。

Callable タスクの実装は以下の通り。

public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        return "OK";
    }
}

Callable タスクを実行して Future オブジェクトから結果を取得する場合は以下の通り。

ExecutorService service = Executors.newSingleThreadExecutor();
Future<String> future = service.submit(new MyCallable());
System.out.println(future.get()); // => OK

ExecutorService インタフェースは Executor の終了に関する機能を提供する。

shutdown メソッドはタスクキューに残っているタスクを実行してから Executor を終了する。shutdownNow メソッドはタスクキューに残っているタスクをキャンセルして Executor を終了する。

終了した Executor は新しいタスクを受け付けず、タスクの実行を依頼しようとすると java.util.concurrent.RejectedExecutionException をスローする。

isShutdown メソッドは Executor が終了しているかどうかを返す。実行中のタスク有無は問わない。Executor のタスクキュー内のタスクがすべて実行されたかどうかは isTerminated メソッドを使う。

Fork/Join フレームワークは、大きいタスクを小さいタスクに分割し、分割したタスクを複数のスレッドで同時並行に実行する仕組みを提供する。マルチコアCPUを効率的に利用することを目的とする。

Work-stealing と呼ばれるアルゴリズムを採用している。ワーカースレッドは自身のワークキューに両端キューを持つ。分割 (fork) したタスクをワークキューに挿入する。ワークキュー内のタスクをすべて実行したワーカースレッドは、他のスレッドのワークキューの末尾から未処理のタスクを横取り (stealing) して処理する。ワークキューの先頭にはキューを所有するワーカースレッドのみがアクセスできる。

Fork/Join フレームワークを利用してタスクを実行するには、ExecutorService インタフェースの実装クラスである java.util.concurrent.ForkJoinPool クラスを使う。実行するタスクは、抽象クラスの java.util.concurrent.ForkJoinTask<V> クラスを使う。

ForkJoinPool クラスでタスクを実行するメソッドは以下の通り。

  • void execute(ForkJoinTask<?>) メソッド : 非同期でタスクを実行して処理結果は返さない
  • <T> T invoke(ForkJoinTask<T>) メソッド : タスクの完了を待機して処理結果を返す
  • <T> ForkJoinTask<T> submit(ForkJoinTask<T>) メソッド : 非同期でタスクを実行して処理結果を返す (ForkJoinTask オブジェクトを返す)

通常、ForkJoinPool で実行するタスクには java.util.concurrent.ForkJoinTask<V> クラスのサブクラスを利用する。いずれも抽象クラスで、抽象メソッドとして compute メソッドを宣言している。継承するサブクラスでは compute メソッドを実装する。

  • RecursiveAction クラス : 処理結果として戻り値が不要な場合
  • RecursiveTask<V> クラス : 処理結果として戻り値が必要な場合 (戻り値の型は V)

compute メソッドは、Executor となる ForkJoinPool オブジェクトの execute メソッド、invoke メソッド、submit メソッドのいずれかのメソッドから間接的に呼ばれる。

ForkJoinTask クラスの fork メソッドは現在のスレッドプール内でタスクを非同期に実行する。join メソッドは実行が完了したタスクの結果を返す。

以下、RecursiveTask クラスの実装例。最初にタスクを fork メソッドで非同期実行しつつ、次のタスクを compute メソッドで実行する。最後に最初のタスクを join メソッドで待ち合わせて結果を返す。実際は、compute メソッドの中でさらに並列実行されている可能性はある。

// タスクを分割する
MyTask task1 = new MyTask(...);
MyTask task2 = new MyTask(...);

// タスク1を非同期に実行する
task1.fork();
// computeでタスク2を実行してタスク1の実行結果と合わせる
task2.compute() + task1.join();

ストリームAPIの処理は、順次処理か並列処理のいずれかで実行する。stream メソッドでストリームオブジェクトを生成した場合は順次実行モードになる。parallelStream メソッドでストリームオブジェクトを生成した場合は並列実行モードになる。

順次処理から並列処理の変更は parallel メソッドを使う。並列処理から順次処理の変更は sequential メソッドを使う。

ストリームオブジェクトの要素数や処理内容によっては並列処理でもパフォーマンスが劣化することがある。

JDBCによるデータベース・アプリケーションの作成

JDBCJDBC APIJDBC ドライバの2つのレイヤから構成される。JDBC ドライバは JDBC API で規定されるインタフェースの実装を提供する。具体的には、java.sql パッケージの Connection インタフェースStatement インタフェースResultSet インタフェース

DriverManagerSQLException はインタフェースではなくクラス。

JDBC は、接続URLと呼ばれる識別子を使用してデータベースに接続する。ポート番号を省略した場合はDB製品のデフォルトが使われる。

jdbc:<db_product_name>://<hostname>:<port>/<db_name>?<option...>

DBに接続するには、java.sql.DriverManager クラスの static メソッドである getConnection メソッドを利用する。getConnection メソッドは java.sql.Connection オブジェクトを返す。DBの接続に失敗した場合は java.sql.SQLException がスローされる。

JDBC 3.0 では Class.forName("JDBC ドライバのクラス名")JDBC ドライバをロードする必要がある。JDBC 4.0 ではクラスパス上の JDBC ドライバを自動でロードするため Class.forName メソッドの呼び出しは不要。

JDBC 4.0 以降に準拠したドライバでは、ドライバの完全修飾クラス名を記述した java.sql.Driver ファイルを jar ファイルの META-INF/services ディレクトリに配置する。

トランザクションの分離レベルに関する設定は、java.sql.Connection インタフェースの setTransactionIsolation メソッドを使用する。DBそのものの設定は Connection オブジェクトの getMetaData メソッドで返される DatabaseMetaData オブジェクトから取得する。

DBにSQL文を発行するには java.sql.Statement オブジェクトを使用する。Statement オブジェクトの取得には、Connection オブジェクトの createStatement メソッドを使用する。一般的には Statement インタフェースのサブインタフェースとして、 プリコンパイルされたSQLを実行するための PreparedStatement やストアドプログラムを実行するための CallableStatement を使用する。

SQLを実行する場合は Statement インタフェースで宣言されている以下のメソッドを利用する。

  • executeQuery メソッド : クエリ (SELECT) を実行して ResultSet オブジェクトを返す
  • executeUpdate メソッド : 更新系のSQLDDL文 (CREATE TABLE など) を実行して更新された行数を int で返す
  • execute メソッド : クエリ/更新系SQLを実行する (実行したSQLがクエリなら true、実行したSQLがクエリ以外の場合は false を返す)

execute メソッドの戻り値が true の場合、ResultSet オブジェクトが返っているため、getResultSet メソッドで ResultSet オブジェクトを取得する。execute メソッドの戻り値が false の場合、getUpdateCount メソッドで更新行数を取得することができる。

Java 7 以降なら try-with-resources を使って close する。

try (Connection conn = DriverManager.getConnection(url);
     Statement stmt = conn.createStatement();
     ResultSet rs = stmt.executeQuery("SELECT * FROM foo")) {
    while (rs.next()) {
        // ...
    }
} catch (SQLException e) {
    e.printStackTrace();
}

ResultSet オブジェクトは、クエリの結果セットを表すオブジェクト。内部のポインタ (カーソル) で処理対象の行 (レコード) を指定する。レコードの特定列のデータを取得するには ResultSet インタフェースが宣言しているデータ型に対応した getXxx メソッドを利用する。

getXxx メソッドで列インデックスをを指定する場合、最初の列のインデックスは1であることに注意。

カーソルの初期位置は先頭レコードの1つ前を指す。カーソルが先頭レコードの1つ前を指している状態でレコードを操作するメソッドを呼び出すと SQLException がスローされる。同様にカーソルが最終レコードの1つ後ろを指している状態でレコードを操作するメソッドを呼び出すと SQLException がスローされる。

一般的に、カーソルを移動する際は next メソッドを利用する。移動先にレコードが存在する場合は true を返す。レコードが存在しない場合は false を返す。

ResultSet インタフェースから日付や時刻を取得する場合は以下のメソッドを使う。戻り値はいずれも java.util.Date のサブクラス。

  • getDate メソッド : java.sql.Date オブジェクトを返す (日付)
  • getTime メソッド : java.sql.Time オブジェクトを返す (時間)
  • getTimestamp メソッド : java.sql.Timestamp オブジェクトを返す (日付と時間)

java.sql.Date クラス、java.sql.Time クラス、java.sql.Timestamp クラスでは、それぞれ toLocalDate メソッド、toLocalTime メソッド、toLocalDateTime メソッドが提供されている。(Date and Time API が提供する型に変換する)

ResultSet オブジェクトは、元の Statement オブジェクトや Connection オブジェクトが close された場合は利用できない。1つの Statement オブジェクトから取得できる有効な ResultSet オブジェクトは1つのみ。2つ以上の ResultSet オブジェクトを取得すると1つ目の ResultSet オブジェクトは close される。close された ResultSet オブジェクトを利用すると SQLException がスローされる。

スクロール可能な ResultSet (Scrollable ResultSet) はカーソルを任意の位置に移動できる。

createStatement メソッドは、引数を取らないか2つの引数を取るメソッドのみ提供されている。

Statement stmt = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);

第1引数には ResultSet オブジェクトの型を示す定数を指定する。

  • ResultSet.TYPE_FORWARD_ONLY : 順方向のみ (デフォルト)
  • ResultSet.TYPE_SCROLL_INSENSITIVE : スクロール可能 (元のデータの変更は ResultSet に反映されない)
  • ResultSet.TYPE_SCROLL_SENSITIVE : スクロール可能 (元のデータの変更が ResultSet に反映される)

第2引数には ResultSet によるレコードの更新可否を表す定数を指定する。

  • ResultSet.CONCUR_READ_ONLY : 更新できない ResultSet オブジェクトを示す
  • ResultSet.CONCUR_UPDATABLE : 更新できる ResultSet オブジェクトを示す

スクロール可能な ResultSet が提供しているカーソルを相対位置に移動するメソッド。

  • boolean next メソッド : カーソルを現在位置から順方向に1行移動する
  • boolean previous メソッド : カーソルを前の行に移動する
  • boolean relative(int) メソッド : カーソルを正または負の相対行数だけ移動する

スクロール可能な ResultSet が提供しているカーソルを絶対位置に移動するメソッド。

  • boolean absolute(int) メソッド : カーソルを指定された行番号に移動する
  • boolean afterLast メソッド : カーソルを終端に移動する (最終行の次の行)
  • boolean beforeFirst メソッド : カーソルを先端に移動する (先頭行の前の行)
  • boolean first メソッド : カーソルを先頭行に移動する
  • boolean last メソッド : カーソルを最終行に移動する

absolute メソッドで先頭行に移動する場合は1を指定する。0を指定した場合は先頭行の前の空行を指す。レコード数より大きな行番号を指定した場合は最終行の次の空行を指す。

更新可能な ResultSet では、レコードの挿入、更新、削除ができる。変更内容をDBに反映するには updateRow メソッドを呼び出す。updateRow メソッドを呼び出すまでは元のデータを返す。また、updateRow メソッドを呼び出す前に cancelRowUpdates メソッドを呼び出すと、ResultSet への変更を取り消すことができる。

更新可能なResultSet で新規レコードを挿入するには、moveToInsertRow メソッドを呼び出してカーソルを挿入専用行に移動する必要がある。データをセットする場合は、データ型に対応した updateXxx メソッドを利用し、insertRow メソッドでレコードをテーブルに挿入する。

カーソルを挿入専用行に移動する直前の位置に戻すには moveToCurrentRow メソッドを呼び出す。

java.sql.SQLException クラスの getErrorCode メソッドはベンダー固有のエラーコードを返す。getSQLState メソッドはSQLステートを返す。SQLステートは ANSI SQL で標準化されているデータベース製品共通の情報。

SQLException は setNextException(SQLException) メソッドでチェーンすることができる。チェーンした例外は SQLException getNextException メソッドや Iterator<Throwable> iterator メソッドで取得できる。

ローカライズ

特定の地域にアプリケーションを対応させることをローカライズと呼ぶ。言語や数値、日付、通貨などを特定の国や地域で使用されている形式で表示する。ロケールjava.util.Locale クラスがサポートする。

Locale locale = new Locale("言語コード", "国コード", "バリアント値");
  • 言語コード : 2桁の英小文字で表される (日本語なら ja)
  • 国コード : 2桁の英大文字で表される (日本なら JP)
  • バリアント値 : Locale のバリエーションを示すために使用される任意の値

システムのデフォルトの Locale オブジェクトを取得するには Locale クラスの static メソッドで宣言されている getDefault メソッドを使用する。

Locale コンストラクタで指定する言語コードや国コードは正しい書式としての妥当性はチェックされない。Locale クラスの static メンバークラスである Locale.Builder クラスのセッターメソッドでロケールに関連する情報を設定する場合は書式がチェックされる。書式が不正な場合は java.util.IllformedLocaleException がスローされる。

// Builder パターンで設計されている
Locale locale = new Locale.Builder().setLanguage("cat").setRegion("ES").build();

一般的によく使われる国や言語のロケールに対応した Locale オブジェクトを参照する public な定数が提供されている。

Locale locale = Locale.JAPAN;

プロパティは key=value 形式で表示されるデータ。プロパティを記述したファイルをプロパティファイルと呼ぶ。プロパティファイルは、java.util.Properties クラスで扱う。

Properties クラスは、特定のフォーマットに従うテキストファイルXML ファイルに対応する。テキストファイルの場合、キーと値は =: で区切って1行ごとに記述する。#! で始まる行はコメントとして扱う。

プロパティファイルは FileInputStream クラスか FileReader クラスで読み込む。

Properties prop = new Properties();
prop.load(input); // InputStream オブジェクトか Reader オブジェクトを指定する
String value = prop.getProperty("key");

getProperty メソッドは、指定されたキーが存在しない場合は null を返す。第2引数にデフォルト値を指定するオーバーロードメソッドが存在する。

Properties クラスは、Map インタフェースの実装である java.util.Hashtable クラスのサブクラスである。Map インタフェースの default メソッドである forEach メソッドで読み込んだプロパティを繰り返し処理できる。単純にコンソールに出力する場合は、PrintStream オブジェクトか PrintWriter オブジェクトを引数に取る list メソッドを利用する。

リソースバンドル (Resource Bundle) は、主にUIにおける言語や表示形式をロケールごとに1つにまとめたもの。

リソースバンドルは、ロケールごとに用意されたクラスファイルプロパティファイル (.properties) で表す。標準では XML 形式には対応していない

リソースバンドルは、<基底名_言語コード_国コード> の形式でファイル名を付ける。リソースバンドルはクラスパス上に配置する。パッケージディレクトリに配置した場合は Java のクラスと同じようにパッケージ名で修飾する。

リソースバンドルは java.util.ResourceBundle クラスで扱う。ResourceBundle クラスは抽象クラスであるため、static メソッドである getBundle メソッドを使用して ResourceBundle オブジェクトを取得する。

getBundle メソッドでリソースバンドルを検索する流れは以下の通り。リソースバンドルが見つからない場合は java.util.MissingResourceException がスローされる。

  1. <言語コード_国コード> が一致するリソースバンドル (クラスファイル→プロパティファイル)
  2. <言語コード> が一致するリソースバンドル (クラスファイル→プロパティファイル)
  3. <基底名> が一致するリソースバンドル (クラスファイル→プロパティファイル)

クラスを使用してリソースバンドルを作成する場合は ResourceBundle クラスのサブクラスである java.util.ListResourceBundle クラスや java.util.PropertyResourceBundle クラスを継承したクラスを作成する。ListResourceBundle クラスは抽象クラスであり、サブクラスで抽象メソッドである getContents メソッドをオーバーライドする。getContents メソッドはキーと値のペアを Object[][] 型で返すように実装する。

protected Object[][] getContents() {
    return new Object[][] {
        { "key1", "value1" },
        { "key2", "value2" },
    };
}

値を取得する場合は ResourceBundle クラスの getString メソッドを呼び出す。

JSUG勉強会 2017年その3 ~ ドメイン駆動設計 powered by Spring に行ってきた

JSUG勉強会 2017年その3 に行ってきました。簡単に所感をまとめます。

jsug.doorkeeper.jp

今回のテーマはドメイン駆動設計と Spring です。資料はこちら。

www.slideshare.net

  • ドメインロジックに集中する
  • Spring Frameworkドメインモデル以外のことをすべて用意するフレームワーク
  • ドメインモデルは振る舞いとデータの両方を組み込んだオブジェクトモデル
    • ドメインロジックをオブジェクトで表現する
    • データを持つクラスが唯一ロジックを持つ
    • 変更容易性を向上する
  • ドメインロジックをモジュール化する場合はドメインオブジェクトとして部品化する
  • ドメインロジックを自己文書化する
    • ドメインオブジェクトを使う他のレイヤのコードに自己文書化が波及する
    • 引数や戻り値の型で何をしているかが分かる
  • ドメインを隔離する
  • ドメインオブジェクトに getter/setter を書かない
    • DirectFieldAccess (↓のスライドを参照)
  • Service クラスに @Validated を付ける
    • メソッドの引数や戻り値にバリデーションができる
    • 契約による設計 (事前条件/事後条件) の考え方が実現できる
  • モデルの一部としてインタフェースを宣言する
    • Repository インタフェース
    • インタフェースで実装を分離する
    • データベースの都合をドメインオブジェクトに持ち込まない
    • データソース層がドメインモデルに依存する (依存関係が逆転する)

今回、3層のレイヤ (@Controller, @Service, @Repository) ごとに説明されていて、自分には理解しやすくとても参考になりました。Spring 特有の話ではあったかもしれませんが、難しく考えがちなドメイン駆動設計が身近なものに感じられました。

一気に全体を書き替えるのではなく、まずはトランザクションスクリプトの中に埋め込まれてるドメインロジックをドメインオブジェクトに切り出すところからスタートする感じです。ようするにリファクタリングですかね。

DirectFieldAccess は初めて知りました。あと、クラスに @Validated を付けられるんですね...。これも初めて知りました。契約による設計をシンプルに実装できそう。あとで試してみよう。