概要
- Java Gold 試験用にまとめていた自分用メモ
1. Java のクラス設計
hashCode メソッド
- オブジェクトの識別に使用される int 型のハッシュ値を生成して返すメソッド
- デフォルトではメモリアドレスを基にした値を生成
- オブジェクトを検索する際のパフォーマンスを向上させる目的で使用
final 属性
対象 | 意味 |
---|---|
final クラス | 継承不可 |
final 変数(フィールド / ローカル) | 再代入不可(定数) |
final メソッド | オーバーライド不可 |
下記はいずれも可
void doIt() (final int x) {
final int y = 1;
}
static 初期化子
コード | 実行タイミング |
---|---|
static 初期化子 | クラスロード時 |
インスタンス初期化子 | インスタンス生成前 |
コンストラクタ | インスタンス生成後 |
public class Hoge {
static int x;
{ /* インスタンス初期化子 */ }
static {
x = 1;
/* static 初期化子 */
}
Hoge() {
/* コンストラクタ */
}
}
- クラスロードは jvm が勝手に行うが、Class.forName で意図的に呼び出すこともできる。
Java SE 8 インタフェース仕様
以前は抽象メソッドのみだったが
- default メソッド
- static メソッド
を宣言できるようになった。
interface Foo {
void x();
static void y() {
/* static method */
// Foo.y() でしか呼び出せない
// Foo を実装した Bar で Bar.y() は不可
}
default void z() {
/* default method */
}
}
-
同じシグニチャの default メソッドを持つ複数のインタフェースを実装するとコンパイルエラー
- @Override すれば OK
-
菱形継承される
抽象クラス
- 抽象メソッドがあるからといって、暗黙的に抽象クラスとはならない。明示的に abstract キーワードを記述する必要がある。
入れ子クラス
public class Outer {
/* static メンバークラス */
static class X { }
/* メンバークラス(内部クラス) */
private class Y { }
void doIt() {
/* ローカルクラス(内部クラス) */
class Z { }
}
}
クラスの内部で呼び出すとき
- 内部クラスは static メソッドからは呼び出せない(インスタンスの生成は保証されてないため)
public class Outer {
public static void x() {
Outer outer = new Outer();
Inner inner1 = outer.new Inner(); // 内部クラスは外部クラスのインスタンス参照を用いて呼び出す
Inner inner2 = new Outer().new Inner(); // OK
inner.doIt();
}
public class Inner {
public void doIt() {
System.out.println("doIt");
}
}
}
他のクラスから呼び出すとき
public class Ohter {
public void doIt() {
Outer.Inner inner = new Outer().new Inner();
inner.doIt();
}
}
匿名クラス
public interface Foo() {
public void doIt();
}
...
Foo foo = new Foo() {
@Override
public void doIt() {
System.out.println("OK!");
}
}
foo.doIt();
...
Other ohter = new Other();
other.doSomething(new Foo() {
public void doIt() {
System.out.println("OK!"); // 再利用性が低いならこのように記述するのもあり
}
})
enum
- トップレベルでも入れ子型でも可能
- クラスとしてコンパイルされ、フィールドやメソッドも持てる
- ただし、ローカルクラスと違ってメソッド内で宣言することは不可
public class TrafficController {
public enum Stoplight {
GREEN, YELLOW, RED;
}
}
- 暗黙的に java.lang.Enum クラスのサブクラスになる
- 他のクラスは継承不可
- インタフェースの implements は可能
- 暗黙的に final
- トップクラスの場合、public もしくはデフォルトのみ
- 入れ子の場合は全部 OK
/* 列挙定数の文字列を取得するメソッドは2パターン */
String green = Stoplight.GREEN.name(); // Enum で final 宣言されてる
String red = Stoplight.RED.toString(); // カスタマイズ可能
/* 文字列から列挙型を取得 */
Stoplight green = Stoplight.valueOf("GREEN");
Stoplight yellow = Stoplight.valueOf("yellow") // IllegalArgumentException
/* 配列で全列挙しを返す */
Stoplight[] list = Stoplight.values();
/* 宣言された順番を返す */
Stoplight green = Stoplight.GREEN;
int g = green.ordinal(); // 0
- 列挙型では private コンストラクタしか宣言できない
public enum Size {
NA, S(1), M(5), L(10);
private int value;
private Size() {} // 引数を取らないコンストラクタ。NA に使われる。初期値「0」
private Size(int value) {
this.value = value;
}
public int getValue() {
return value; // getter は書いて良い
}
}
2. コレクションとジェネリクス
ジェネリッククラス
/* 以下はいずれもコンパイルエラー */
Foo<Number> foo = new Foo<Long>(); // スーパークラスでも異なっていてはダメ
Foo<String> foo = new Foo<>() {}; // 匿名クラスは型宣言を省略できない
/* 以下はいずれもコンパイルエラー */
T obj = new T(); // 型変数によるインスタンス生成
T[] array = new T[3]; // 型変数を要素に指定した配列の生成
static T t; // static
void doIt() {
class<T> klass = T.class; // .class の参照
}
void doIt(T t) {
if (t instanceof T) { /* do something */} // instanceof による型比較
}
/* 以下は OK */
T t; // インスタンス生成を伴わない宣言
T doIt() {
/* do something */
return t; // 返却
}
ジェネリックメソッド
戻り値の記述の前に型パラメータを宣言する必要がある
class Foo {
<T> void doIt(T t) { /* do something */}
}
class Foo {
<T> T doIt() { /* do something */ }
}
型境界
class Foo<T extends Number> {}
class Foo<T, U extends T> {}
/* interface でも extends を使う */
interface X {}
class Foo<T extends X> {}
ワイルドカード ?
<? extends T> // 上限境界(有界ワイルドカード)
<? super T> // 下限境界(有界ワイルドカード)
<?> // 境界なしワイルドカード =<? extends Object>
...
class Foo<?> {} // NG(型パラメータリストでは使えない)
class Foo<T> {}
Foo<?> foo = new Foo<?>(); // NG(インスタンス生成でも使えない)
class Foo<T> {}
Foo<?> foo; // OK
List<E> インタフェース
- List インタフェースは Collection インタフェースを継承している
- List<E> インタフェース
ArrayList<E>
-
LinkedList<E>
- 双方向連結リスト
Vector<E>
CopyOnWriteArrayList<E>
ArrayList クラス
- スレッドセーフでない
- 内部的に配列を使用
- インデックスを指定したランダムアクセスのパフォーマンスは高速
- 要素追加時は、新しい配列を作成して1つずつずらしてコピーされるので
- 先頭に近い部分に要素を追加する
- 全体の要素数が多い
ときはパフォーマンスが劣化する。
LinkedList クラス
- スレッドセーフでない
- 双方向に繋がっているリンクを使用
- ランダムアクセスは、順にたどっていくため中心に近いほど遅い
- 要素の追加は、リンクを貼り直すだけなので高速
Arrays クラス
List<Double> Arrays.asList(1.5, 2.0); // OK
ArrayList<Double> Arrays.asList(1.5, 2.0); // NG java.util.ArrayList クラスとは互換性がない
Integer[] array = {1, 2, 3};
List<Integer> list1 = Arrays.asList(array) // Arrays.asList には配列も渡せる
// ↑ List には add メソッドなどがない
// ↓ ArrayList として扱いたければ以下のようにコンストラクタを使う
ArrayList<Integer> list2 = new ArrayList<>(list1);
list1.set(0, 2); // list1 は内部的に array を見ているだけなので、この変更は array に影響する
list2.add(4); // new した ArrayList はもとのオブジェクトとは関係なくなっているので、array に影響しない
System.out.println(Arrays.toString(array)); // [2, 2, 3]
Queue<E> インタフェース
Queue<E> インタフェース
-
Deque<E>
インタフェース:FIFO を実現-
ArrayDeque<E>
クラス -
LinkedList<E>
クラス-
List<E>
も継承している
-
-
ConcurrentLinkedDeque<E>
クラス -
LinkedBlockingDeque<E>
クラス
-
Deque<E> インタフェース
- 両端から挿入・削除可能
- スタックとしてもキューとしても用いることができる
- インデックスによる要素へのアクセスなし
ArrayDeque<E> クラス
- FIFO(キュー)として用いるなら LinkedList<E> よりも高速
- FILO(スタック)として用いるなら Stack<E> よりも高速
- スレッドセーフでない(Stack<E> はスレッドセーフ)
- null を格納することはできない
Set<E> インタフェース
- equals メソッドによって重複が決まる
- 通常、null は1つだけ(禁止する実装もある)
- Set<E> インタフェース
- HashSet<E> クラス
- 要素の管理にハッシュテーブルを使用
- SortedSet<E> インタフェース
- NavigableSet<E> インタフェース
- TreeSet<E> クラス
- 木構造に基づく実装で要素の順序付けをサポート
- TreeSet<E> クラス
- NavigableSet<E> インタフェース
- HashSet<E> クラス
Map<K, V> インタフェース
-
コレクションフレームワークに含まれているが、Map は Collection インタフェースのサブインタフェースではない
-
Map<K, V> インタフェース
- Hashtable<K, V> クラス
- HashMap<K, V> クラス
- SortedMap<K, V> インタフェース
- NavigableMap<K, V> インタフェース
- TreeMap<K, V> クラス
- NavigableMap<K, V> インタフェース
-
Map.Entry<K, V> インタフェース
Map<Integer, String> map = new HashMap<>();
...
for (Map.Entry<Integer, String> entry : map.entrySet()) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
Comparable<T> インタフェース
-
Comparable<T>:自身と他のオブジェクトが比較可能なことを表す
-
TreeSet<E> は、要素を並び替えるために Comparable 型にキャストする
- そのため、Comparable が実装されていないと例外がスローされる
public class Product {
private int id;
private String name;
public Product(int id, String name) {
this.id = id;
this.name = name;
}
}
...
Set<Product> set = new TreeSet<>();
set.add(new Product(2, "B")); // 実行時に例外スロー
set.add(new Product(1, "A")); // 実行時に例外スロー
// Product は Comparable<Product> を実装している必要がある
...
// 正しい実装
public class Product Comparable<Product> {
...
public int compareTo(Product o) {
return this.id - o.id;
}
}
- compareTo は
- 自分が小さいときは負
- 等しければ 0
- 自分が大きいときは正
Comparator<T> インタフェース
- Comparator<T>:2つの T オブジェクトの比較ができることを表す
- TreeSet や TreeMap で格納されるときに、要素の並べ順序をデフォルトとは別の順序にしたいときに使う
class LengthComparator implements Comparator<String> {
@Override
public int compare(String o1, String o2) {
return o1.length() - o2.length();
}
}
...
Set<String> set = new TreeSet<>(new LengthComparator()); // 文字数順に並ぶようになる
...
// 以下のようにもかける(匿名クラス)
Set<String> set = new TreeSet<>(new Comparator<String>() {
public int compare(String o1, String o2) {
return o1.length() - o2.length();
}
})
3. ラムダ式と組込み関数型インタフェース
関数型プログラミング
- Java SE 8 で導入
- ラムダ式
- メソッド参照
- ストリーム API
関数型インタフェース
- 抽象メソッドが1つのみのインタフェース
- Single Abstract Method (SAM) 要件
- static メソッド、default メソッドは SAM に関係ない
/* 関数型インタフェースとして妥当 */
interface Foo {
void x(); // 抽象メソッド(SAM)
default void y() {} // デフォルトメソッド
static void z() {} // static メソッド
}
-
@FunctionalInterface
アノテーションで、関数型インタフェースの妥当性をコンパイルチェックできる
@FunctionalInterface
public interface Foo {
void x();
void y(); // コンパイルエラー
}
ラムダ式
Runnable r = () -> System.out.println("ok");
(String msg) -> System.out.println(msg); // ok
(msg) -> System.out.println(msg); // ok 引数の型は省略可能
msg -> System.out.println(msg); // ok 引数が1つなら () 省略可能
/* () を省略したら引数の型は宣言不可 */
String msg -> System.out.println(msg); // コンパイルエラー
(int a, int b) -> System.out.println(a + b); // ok
(a, b) -> System.out.println(a + b); // ok
/* 引数が2つ以上だと () 省略不可 */
a, b -> System.out.println(a + b); // コンパイルエラー
int a, int b -> System.out.println(a + b); // コンパイルエラー
x -> { return x * x }; // ok
x -> x * x; // ok {} を省略した場合、return は不要
/* {} で囲った場合は return が必要 */
x -> { x * x }; // コンパイルエラー
/* {} を省略した場合、return はつけられない */
x -> return x * x; // コンパイルエラー
function パッケージ
-
43 あるが、基本は以下の4つ
- Supplier<T>
- 供給する
T get()
- Predicate<T>
- 評価する
boolean test(T t)
- Consumer<T>
- 消費する
void accept(T t)
- Fuction<T, R>
- 適用する
R supply(T t)
- Supplier<T>
-
その他
- BiFunction<T, U, R>
R apply(T t, U u)
- UnaryOperator<T>
- 単項演算子
T apply(T t)
- Function<T, T> に相当
- BinaryOperator<T>
- 二項演算子
T apply(T t1, T t2)
- BiFunction<T, T, T> に相当
- BiFunction<T, U, R>
UnaryOperator<Integer> increment = i -> i + 1;
System.out.println(increment.apply(1)); // 2
BinaryOperator<Integer> add = (a, b) -> a + b;
System.out.println(add.apply(1, 2)); // 3
メソッド参照
- ラムダ式が何らかのメソッドの呼び出しのみで完結し、かつメソッドのシグニチャが同一の場合、メソッド参照(Method Reference)で記述できる
Consumer<String> c = s -> System.out.println(s);
c.accept("Hello"); // => Hello
// 以下のように書ける
Consumer<String> c = System.out::println;
BinaryOperator<Integer> op = (x, y) -> Integer.max(x, y);
// 以下のように書ける
BinaryOperator<Integer> op = Integer::max;
Function<String, Integer> f = s -> new Integer(s);
// 以下のように書ける
Function<String, Integer> f = Integer::new;
Function<String, Integer> f = s -> s.length();
// 以下のように書ける
Function<String, Integer> f = String::length; // 引数としてうけとったインスタンスの引数を持たないメソッドを呼び出す場合
String s1 = "Hello, ";
Function<String, String> f = s2 -> s1.concat(s2);
// 以下のように書ける
Function<String, String> f = s1::concat;
4.Stream API
- データ集合の操作用 API
- java.util.stream パッケージ
- java.io のファイル操作で出てくるストリームとは全く無関係
- 関数型インタフェース、ラムダ式を活用
- 関数は状態に依存しないので、並列処理に置き換えることが可能
ストリーム API の処理
- データソースからストリーム・オブジェクトを取得
- データソース:ストリーム処理の対象となるデータ集合
- ストリーム・オブジェクトに対して中間操作を適用
- 中間操作は必須ではない
- 中間操作では、処理関数を保持した新しいストリーム・オブジェクトを返すのみ
- 中間操作の処理が実行されるのは終端操作の実行時 = 「遅延評価」
- ストリーム・オブジェクトに対する終端操作を適用
- 必須
- 一度きり、終端操作を適用したストリームに再度ストリーム処理を適用することはできない
- 結果はストリーム・オブジェクトとは限らない
BaseStream<T, S extends BaseStream<T, S>> インタフェース
- 4 種類の派生ストリーム
- Stream<T>
- 参照型を扱う
- IntStream
- LongStream
- DoubleStream
- プリミティブ型
- Stream<T>
- Stream と IntStream は互換性は無い
stream メソッド
- データソースとして List や Map などのコレクションをストリーム API で処理するために変換するメソッドが用意されている
// メソッド定義
default Stream<E> stream();
//例:
List<String> list = Arrays.asList("A", "B", "C");
Stream<String> stream = list.stream();
forEach メソッド
- 繰り返し処理を終端操作として適用するためのメソッド
// メソッド定義
void forEach(Consumer<? super T> action)
// 例:
List<String> list = Arrays.asList("A", "B", "C");
Stream<String> stream = list.stream();
stream.forEach(System.out::print); // => ABC
- Iterable インタフェースにも forEach メソッドが default メソッドとして定義されている。
- Iterable インタフェースを継承する Collection インタフェースの実装クラスでは、ストリーム・オブジェクトを取得することなく forEach メソッドを利用することができる
Arrays.asList("A", "B", "C").forEach(System.out::print);
-
forEach は終端操作用メソッド
-
同一ストリームに2回呼び出すと、実行時に IllegalStateException が発生する
-
Map<K, V> インタフェースは Collection を継承していないが、forEach を使える
// メソッド定義
default void forEach(BiConsumer<? super K, ? super V> action)
// 例:
Map<Integer, String> map = new TreeMap<>();
map.put(1, "A");
map.put(2, "B");
map.forEach((k, v) -> System.out.println(k + v)); // => 1A2B
Arrays.stream メソッド
/* Arrays クラスの stream メソッド */
static <T> Stream <T> stream(T[] array) // Stream を返す
static IntStream stream(int[] array) // IntStream を返す(Stream<Integer> ではない)
// LongStream, DoubleStream も同様
...
// 範囲を引数に取れる
int[] array = {1, 2, 3, 4, 5};
IntStream stream = Arrays.stream(array, 1, 4);
stream.forEach(System.out::print); // => 234
// IntStream と LongStream では数値範囲からストリーム・オブジェクトを生成できる
IntStream.range(1, 5)
.forEach(System.out::print); // => 1234
IntStream.rangeClosed(1, 5)
.forEach(System.out::print); // => 12345
of メソッド
- 任意の参照型オブジェクト集合から Stream オブジェクトを取得
static <T> Stream<T> of(T t)
static <T> Stream<T> of(T... values)
...
X x = new X();
Y y = new Y();
Stream.of(x, y);
...
static IntStream of(int t)
static IntStream of(int... values)
// LongStream, DoubleStream も同様
lines メソッド
- テキストファイルから読み込んだ文字列を Stream オブジェクトとして扱うことができる
- java.io.BufferedReader と java.nio.file.Files クラスに lines メソッドが追加
// BefferedReader メソッド定義
Stream<String> lines()
// 例:
try (BufferedReader br = new BufferedReader(new FileReader("sample.txt"))) {
br.lines().forEach(System.out::println);
} catch (IOException e) {
e.printStackTrace();
}
// Files メソッド定義
static Stream<String> lines(Path path)
// 例:
Path path = Paths.get("sample.txt");
Files.lines(path).forEach(System.out::println);
フィルタリング
- 代表的な中間操作
// メソッド
Stream<T> filter(Predicate<? super T> p)
// 例
IntStream.range(1, 5).filter(n -> n % 2 == 0)
.forEach(System.out::print) // => 24
マッピング
- 代表的な中間操作
// メソッド
Stream<T> map(Function<? super T, ? extends R> mapper)
IntStream.range(1, 5).map(n -> n * 2)
.forEach(System.out::print) // => 2468
ソート
// メソッド
Stream<T> sorted() // 自然順序
Stream<T> sorted(Comparater<? super T> c)
// 例
Arrays.asList(2, 3, 1).stream()
.sorted((i, j) -> j - i)
.forEach(System.out::print); // => 321
comparing メソッド
- Comparator<T> インタフェースに追加された便利な static メソッド
/* TreeSet のソート */
// ラムダ式を渡す方法
Set<Employee> set = new TreeSet<>((e1, e2) -> e1.getId() - e2.getId());
// comparing メソッドを使用する方法
// ソートのキーに使用する値を返すように実装された Function オブジェクトを引数にわたすことで、
// 適切な Comparator オブジェクトを生成して返す
Set<Employee> set = new TreeSet<>(Comparator.comparing(Employee::getId));
// comparingInt, ComparingLong などもある
peek メソッド
- 単純にもとのストリーム・オブジェクトを返す中間メソッド
- 何らかの副作用を伴う処理を実行できる
- 通常はデバッグ目的
// メソッド定義
Stream<T> peek(Consumer<? super T>) action
// 例
Stream<String> stream = Stream.of("banana", "apple", "orange")
.filter(e -> e.length() > 5)
.peek(e -> System.out.print(e + " "))
.map(String::toUpperCase)
.peek(e -> System.out.print(e + " "));
System.out.print("ok");
long count = stream.count(); // => ok banana BANANA orange ORAGE
// 中間操作はあくまで遅延評価なので、終端操作である count() が呼ばれたときに実行される
// 1要素ごとに通しで行われるので、 ok banana orange BANANA ORANGE とはならない
短絡終端操作
- Match 系
- Predicate オブジェクトを引数にとり、boolean 型を返す
IntPredicate p = n -> n == 1;
IntStream.of(1, 2, 3).allMatch(p); // false
IntStream.of(1, 1, 1).allMatch(p); // true
IntStream.of(1, 2, 3).anyMatch(p); // true
IntStream.of(2, 3, 4).anyMatch(p); // false
IntStream.of(1, 2, 3).noneMatch(p); // false
IntStream.of(2, 3, 4).noneMatch(p); // true
- find 系
Optional<T> findAny() // ストリームのいずれかの要素を含む Optional または空の Optional を返す。常に同じとは限らない。
Optional<T> findFirst()
// Optional<T> は null かもしれない T オブジェクトをラップする
- allMatch は && とおなじ
- anyMatch は || とおなじ
IntPredicate p = i -> {
System.out.print("called ");
return i % 2 == 0;
}
// false になった時点で終了
boolean result = IntStream.of(1, 2, 3).allMatch(p); // => called 1
System.out.println(result); // false
flatMap メソッド
- 入れ子構造のストリームを平坦なストリームに変換
List<List<String>> list = Arrays.asList(
Arrays.asList("Java", "Oracle"),
Arrays.asLit("Lambda", "Java")
);
list.stream()
.flatMap(l -> l.stream()).distinct()
.forEach(System.out::print); // => JavaOracleLambda
// ここでいう l は Arrays.asList("Java", "Oracle") とか
// flatMapToInt など
merge メソッド
Map<String, String> map = new HashMap<>();
map.put("A", "B");
map.merge("A", "C", (v1, v2) -> v1.concat(v2));
map.merge("B", "C", (v1, v2) -> v2.concat(v2));
System.out.println(map);
リダクション
- データの集合を1つに要約する操作
long count() // ストリームの個数
Optional<T> max(Comparator<? super T> c) // 最大要素
Optional<T> min(Comparator<? super T> c) // 最小要素
// IntStream 専用
long count()
OptionalDouble average()
OptionalInt max()
OptionalInt min()
int sum()
// 自分で reduce メソッドを定義できる
IntStream stream1 = IntStream.of(1, 2, 3);
System.out.println(stream1.reduce((x, y) -> x + y)); // => 1 + 2 + 3 = 6
IntStream stream2 = IntStream.of(1, 2, 3);
System.out.println(stream2.reduce(1, (x, y) -> x + y)); // => 1 + 1 + 2 + 3 = 7
可変リダクション操作
- 何らかの可変コンテナ(List、Map、StringBuilder など)に要素を収集する操作
- java.util.stream.Collector<T, A, R> が提供されている
- supplier()
- accumulator()
- combiner()
- finisher()
- からなる
// Collector オブジェクトを自作する代わりに、Collectors クラスを利用できる
Double total = Arrays.asList(
new Book("Java Programming", 25.20),
new Book("Introduce Java SE 8" 22.98),
new Book("Functional Programming pocket guide", 16.77)
).stream()
.collect(Collectors.summingDouble(Book::getPrice));
5. 例外とアサーション
再スロー
public class Example {
public void doIt() throws Exception {
try {
/* NumberFormatException が発生しうるコード */
} catch (NumberFormatException e) {
throw new Exception(e); // 原因を格納(cause)
}
}
}
...
Throwable e.getCause() // cause を取り出し
multi-catch 文
- 継承関係にあるものはダメ
catch (IOException | NumberFormatException e)// OK
catch (IOException | Exception e) // コンパイルエラー
// multi-catch では暗黙的に final なので再代入するとコンパイルエラー
try{} catch (Exception e) {
e = new Exception(); // コンパイルエラー
}
try-with-resources 文
- 外部リソースを自動的にクローズする
- finally で記述するより簡潔
- 複数のときは「;」
try (FileReader in = new FileReader("in.txt");
FileWriter out = new FileWriter("out.txt")) {
// IO
} catch (IOException e) {
// 例外処理
}
- try-catch では catch ブロックまたは finally ブロックが必須
- try-with-resources はあくまでクローズが目的で、catch、finally はなくても OK
- AutoCloseable インタフェースを継承している必要がある
- ないものを try-with-resources するとコンパイルエラー
class MyResource implements AutoClosebale {
@Override
public void close() {
// close 処理
}
}
assert 文
assert x == 1; // ok
assert(x == 1); // ok
assert (x == 1); // ok
assert x == 1 : "erro message を設定可能"
- アサーション機能はデフォルトで無効
- 実行時に -ea をつける(enable assertion)
$ java -ea Foo
6. 日付 / 時刻 API
Date and Time API
- java.time
- java.time.chrono
- java.time.format
- java.time.temporal
- java.time.zone
java.time パッケージ
-
Local Time
- 時差情報を含まない現在日時
- LocalDate
- LocalTime
- LocalDateTime
-
Offset Time
- ISO 8601 に準拠した時差情報を含む
- OffsetTime
- OffsetDateTime
-
Zone Time
- タイムゾーン(地域情報)による時差を扱う
- ZonedDateTime
-
Year
- 2016
-
YearMonth
- 201604
-
MonthDay
- 0401
LocalDate
LocalDate date = LocalDate.now();
LocalDate.of(2016, 4, 1);
LocalDate.of(2016, Month.APRIL, 1);
Temporal / TemporalAccessor インタフェース
-
日時を表現する
- Local~
- Offset~
は、TemporalAccessor(読み取り)と Temporal(書き込み)インタフェースを実装している。
-
java.time.temporal.TemporalAccessor
-
java.time.temporal.Temporal
-
java.time パッケージで列挙型は2つのみ
- java.time.DayOfWeek.MONDAY
- java.time.Month.JANUARY
parse
LocalDate.parse("2016-04-01"); // 文字列を「解析」する
比較
LocalDate d1 = LocalDate.of(2016, Month.APRIL, 1);
LocalDate d2 = LocalDate.of(2017, Month.APRIL, 1);
// 自然によめば OK
System.out.print(d1.isBefore(d2)); // => true
System.out.print(d1.isAfter(d2)); // => false
System.out.print(d1.isEqual(d2)); // => false
from
- TemporalAccessor 型同士で変換する
- Date から DateTime の変換は不可(情報を持っていないため)
- 時差情報 / タイムゾーンつきからなしへの変換は可能
LocalDate d = LocalDate.of(2016, Month.APRIL, 1);
LocalDateTime dt = LocalDateTime.from(d); // DateTimeException
System.out.println(dt);
TemporalUnit インタフェース / ChronoUnit 列挙型
- java.time.temporal.TemporalUnit
- 日付や時間の単位を表すインタフェース
- java.ime.temporal.ChronoUnit 列挙型
- TemporalUnit を実装
- DAYS
- HOURS
- MINUTES
- MONTHS
- SECONDS
- WEEKS
- YEARS
- TemporalUnit を実装
LocalDate d = LocalDate.of(2016, Month.APRIL, 1); // 2016-04-01
System.out.println(d.plus(10, ChronoUnit.DAYS)); // => 2016-04-11
- TemporalUnit インタフェースは簡単な計算メソッドも定義されている
LocalDate start = LocalDate.of(2016, Month.APRIL, 1);
LocalDate end = LocalDate.of(2016, Month.APRIL, 10);
ChronoUnit.DAYS.addTo(start, 5); // 2016-04-06
ChronoUnit.DAYS.between(start, end); // 9 日
ZoneId
- タイムゾーンを表すクラス
System.out.println(ZoneId.systemDefault()); // => Asia/Tokyo (現在のタイムゾーン取得)
Duration / Period
- いずれも時間量を表す TemporalAmount インタフェースの実装
- java.time.Duration
- 時間ベースの間隔
- java.time.Period
- 日付ベースの間隔
- java.time.Duration
Period.distance(start, end); // 日付間隔
...
// Period
int getDays()
int getMonths()
int getYears()
// Duration
int getNano()
int getSeconds()
Formatter
java.time.format.DateTimeFormatter クラス
DateTimeFormatter formatter1 = DateTimeFormatter.BASIC_ISO_DATE;
// ロケール特有のフォーマッタを返すファクトリ・メソッド
DateTimeFormatter formatter2 = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM);
// 独自のカスタム・パターンを使用
DateTimeFormatter formatter3 = DateTimeFormatter.ofPattern("yyyy/MM/dd");
System.out.println(formatter1.format(LocalDateTime.now())); // => 20180728
System.out.println(formatter2.format(LocalDateTime.now())); // => Jul 28, 2018 1:23:03 PM
System.out.println(formatter3.format(LocalDateTime.now())); // => 2018/07/28
Instant クラス
java.time.Instant クラス
- 単一時点を表すクラス
- 1970-01-01T00:00:00Z からの経過秒数(エポック秒)
System.out.println(Instant.EPOCH); // => 1970-01-01T00:00:00Z タイムゾーンの情報が含まれている
System.out.println(Instant.now().toEpochMilli()); // 1532752301770
System.out.println(Instant.now().getEpochSecond()); // 1532752301
- 従来の API である java.util.Date クラスや java.util.Calendar クラスには toInstant メソッドが追加されており、新しい Date and Time API のオブジェクトとの相互変換に使用できる
Date date = new Date();
LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
LocalDateTime localDateTime = LocalDateTime.now();
Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
- ofEpochMilli/Second でエポック秒から Instant オブジェクトを取得できる
Instant instant1 = Instant.ofEpochMilli(1532752301770L);
Instant instant2 = Instant.ofEpochSecond(1532752301L);
Instant instant3 = Instant.ofEpochSecond(1532752301L, 770000000L); // ナノ秒で補正
System.out.println(instant1); // => 2018-07-28T04:31:41.770Z
System.out.println(instant2); // => 2018-07-28T04:31:41Z
System.out.println(instant3); // => 2018-07-28T04:31:41.770Z
- toInstant メソッド
Instant instant1 = ZonedDateTime.now().toInstant(); // OK
Instant instant2 = LocalDateTime.now().toInstant(); // コンパイルエラー(時差情報を持たないので ZoneOffset オブジェクトを渡す必要がある)
7. Java I/O
java.io.File クラス
- ファイルだけでなくディレクトリも扱える
- データの読み書きするための機能は提供していない
- シンボリックリンクなど UNIX 系のファイルシステム固有の機能は利用できない
- ファイルサイズ、最終更新日時のような基本的なファイル属性は取得できる
- ファイル所有者、セキュリティ属性などの詳細なファイル属性の取得・設定はできない
File file = new File("/tmp/sample.txt"); // 実際に存在するかは関係ない
file.exists(); // 存在するかどうか
- Java SE 7 以降を使用する場合には従来の File クラスの代わりに Path インタフェースを使用することが推奨されている
入出力ストリーム
-
入出力ストリーム = デバイスとデータの流れを抽象化したもの
- 入力デバイス --(入力ストリーム)--> Javaアプリケーション --(出力ストリーム)-->出力デバイス
-
入出力ストリームを扱うライブラリは java.io パッケージとして提供されている
- 4種類の基本抽象クラス
入力 | 出力 | |
---|---|---|
バイナリ | InputStream | OutputStream |
テキスト | Reader | Writer |
- それぞれの具象クラス
入力 | 出力 | |
---|---|---|
バイナリ | FileInputStream | FileOutputStream |
テキスト | FileReader | FileWriter |
// ファイルコピー
public static void main(String args[]) {
try(FileReader in = new FileReader("sample.txt");
FileWriter out = new FileWriter("copy.txt")) {
int ch;
while ((ch = in.read()) != -1) { // 先頭から1バイトずつ読み取る。終端の場合は -1
out.write((char)ch);
}
} catch (IOException e) {
e.printStackTrace();
}
}
BufferedReader/BufferedWriter クラス
- テキストを1行単位で読み書きするためのクラス
- Decorator パターンが使われている
- BufferedReader / Writer:Decorator
- FileReader / Writer:Decorated
public static void main(String args[]) {
try(BufferedReader in = new BufferedReader(new FileReader("sample.txt"));
BufferedWriter out = new BufferedWriter(new FileWriter("copy.txt"))) {
String line;
while ((line = in.readLine()) != null) {
out.write(line); // 改行文字は含まれない
out.newLine(); // 改行文字はプラットフォームによって異なるため newLine を使う
}
} catch (IOException e) {
e.printStackTrace();
}
}
InputStream クラスおよび Reader クラスの読み取り一を制御する機能
void mark(int readlimit) // 現在位置にマークを設定(引数はマーク後に読み取れる文字数の上限)
void reset() // 最後に設定されたマークに移動
void skip(long n) // n バイトのデータをスキップ
PrintStream / PrintWriter
- java.io.PrintStream / java.io.PrintWriter
- プリミティブ型をそのまま出力できる
- 両クラスにほとんど違いはない
- PrintWriter を用いれば良い
- FileWriter や BufferedWriter は String / int / char[] を出力する write メソッドしか持たない
try (PrintWriter writer = new PrintWriter("out.txt")) {
writer.println("Hello");
writer.println(0.5);
writer.print(true);
} catch (Exception e) {
e.printStackTrace();
}
標準入力
java.io.Console クラスを使用することで簡潔に
Console console = System.console();
String line = console.readLine();
パスワードを入力させるときに非表示にできる
Console console;
char[] passwd;
public char[] passwd1 = console.readPassword();
public char[] passwd2 = console.readPassword("[%s]", "Password:"); // プロンプトを設定
Serialize / Deserialize
- Serialize(直列化) / Deserialize(非直列化)
- オブジェクトをバイト列化 / バイト列をオブジェクト化
- オブジェクトをファイルに保存する必要がある場合
- ヒープ領域が不足して、アクティブでないオブジェクトをハードディスクに退避させたい場合
- オブジェクトをネットワーク越しに他の JVM に転送する必要がある場合
- java.io.Serializable インタフェースを実装する必要がある
- 実装を持たないマーカー・インタフェース
// Serialize
Hoge outObj = new Hoge("test");
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("sample.ser"))) {
out.writeObject(outObj);
} catch (IOException e) {
}
// Deserialize
Hoge inObj;
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("sample.ser"))) {
inObj = (Hoge)in.readObject();
System.out.println(inObj); // => Hoge(item=test)
} catch (IOException | ClassNotFoundException e) {
}
...
public class Hoge implements Serializable {
private static final long serialVersionUID = -6446448058187542427L;
private String item;
Hoge(String item) {
this.item = item;
}
@Override
public String toString() {
return "Hoge(item=" + item + ")";
}
}
- 以下に示すフィールド変数は直列化の対象から除外される
- static 変数
- transient 付きで宣言された変数
- これらは復元時には初期値(int なら 0)になる
Path インタフェース
java.nio.file.Path インタフェース
- Java SE 6 以前の java.io.File クラスの欠点を解消するために導入
- ファイルやディレクトリのパスを表すのは File クラスと同様
- プラットフォームのファイルシステム固有の仕組みを透過的に扱える
- 柔軟性や拡張性も向上
java.nio.file.FileSystem クラス
- プラットフォーム固有のファイルシステムを表す
FileSystem fileSystem = FileSystems.getDefault(); // ファクトリメソッド
- Path オブジェクトはファイルシステムに関連付けられる
- Paths クラスからも取得できる
FileSystems.getDefault().getPath("Path");
Paths.get("path");
- File オブジェクトから Path オブジェクトへの変換
File file = new File("sample.txt");
Path path = file.toPath();
Path path = Paths.get("c:¥¥x¥¥y¥¥z");
path.getRoot(); // C:¥
path.getName(0); // x(ルートは含まない)
path.getName(1); // y
path.getFileName() // z
path.getNameCount() // 3(ルートを含まないパス名の数)
path.subpath(1, 2) // y
// Linux / Unix なら
Path path = Paths.get("/x/y/z");
path.getRoot(); // /
path.getName(0); // x
Path p1 = Paths.get("C:¥¥abc");
Path p2 = Paths.get("D:¥¥xyz");
Path p3 = Paths.get("abc");
System.out.println(p1.resolve(p2)); // => D:¥¥xyz(絶対パスを渡されたら渡されたパスを返す)
System.out.println(p2.resolve(p3)); // => D:¥¥xyz¥abc(相対パスを渡されたら結合したパスを返す)
System.out.println(p2.resolveSibling(p3)); // => D:¥¥abc(相対パスを渡されたら親ディレクトリからのパスを返す)
Path p4 = Paths.get("C:¥¥abc¥¥def¥¥..¥¥xyz¥¥.¥¥tmp.txt");
System.out.println(p4.normalize()); // => C:¥¥abc¥¥xyz¥¥tmp.txt(冗長箇所をなくす)
Path p5 = Paths.get("C:¥¥abc¥¥xyz");
Path p6 = Paths.get("C:¥¥xyz¥¥abc");
System.out.println(p5.relativize(p6)); // => ..¥¥..¥¥xyz¥¥abc(相対パスを変えす)
Files クラス
-
ファイルやディレクトリの作成・削除・コピー・移動、ファイル属性の取得・設定など
-
copy メソッド
- デフォルトではコピー先にファイルが存在する場合は FileAlreadyExistsException
- 置換させるには StandardCopyOption.REPLACE_EXISTING オプションを付与
- デフォルトではファイルの属性はコピーされない
- コピーするには StandardCopyOption.COPY_ATTRIBUTES オプション付与
- ディレクトリ内にファイルが存在する場合、ファイルはコピーされない
- シンボリック・リンクをコピーした場合、リンク自体はコピーされない
- LinkOption.NOFOLLOW_LINKS オプションか StandardCopyOption.REPLACE_EXISTING オプションが必要
- デフォルトではコピー先にファイルが存在する場合は FileAlreadyExistsException
Files.copy(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING, ...);
attribute パッケージ
- ファイル属性を扱うためのインタフェースやクラスが含まれている
- BasicFileAttributes インタフェース
- 基本的なファイル属性を扱うためのインタフェース
- 2つのサブインタフェース
- DosFileAttributes インタフェース
- DOS ファイルシステム(Windows 全般)
- PosixFileAttributes インタフェース
- POSIX 互換 OS(UNIX や Linux)
- DosFileAttributes インタフェース
- AttributeView インタフェース
- ファイル属性のセット(属性ビュー)を表すインタフェース)
- BasicFileAttributeView インタフェース
- readAttributes() メソッドで BasicFileAttributes を取得
- 2つのサブインタフェース
- DosFileAttributeView インタフェース
- PosixFileAttributeView インタフェース
ファイル属性の取得
Path file = Paths.get("sample.txt");
// 属性ビュー名(basic / dos / posix)は省略すると basic となる
long fileSize = (long)Files.getAttribute(file, "posix:size");
ディレクトリの再帰処理(トラバース)
- walkFileTree メソッドを使用
- FileVisitor インタフェースを実装したオブジェクトが実際の再帰処理を行う
- 詳細なコードは一旦略
ストリーム API 関連
- Files.lines メソッド
- テキストファイルのデータを扱う Stream オブジェクトの取得
try (Stream<String> stream = Files.lines(Paths.get("sample.txt"))) {
stream.forEach(System.out::println);
}
- Files.list メソッド
- ディレクトリ内のエントリを扱う Stream オブジェクトの取得
- ディレクトリを再帰的には辿らない
try (Stream<Path> stream = Files.list(Paths.get("rootDir"))) {
// rootDir 直下の通常ファイルを書き出し
stream.filter(Files::isRegularFile).forEach(System.out::println);
}
- Files.walk メソッド
- ファイルツリーをトラバースし、結果を Stream オブジェクトで返す
try (Stream<Path> stream = Files.walk(Paths.get("rootDir"))) {
// rootDir 配下の全通常ファイルを書き出し
stream.filter(Files::isRegularFile).forEach(System.out::println);
}
8. 並行性
並行処理ユーティリティ(Concurrent Utilities)
- java.util.concurrent パッケージ
- java.util.concurrent.atomic パッケージ
- java.util.concurrent.locks パッケージ
並行処理ユーティリティが提供する仕組みや機能
-
スレッド・プール
- スレッドをあらかじめ生成し、プールしておく
-
並行コレクション
- 従来の同期化コレクションは、状態へのアクセスを直列化することでスレッド・セーフを実現
- パフォーマンスの劣化を引き起こす
- 複数スレッドからの並行アクセスを前提にパフォーマンスを最適化し、高いスループットを実現
- 従来の同期化コレクションは、状態へのアクセスを直列化することでスレッド・セーフを実現
-
アトミック変数
- ステートの読み込み → 値の変更 → ステートの書き戻し をアトミックに扱う
-
カウンティング・セマフォ
- 有限リソースに対して並行アクセス可能なスレッド数を自由に設定できる
アトミック変数
- java.util.concurrent.atomic パッケージで宣言
- AtomicBoolean
- AtomicInteger
- AtomicLong
- AtomicReference
public class Counter {
private AtomicInteger count = new AtomicInteger();
public int getCount() {
return count.incrementAndGet();
}
}
並行コレクション
- java.util パッケージで提供されるコレクション・クラスの多くはシングルスレッド・アプリケーションで使用することを前提に設計
- 非スレッドセーフ
- ArrayList
- HashMap
- スレッドセーフ
- Vector
- Hashtable
- 利用状況によってはパフォーマンス劣化
- 非スレッドセーフ
java.util.concurrent パッケージが提供する並行コレクション
- 同時並行正とパフォーマンスを最適化
- BlockingQueue
- ConcurrentHashMap
- ConcurrentLinkedDeque
- CopyOnWriteArrayList
など
ConcurrentHashMap
- java.util.concurrent.Concurrentmap インタフェース
- 従来の Map 実装クラスが提供する機能に加え、様々なアトミック操作をサポート
- 2つの実装クラス
- ConcurrentHashMap クラス
- ConcurrentSkipListMap クラス
ConcurrentHashMap
- HashTable クラスや Collections.synchronizedMap メソッドのように、マップ全体をロックして排他的アクセスを実現する方式は採用していない
- ロック・ストライピング(ロックの細分化)を用いて実行性能の最適化
- size メソッドや isEmpty メソッドの厳密性要件が弱められており、近似値となる可能性
- ConcurrentHashmap の Iterator の設計方針は「Weakly Consistent(弱い整合性)」
- 並行アクセスにおける要素の変更を許容
- java.util.ConcurrentModificationException がスローされることはない
CopyOnWriteArrayList
- add、set、remove などが呼び出されると内部で要素を保持している配列のコピーを作成
- 変更処理中に他のスレッドが読み取りを行う場合、コピー前のリストが返却される
- java.util.ConcurrentModificationException がスローされることはない
- サイズが大きいと性能劣化
- 変更処理中に他のスレッドが読み取りを行う場合、コピー前のリストが返却される
CyclicBarrier
java.util.concurrent.CyclicBarrier クラス
- 協調して動作するスレッドの集合(スレッド・パーティ)内の各スレッドの足並みを合わせるための機能を提供
- 各スレッドは、設定されたバリアに到達すると、他のスレッドがバリアに到達するまで待機
- 他スレッドがバリアに到達するとバリアは解除され、実行を再開(トリップ)
// バリア作成
// CyclicBarrier barrier = new CyclicBarrier(3);
Runnable r = () -> {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + ": start");
try {
Thread.sleep((int) (Math.random() * 5000)); // ランダム時間待機
System.out.println(threadName + ": execute");
// バリア設定
// barrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} /* catch (BrokenBarrierException e) {
e.printStackTrace();
} */
System.out.println(threadName + ": end");
};
new Thread(r).start();
new Thread(r).start();
new Thread(r).start();
# バリアを設定しなかった場合
Thread-0: start
Thread-2: start
Thread-1: start
Thread-2: execute
Thread-2: end
Thread-1: execute
Thread-1: end
Thread-0: execute
Thread-0: end
# した場合
Thread-0: start
Thread-2: start
Thread-1: start
Thread-0: execute
Thread-1: execute
Thread-2: execute
Thread-2: end
Thread-0: end
Thread-1: end
Excecutor フレームワーク
- Thread クラスを使用してタスクごとにスレッドを生成し、複数のタスクを非同期に実行できる
- Thread クラスを使用したタスクの実行における問題点
- スレッドの生成・破棄におけるリソース消費・パフォーマンス劣化
- 従来の Thread クラスによるタスクの実行では、スレッド生成と管理における詳細をプログラムで制御することは困難
- 生成可能なスレッド数はプラットフォームの環境や物理的なメモリリソースの量によって上限がある
- 上限を超えた場合 java.lang.OutOfMemoryError
- スレッドの生成・破棄におけるリソース消費・パフォーマンス劣化
Executor フレームワーク
- スレッド・プールの実装を提供
- タスクの実行におけるライフサイクル管理や監視、統計収集などの機能も提供
- 従来の Thread クラスによるタスクの実行における問題点を解消
- タスクのスケジューリング(遅延開始と周期的実行)を実現
- ScheduledThreadPoolExecutor クラスを使用
Executor インタフェース
- タスクの実行者とタスクの実行方法を分離
- 従来の Thread クラスを使用したタスクの実行では、両者とも Thread クラスの責務
- Executor インタフェース:タスクの実行者
- 2つのサブインタフェース:タスクの実行方法
- ExecutorService
- タスクの終了管理
- タスクの進行状況を管理
- ScheduledExecutorService
- ExecutorService を継承
- 上記に加え、タスクのスケジュール管理
- ExecutorService
- 2つのサブインタフェース:タスクの実行方法
public class MyTask implements Runnable {
public void run() {
System.out.println("execute");
}
}
...
/* 従来の Thread クラスを使用したタスク実行 */
Runnable task = new MyTask();
Thread thread = new Thread(task);
thread.start();
/* Executor を使用したタスク実行 */
Runnable task = new MyTask();
Executor executor = new Executor() {
// スレッドの生成と実行を Executor が隠蔽
// スレッド・プールやスケジューリングなどの処理を Executor で実装できる
@Override
public void execute(Runnable command) {
new Thread(command).start();
}
};
executor.execute(task);
/* Executors ファクトリ・メソッドを使用 */
Runnable task = new MyTask();
Executor executor = Executors.newScheduledThreadPool(3); // 3つのスレッド・プールを利用
executor.execute(task);
java.util.concurrent.Callable<V> インタフェース
- Runnable インタフェースと違って、
- 戻り値を返すことができる
- チェック例外をスローできる
- ExecutorService の submit メソッドを使用
- 戻り値に Future<T> オブジェクト
public class MyTask implements Callable<String> {
@Override
public String call() throws Exception {
return "hello";
}
}
...
ExecutorService service = Executors.newSingleThreadExecutor();
Future<String> future = service.submit(new MyTask());
try {
// get メソッドで結果を取得
System.out.println(future.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
Future<T> インタフェース
- get メソッドによるタスクの実行結果の取得
- タスクの完了の判定
- タスクのキャンセル試行
Executor の終了
- task が終了しても、Executor を実行しているスレッドは自動的には終了しない
- Ctrl + C などを入力する必要がある
- ExecutorService インタフェースでは、Executor の終了に関する機能を提供
-
shutdown()
- 残ったタスクを実行し終えてから終了
-
shutdownNow() メソッド
- 即時終了
- Executor 終了後に新たなタスク実行を依頼すると java.util.concurrent.RefectedExecutionExeption
-
shutdown()
- Executor が終了しているかどうか
-
isShutdown()
- まだタスクを実行していても true
-
isTerminated()
- タスクがすべて実行されてから true
-
isShutdown()
Fork / Join フレームワーク
- 大きなタスクを小さなタスクに分割し、複数スレッドで同時並行的に実行することで処理パフォーマンスを向上させる仕組み
- マルチコア・アーキテクチャを効率的に使用することが最大の目的
-
Work-stealing アルゴリズムを採用
- ワーカースレッドは自身のワークキュー(両端キュー)を持ち、分割(fork)したタスクをワークキューに挿入- 自身のワークキューをすべて実行し終えたワーカースレッドは、他のスレッドのワークキューの末尾から未処理のタスクを横取り(stealing)して実行
- ワーカスレッド間のロードバランシング(タスクの割り振り)が自然と最適化される仕組み
java.util.concurrent.ForkJoinPool クラス
- Fork / Join フレームワークを利用したタスクを実行するための ExecutorService インタフェースの実装クラス
java.util.concurrent.ForkJoinTask<V>
- ForkJoinPool クラスによって実行されるタスクを表す抽象クラス
ForkJoinPool クラスのタスク実行用メソッド
void execute(ForkJoinTask<?> task) // 非同期で実行、処理結果は受け取らない
<T> T invoke(ForkJoinTask<T> task) // タスクの実行が終了するまで待機、処理結果を受け取る
<T> ForkJoinTask<T> submit(ForkJoinTask<T> task) // 非同期でタスクを実行、処理結果をタスクから受け取る
ForkJoinTask<V> のサブクラス(抽象クラス)
- RecursiveAction クラス
- 処理結果としての戻り値が必要ない場合
- RecursiveTask<V> クラス
- 必要な場合
戻り値なしの場合
public class MyAction extends RecursiveAction {
@Override
protected void compute() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("MyAction executed.");
}
}
...
ForkJoinPool executor = new ForkJoinPool();
/* execute による非同期実行 / invoke による同期実行 */
executor.execute(new MyAction());
// or
// executor.invoke(new MyAction());
System.out.println("Main executed.");
Thread.sleep(3000);
System.out.println("Done.");
# 非同期(execute)の場合
Main executed.
MyAction executed.
Done.
# 同期(invoke)の場合
MyAction executed.
Main executed.
Done.
戻り値ありの場合
public class MyTask extends RecursiveTask<Integer> {
@Override
protected Integer compute() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("MyTask executed.");
return 1;
}
}
...
ForkJoinPool executor = new ForkJoinPool();
/* invoke による同期実行 / submit による非同期実行 */
Integer result = executor.invoke(new MyTask());
/* or */
// ForkJoinTask<Integer> result = executor.submit(new MyTask());
System.out.println("Main executed.");
Thread.sleep(3000);
/* 結果の取り出し */
System.out.println("Result: " + result);
/* or */
// System.out.println("Result: " + result.get()); // get メソッドを利用して取り出し
# 同期(invoke)の場合
MyTask executed.
Main executed.
Result: 1
# 非同期(submit)の場合
Main executed.
MyTask executed.
Result: 1
ストリーム並列処理
- ストリーム API での処理は
- 順次処理(Sequential)
- 並列処理(Parallel)
のいずれかで実行できる
- 並列処理が必ずしもパフォーマンスに優れるわけではない
- java.util.Collection インタフェースの stream / parallelStream メソッドを使用したのかで決定
- ストリーム処理の途中で実行処理を変更することも可能
- parallel()
- 並列処理モードに変更
- sequential()
- 順次実行モードに変更
- parallel()
- isParallel() でどちらか判別可能
/* 並列ストリームによる処理 */
Arrays.asList(1, 2, 3).parallelStream()
.map(n -> n * 2)
.forEach(System.out::print); // 462(表示順序は不定)
/* 順次ストリームへの変換 */
Arrays.asList(1, 2, 3).parallelStream()
.map(n -> n * 2)
.sequential() // 順次ストリームに変換
.forEach(System.out::print); // => 246
OptionalInt opt = IntStream.of(1, 2, 3)
.parallel()
.findAny();
System.out.println(opt.getAsInt()); // 1, 2, 3 のいずれかが表示される
9. JDBC によるデータベース・アプリケーション
JDBC
- RDBMS を利用するためのテクノロジー
- JDBC API
- JDBC ドライバ
からなる
- java.sql パッケージおよび javax.sql パッケージ
- JDBC ドライバはデータベースの開発元によって提供
- DB 製品に応じて個別に入手する
接続 URL
jdbc:xxx://host[:port]/dbname[?option]
jdbc:プロトコル(固定)
xxx:データベース製品名
以下:接続詳細
/* データベースとの接続 */
Connection conn = DriverManager.getConnection("接続 URL")
/* SQL 文の発行 */
Statement stmt = conn.createStatement();
...
try(Connection conn = DriverManager.getConnection(URL, USER, PASS)) {
Statement stmt = conn.createStatement();
// some process
} catch (SQLException e) {
e.printStackTrace();
}
SQL 実行用メソッド
/* クエリ(select)の場合 */
ResultSet executeQuery(String sql)
/* 更新 SQL や DDL(create table など)の場合 */
int executeUpdate(String sql) // 更新された行数
/* 両方実行可能 */
boolean execute(String sql)
ResultSet インタフェース
- クエリ結果であるテーブルをそのままのイメージで扱う
- 処理対象業を指定するポインタをカーソルという
String 型データをとりだすメソッド
String getString(int columnIndex) // 列インデックス(ただし「1」始まり)
String getString(String columnLabel) // カラム名
- カーソルの初期位置は先頭レコードの1つ上
- カーソルを動かさずにレコード操作メソッドを呼び出すと SQLException
カーソルの移動
while(rs.next()) {
System.out.println(rs.getString("name"));
}
日付/時刻の取得
- Date、Time、Timestamp のいずれも java.sql パッケージのクラス
- java.util.Date クラスのサブクラス
Date getDate(int columnIndex)
Date getDate(String columnLabel)
Time getTime(int columnIndex)
Time getTime(String columnLabel)
Timestamp getTimestamp(int columnIndex)
Timestamp getTimestamp(String columnLabel)
...
Date sqlDate = rs.getDate("列名");
LocalDate localDate = sqlDate.toLocalDate();
Time sqlTime = rs.getTime("列名");
LocalTime localTime = sqlTime.toLocalTime();
Timestamp sqlTimestamp = rs.getTimestamp("列名");
LocalDateTime localDateTime = sqlTimestamp.toLocalDateTime();
- ResultSet オブジェクトは取得元の Statement オブジェクトや Connection オブジェクトが close されると利用できない
- もととなる Statement オブジェクトから別の ResultSet オブジェクトを取り出した場合、最初に取得した ResultSet オブジェクトは close される
execute メソッド
- 実行した SQL が SELECT 文かつ ResultSet が存在すれば true
- 実行した SQL が SELECT 以外または ResultSet が存在しない場合は false
boolean result = stmt.execute("SELECT * FROM employee"); // true
ResultSet rs = stmt.getResultSet(); // getResultSet() で取り出し
...
boolean result = stmt.execute("DELETE FROM employee WHERE id = 1"); // false
int count = stmt.getUpdateCount(); // getUpdateCount() で更新行数取得
スクロール
- ResultSet のデフォルトの挙動では下方向(順方向)にしかカーソルは移動できない
- Scrollable ResultSet では任意の位置に移動可能
- createStatement で resultSetType を指定
-
ResultSet.TYPE_FORWARD_ONLY
- デフォルト
-
ResultSet.TYPE_SCROLL_INSENSITIVE
- スクロール可能
- (通常は)もとのデータに対する変更を反映しない
-
ResultSet.TYPE_SCROLL_SENSITIVE
- スクロール可能
- (通常は)もとのデータに対する変更を反映する
-
- 第二引数(resultSetConcurrency)が必要
-
ResultSet.CONCUR_READ_ONLY
- 更新できない
-
ResultSet.UPDATABLE
- できる
-
Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException
スクロール可能な ResultSet のメソッド
/* 相対位置 */
boolean next() // 順方向に 1
boolean previous() // 逆方向に 1
boolean relative(int rows) // rows だけ移動
/* 絶対位置 */
boolean absolute(int row) // row へ移動(先頭行が1)
// 負数を指定した場合、最終行を -1 として遡る
ResultSet によるデータ更新
// rs は更新可能(UPDATABLE)とする
ResultSet rs = stmt.executeQuery("SELECT * FROM employee");
rs.absolute(2);
rs.updateString("name", "Lee"); // これだけでは反映されない
rs.updateRow(); // もとになるデータベースを更新する
- 更新可能な ResultSet には新規レコードを挿入するための「挿入専用行」が用意されている
rs.moveToInsertRow(); // 挿入専用行に移動
rs.updateInt(1, 4); // 1列目に「4」をセット
rs.updateString(2, "Freddy"); // 2列目に「Freddy」をセット
rs.insertRow(); // 実際のテーブルに挿入
SQLException
int getErrorCode() // ベンダー固有の例外コードを取得
Stirng getSQLState() // SQL ステートを取得
10. ローカライズ
ローカライズ
- 特定の地域にアプリケーションを対応させること
- ロケールは、地理的、政治的もしくは文化的に特定の領域を表す
- Local クラスを用いることで、数値や日付、通過などのフォーマットにおけるローカライズを容易に実現する
Local クラスのコンストラクタ
- language:言語コード
- ja, en, fr
- country:国コード
- JP, US, CA
- variant:任意値
- 妥当性チェックは入らない
Local(String language)
Local(String language, String country)
Local(String language, String country, String variant)
Local オブジェクトの取得
- getDefault メソッドでシステムのデフォルトの Locale オブジェクトを取得
- JVM が起動時にホスト環境に応じた環境変数
- user.country
- user.language
- user.variant
を設定する
Locale locale = Locale.getDefault();
System.out.println(locale.getLanguage()); // => ja
System.out.println(locale.getCountry()); // => JP
System.out.println(locale.getVariant()); // なし
- Locale.Builder クラスを利用することも可能
- 妥当性チェックができる
Locale.Builder builder = new Locale.Builder();
builder.setLanguage("cat");
builder.setRegion("ES");
Locale locale = builder.build();
// ビルダーパターンで設計されているので簡潔に書ける
Locale locale = new Locale.Builder()
.setLanguage("cat")
.setRegion("ES")
.build();
...
Locale.Builder builder = new Locale.Builder();
builder.setLanguage("123"); // java.util.IllformedLocaleException
- 一般的な Locale オブジェクトは定数として提供されている
Locale locale = Locale.JAPAN;
プロパティ
java.util.Properties クラス
- テキストファイル
- XML ファイル
に対応
テキスト形式
- 「.properties」を使用することが多いが、任意の名前で良い
# properties for the database
host=192.168.0.3
name=testdb
user=root
password=admin
XML 形式
- データ量が大きくなりがちで近年では使われなくなってきている
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>Properties for the database</comment>
<entry key="host">192.168.0.3</entry>
<entry key="name">testdb</entry>
<entry key="user">root</entry>
<entry key="password">admin</entry>
</properties>
プロパティの読み込み
- FileInputStream もしくは FileReader クラスを使用
try (InputStream in = new FileInputStream("example.properties")) {
Properties props = new Properties();
props.load(in);
System.out.println(props.getProperty("host")); // => "192.168.0.3"
} catch (IOException e) {
e.printStackTrace();
}
- Properties クラスは Map インタフェースの実装である java.util.Hashtable クラスのサブクラス
- Map インタフェースの default メソッドである forEach メソッドを使用できる
props.forEach((k, v) -> System.out.println(k + ":" + v));
- list メソッドに出力ストリームを渡すことで、すべてのプロパティの内容を表示できる
props.list(System.out);
リソース・バンドル(Resource Bundle)
- 言語や表示形式(リソース)をロケールごとに1つにまとめたもの
- 実体は以下の2つのいずれか
- クラスファイル
- プロパティファイル
- 拡張子を .properties にする必要がある
- クラスパスが設定されているディレクトリ上に置く
リソース・バンドル の名前
基底名_言語コード_国コード
MyResources_ja_JP
java.util.ResourceBundle
- リソース・バンドル を扱うための抽象クラス
ResourceBundle rb = ResourceBundle.getBundle("MyResources"); // 拡張子は不要
ResourceBundle rb = ResourceBundle.getBundle("MyResources", Locale.US); // ロケールを指定することも可能
-
指定したリソース・バンドルが見つからなかった場合、MissingResourceException
-
常に「クラスファイル」→「プロパティファイル」の順で検索される
- 検索順は、基底名が「MyResources」、ロケールが「ja_JP」であれば
MyResources_ja_JP.class
MyResources_ja_JP.properties
MyResources_ja.class
MyResources_ja.properties
MyResources.class
-
MyResources.properties
となる
- 検索順は、基底名が「MyResources」、ロケールが「ja_JP」であれば
-
クラスを使用してリソース・バンドルを作成する場合
- ResourceBundle クラスのサブクラスである以下の2つのいずれかを継承したクラスを作成する
-
java.util.ListResourceBundle
クラス- ソースコード内に記述したリソースからリソース・バンドルを作成
-
java.util.PropertyResourceBundle
クラス
- プロパティファイルからリソース・バンドルを作成
-
- ResourceBundle クラスのサブクラスである以下の2つのいずれかを継承したクラスを作成する
public class JapaneseResources extends ListResourceBundle {
@Override
protected Object[][] getContents() {
return new Object[][] {
{ "Thank you.", "ありがとうございます。" },
{ "Hello.", "こんにちは。" }
};
}
}
...
ResourceBundle rb = ResourceBundle.getBundle("resourcebundle.JapaneseResources");
System.out.println(rb.getString("Thank you.")); // => ありがとうございます。
System.out.println(rb.getString("Hello.")); // => こんにちは。