コンテンツにスキップ

Java

値オブジェクトとコレクションオブジェクト

某先輩の書評を読んで気になったので 『現場で役立つシステム設計の原則』 という本を読んだ。

とあるプロジェクトでその先輩に自分の書くコードについて色々ご指摘をいただいていたタイミングであり、自分としてはかなり参考になった点があるので備忘も兼ねて書いてみる。

今回は特に「値オブジェクト」と「コレクションオブジェクト(ファーストクラスコレクション)」について。

値オブジェクト

値オブジェクトはアプリケーションに登場する様々な値に対してその値を扱うための専用クラスを作るという考え方。

値ごとにクラスを定義する

値オブジェクトの考え方をルール化すると、「プリミティブ型や String 型は使わない」という方針となる。

例えば、先述のプロジェクトのコードでは登場する様々な値を String 型で表現していた。

  • userId
  • nickname
  • address
  • etc...

これらはサーバーリクエストの口でバリデーションを通るものの、その後の値がアプリケーションコード内で保証されないため非常に不安定だった。

また、例えば nicknameaddress を引数として受け付けるメソッドがあったとすると

public User hoge(String nickname, String address) {
    ...
}

となるわけだが、この時このメソッドを使う側で間違えて address, nickname の順で引数を指定したとしてもこのメソッドはその引数を受け付けてしまう。

そこで Nickname クラスと Address クラスを定義するというのが値オブジェクトの考え方だ。

class Nickname {
    static final int MIN_LENGTH = 1;
    static final int MAX_LENGTH = 20;

    String nickname;

    Nickname(String nickname) {
        if (nickname.length() < MIN_LENGTH) {
            throw new IllegalArgumentException("nickname length must be at least" + MIN_LENGTH);
        }
        if (nickname.length() > MAX_LENGTH) {
          throw new IllegalArgumentException("nickname length must be under" + MAX_LENGTH);
        }
        this.nickname = nickname;
    }
}

address に対しても同様にクラスを定義して先ほどのメソッドを

public User hoge(Nickname nickname, Address address) {
    ...
}

とすればより堅い実装となる。

値オブジェクトを不変とする

値オブジェクトの値は上書きしないというのも値オブジェクトを扱う上で重要な考え方で、これをルール化すると、「値を変更する度にオブジェクトそのものを新しく(別のオブジェクトに)する」ということになる。

Nickname nickname = new Nickname("sojiro");
nickname.setNickname("shin-sojiro");

ではなく

Nickname nickname = new Nickname("sojiro");
Nickname updatedNickname = nickname.update("shin-sojiro");

とすることで、各オブジェクトがどの値をもっているのかが明確となり、知らないうちに値が変わっている、ということを防ぐことができる。

コレクションオブジェクト(ファーストクラスコレクション)

値オブジェクトの考え方を発展させ、コレクション型のデータとロジックを独自のクラスに切り出す考え方をコレクションオブジェクト、もしくはファーストクラスコレクションという。

List<User> users = new ArrayList<>();
users.add(user);

のように扱っていた User のコレクションを、 Users というクラスを定義し、そのクラスで操作が完結するようにする。

class Users {
    List<User> users;

    Users(List<User> users) {
        this.users = users;
    }

    Users add(User user) {
        List<User> userList = new ArrayList<>(users);
        return new Users(userList.add(user));
    }

    List<User> asList() {
        return Collections.unmodifiableList(users);
    }
}

ここでのポイントは、

  • 値オブジェクトで値に変更がある場合は常に新しいオブジェクトを返したように、コレクションオブジェクトでもコレクションに変更がある場合は新たなオブジェクトを返すようにすること
  • コレクションへの参照を返す場合はなるべく変更できない状態にして返すこと

コレクションへの操作を独自クラスに閉じ込め、同一オブジェクトが持つコレクションの内容が変化することのないようにすることでより堅い実装とすることができる。

感想

Perl からプログラミングを始めたこともあってか(言い訳)、オブジェクト指向と言っても型への意識は低く、如何にコンパイル時点でバグを見つけられる堅い実装とするか、その変数に今どのような値が入っているかをコードの書き方で明確にするか、というような意識も低かった。

現場で役立つシステム設計の原則 からは他にも学んだ点が多いので追って記事にできたらと思う。

参照

現場で役立つシステム設計の原則

Could not open the requested socket エラーが出たら

Android Studio で GAE に乗せるアプリを開発しているとき、ローカルで立ち上げようとすると以下のようなエラーが出た

Could not open the requested socket: Address already in use
Try overriding --address and/or --port.

どうやら以前立ち上げた際のプロセスが生き残っていて邪魔しているらしい

以下のように対応した

Android Studio のメニューから RunEdit Configurations... とたどり、対象 module が使っている port を確認

ポートを指定してプロセスを確認、そして kill

$ lsof -i:8080
COMMAND   PID    USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
java    74792  sojiro   48u  IPv6 0xXXXXXXXXXXXXXXXX      0t0  TCP *:http-alt (LISTEN)
$ kill -9 74792

パーフェクトJava読書メモ chapter 10 Javaプログラムの実行と制御構文

Javaを使うために改訂2版 パーフェクトJavaを読んだメモ

条件分岐

if-else文

インデントの取り方に関係なく else は最も近い(直近の) if にかかる

switch-case文

switch(式) {
case 定数1:
    0個以上の文
case 定数N:
    0個以上の文
default:
    0個以上の文
}

式の評価値は以下

  • int 型
  • int 型に暗黙に型変換される型
    • char
    • byte
    • short
  • 数値ラッパークラス
    • Integer
    • Character
    • Byte
    • Short
  • enum 型
  • String 型

式が null になると NullPointerException が発生する

break; 文がない限り処理を続ける

int i = 0;
switch (i) {
    case 0:
        System.out.println(0);
    case 1:
        System.out.println(1);
    default:
        System.out.println("default");
}
0
1
default

繰り返し

for文

for (初期化式; 条件式; 更新式) {
    文
}

条件式は評価値が boolean もしくは Boolean

初期化式と更新式は複数の式を , で区切って書ける

for (int i = 0, j = 0; (i < 10 && j < 10); i++, j++) {
}

異なる型の宣言と初期化を並べるとエラー

for (int i = 0, byte j = 0; (i < 10 && j < 10); i++, j++) {
}

for文の外に出して回避

int i;
byte j;
for (i = 0, j = 0; (i < 10 && j < 10); i++, j++) {
}

ジャンプ

break文

  • ループを抜ける
  • ループがネストしている場合、抜けるのは内側のループのみ

continue文

  • ループ内の文をスキップしてループの条件式の評価に戻る

ラベル

  • 繰り返しにラベルをつける
  • break文、continue文にラベルを渡すことでどの繰り返しを対象にするか定める
target_loop:
while (true) {
    while (true) {
        break target_loop;
    }
}
  • 外側の while ループに target_loop というラベルがつく
  • break target_loop で外側の while ループを抜ける
  • continue の場合は対象のループ内の文をスキップして条件式の評価に戻る)

参照

改訂2版 パーフェクトJava

パーフェクトJava読書メモ chapter 9 文、式、演算子

Javaを使うために改訂2版 パーフェクトJavaを読んだメモ

Java の文法と文

Java で扱う文は以下の4種類

  • 制御文
  • ブロック文
  • 宣言文
  • 式文

宣言文と式文は終端にセミコロン必要

Java の演算子と式

  • &&||? : (三項演算子)を除くすべての演算子は演算前にすべてのオペランドを評価する
  • オペランドは左のものから評価する
  • メソッド及びコンストラクタ呼び出し式では引数を左から評価する

数値の演算

インクリメント/デクリメント演算の前置後置

前置演算は評価値が演算後の値、後置演算は評価値が演算前の値

int n = 10;
int m = ++n;
// m: 11, n: 11
int n = 10;
int m = n++;
// m: 10, n: 11
int n = 0;
while (++n < 10) {
    // ループが回る回数は 9 回
}

論理演算

遅延評価

&&|| は遅延評価を行う

  • && は左辺の評価値が偽であれば右辺の評価値に関係なく結果が偽になるため右辺を評価しない
  • || は左辺の評価値が真であれば右辺の評価値に関係なく結果が真になるため右辺の評価をしない

これらの演算子の右辺に副作用のある式を書くと、左辺の評価値により実行さるか否かが変わってくるので注意

その他の演算

instanceof 演算子

ダウンキャストを安全に行えるかを事前にチェックする

参照

改訂2版 パーフェクトJava

パーフェクトJava読書メモ chapter 7 インターフェース

Javaを使うために改訂2版 パーフェクトJavaを読んだメモ

クラスとインターフェースの違い

  • インターフェースは雛形としての役割を持たない
  • インターフェースは実体化できず、型定義に特化している
  • インターフェースの継承の目的は多様性のみ(クラスの拡張継承には実装コードの共有という側面もある)

インターフェース宣言

[修飾子] interface インターフェース名 {
    メンバ宣言
}

インターフェースの修飾子

modifier description
public グローバルにアクセス可(書かないとパッケージ内に限定)
abstract インターフェースは暗黙的に abstract なので書かなくても同じ
strictfp インターフェース内に記述した浮動小数点演算を厳密に評価
アノテーション 省略

インターフェースのメンバ

  • 抽象メソッド(実装なし)
  • default メソッド
  • static メソッド
  • 定数フィールド
  • static なネストしたクラス
  • static なネストしたインターフェース

メソッド宣言

modifier description
public 暗黙的に public なので書かなくても同じ
default デフォルトメソッド
static static メソッド
abstract 暗黙的に abstract なので書かなくても同じ
  • default メソッドはインスタンスメソッド
  • インターフェースを継承した具象クラスのインスタンスメソッドになる
  • フィールド変数は暗黙的に public static final

インターフェースと実装クラス

インターフェース継承

[修飾子] class クラス名 implements インターフェース名 {
    クラス本体
}

複数のインターフェースを同時に継承することができる

[修飾子] class クラス名 implements インターフェース名, インターフェース名 {
    クラス本体
}
  • クラスの拡張継承とインターフェースの継承を同時にできる
  • このとき implementsextends より後に書く
[修飾子] class クラス名 extends 親クラス名 implements インターフェース名 {
    クラス本体
}
  • インターフェースを継承したクラスはインターフェースの抽象メソッドをすべてオーバーライドする必要がある
  • インターフェースから継承したメソッドのアクセス制御は public 修飾子がないとコンパイルエラー
  • メソッドのオーバーライドはクラスの拡張継承と同様に行う

ネストしたインターフェース

  • クラス内のネストしたインターフェース
    • public protected 無指定 private のいずれかを指定する
  • インターフェース内のネストしたインターフェース
    • 常に public
  • インターフェース内のネストしたクラス
    • 常に public かつ static
  • ネストして宣言されたインターフェースは常に static

インターフェース自体の拡張継承

  • インターフェースも拡張継承できる
  • インターフェースは複数の親インターフェースを指定可能
interface Parent {
    void print();
}

interface Child extends Parent {
    // void print() を継承
}

interface Father {
    void print();
}

interface Mother {
    void exec();
}

interface Child2 extends Father, Mother {
    // void print(), void exec() を継承
}

参照

改訂2版 パーフェクトJava

パーフェクトJava読書メモ chapter 6 コレクションと配列

Javaを使うために改訂2版 パーフェクトJavaを読んだメモ

コレクションフレームワーク

----- Hash Table Array Tree Linked List Hash Table + Linked List
set HashSet --- TreeSet --- LinkedHashSet
list --- ArrayList --- LinkedList ---
deque --- ArrayDeque --- LinkedList ---
map HashMap --- TreeMap --- LinkedHashMap

コレクション型オブジェクトの生成

コレクションのインターフェース型<要素の型> 変数 = new コレクションのインターフェースを実装した具象クラス<>([コンストラクタの引数]);
List<String> list = new ArrayList<>();
  • 上記例で ArrayList<>ArrayList<String> の略
List<int> list = new ArrayList<>();
  • 要素の型に基本型は指定できない

リスト

リストの具象クラス

  • ArrayList
  • LinkedList

ArrayList

  • 良い点
    • インデックスを指定して要素を読み出す速度が速い[get]
    • インデックスを指定して要素を書き換える速度が速い[set]
    • 先頭から順にすべての要素をなめる処理が速い
  • 悪い点
    • 要素の挿入が遅いことがある[add]
      • 先頭に近い位置への挿入は遅い
      • 末尾に近い位置への挿入は速いときもあるが遅いときもある
    • 要素の削除が遅いことがある[remove]
      • 先頭に近い位置の削除ほど遅い
      • 末尾に近い位置の削除ほど速い
      • 最末尾の削除は高速
    • 条件に合致した要素の検索があまり速くない[contains, indexOf, lastIndexOf]
  • 要素の順序をもつので、順序が入れ替わる要素が多くなる処理は遅い
  • 連続したメモリの確保ができない場合、末尾への挿入は遅くなる

LinkedList

  • 良い点
    • 要素の挿入が速い[add]
    • 要素の削除が速い[remove]
  • 悪い点
    • インデックスを指定して要素を読み出す速度はあまり速くない[get]
    • インデックスを指定して要素を書き換える速度はあまり速くない[set]
    • 条件に合致した要素を検索する処理の速度はあまり速くない[contains, indexOf, lastIndexOf]
  • 要素にたどり着くまでリンクをたどるのでリストの真ん中に近い要素ほどアクセスに時間がかかる

マップ

Map<String, Integer> map = new HashMap<>();
Map<Integer, List<String>> map = new TreeMap<>();
  • Map インターフェースはキーと値の両方の型を <> の中に指定する
  • List インターフェース同様 <> に指定できるのは参照型のみ

マップの具象クラス

  • HashMap
  • LinkedHashMap
  • TreeMap

HashMap

HashMapの内部動作
  • HashMap は内部に配列を確保する。これをハッシュ表と呼ぶ。
  • ハッシュ表の初期サイズは HashMap のコンストラクタで指定する。
  • キーと値のペアを HashMap に追加[put]すると HashMap は内部でキーをハッシュ関数に通す。
  • ハッシュ関数の出力がハッシュ表のインデックスになる。
  • 得られたインデックスの要素としてキーと値のペアを格納する。

LinkedHashMap

LinkedHashMapの内部動作
  • HashMap を拡張継承している
  • ハッシュ表に加えて、 LinkedList と同じリンクリストを内部に保持する
  • 要素を追加すると内部でハッシュ表とリンクリストの両方に要素を追加する
LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)
  • 上記コンストラクタの accessOrdertrue を指定するとリンクリストの順序が要素にアクセスした順になる

TreeMap

二分探索木の一種である赤黒木と呼ばれるアルゴリズムによるツリー構造で要素をもつ

TreeMap がキーの順序をもつことを利用したインターフェース

public class Sample {
    public static void main(String... args) {
        NavigableMap<String, String> map = new TreeMap<>();
        map.put("kanayama", "one");
        map.put("kawai", "four");
        map.put("kushibiki", "five");

        Map.Entry<String, String> entry = map.ceilingEntry("kana");
        if (entry != null) {
            entry.getKey() + ": " + entry.getValue();  // "kanayama: one"
        }
    }
}

セット

  • セットは数学の集合の概念
  • 要素の重複を許さない
  • Set インターフェースを実装した具象クラスは以下の3つ
    • HashSet
    • LinkedHashSet
    • TreeSet
  • 要素の重複を許さない点が Map のキーの性質と重なるため、具象クラスも Map と重なる

スタック、キュー、デック

  • スタック: 最新要素から常に取り出し
  • キュー: 最古要素から常に取り出し
  • デック: 上記どちらにも対応
  • スタック構造は Deque クラス(Stack クラスは古い)
  • キュー構造は Queue クラス

イテレータ

拡張for構文

for (要素型 ループ変数 : コレクション変数) {
    forループが回るたびにループ変数が要素オブジェクトを順に参照
}

イテレータ

  • イテレーションを抽象化したオブジェクト
  • Iterator インターフェースは以下のメソッドをもつ
    • boolean hasNext();
    • E next();
    • void remove();
  • ListIterator インターフェースは Iterator を継承している
  • ListIterator は上記に加えて以下の以下のメソッドをもつ
    • boolean hasPrevious();
    • E previous();

セットに対するイテレーション

// Set<Integer> set
for (Integer n : set) {
    sum += n;
}

for (Iterator<Integer> it = set.iterator(); it.hasNext(); ) {
    Integer n = it.next();
    sum += n;
}

マップに対するイテレーション

// Map<String, String> map

// 拡張 for でキーを繰り返す
for (String key : map.keySet()) {
    System.out.println(key);
}

// イテレータで値を繰り返す
for (Iterator<String> it = map.values().iterator(); it.hasNext(); ) {
    String val = it.next();
    System.out.println(val);
}

// 拡張 for でキーと値を繰り返す
for (Map.Entry<String, String> entry : map.entrySet()) {
    System.out.println(entry.getKey());
    System.out.println(entry.getValue());
}

リストを逆順になめる

// List<Integer> list
for (ListIterator<Integer> it = list.listIterator(list.size()); it.hasPrevious(); ) {
    Integer i = it.previous();
    System.out.println(i);
}

配列

int[] arr = new int[3];

int[] arr = { 0, 1, 2 };

多次元配列の初期化

StringBuilder[][][] arr = {
    {
        { new StringBuilder("000"), new StringBuilder("001") },
        { new StringBuilder("010"), new StringBuilder("011") },
    },
    {
        { new StringBuilder("100"), new StringBuilder("101") },
        { new StringBuilder("110"), new StringBuilder("111") },
    },
};

参照

改訂2版 パーフェクトJava

パーフェクトJava読書メモ chapter 5 クラス

Javaを使うために改訂2版 パーフェクトJavaを読んだメモ

オブジェクトの生成

Javaのオブジェクト生成方法は以下の5つ

procedure description
new 基本的な生成手段
String リテラル及び結合演算式 文字列固有の手段
オートボクシング 数値クラス固有の手段
リフレクション フレームワークなどが下位に隠蔽すべき手段
clone メソッド Object クラスに実装された手段

ファクトリパターン

new 式をファクトリメソッドに隠蔽することでオブジェクトの生成をコンストラクタから分離する

class Sample {
    // コンストラクタへのアクセスを制限
    private Sample() {}

    // ファクトリメソッド
    static Sample getInstance() {
        Sample sample = new Sample();
        return sample;
    }
}
  • オブジェクトプーリング
    • オブジェクトの生成にコストがかかる場合、キャッシュされた生成済みオブジェクトを使う
  • シングルトンパターン
    • オブジェクトの数を1つに制限する

クラス宣言

新しいクラスを定義するには次のように予約語 class を使う

[修飾子] class クラス名 {
    メンバ宣言
      - フィールド宣言
      - メソッド宣言
      - ネストしたクラス宣言及びネストしたインターフェース宣言
    コンストラクタ宣言
    初期化ブロック
}

クラスの修飾子

  • クラス宣言時に指定できる修飾子は以下の5つ
  • 複数同時指定可能(ただし finalabstract を同時に指定するとコンパイルエラー)
modifier description
public グローバルにアクセス可(書かないとパッケージ内に限定)
final 継承禁止
abstract 抽象クラス
strictfp クラス内に記述した浮動小数点演算を厳密に評価
アノテーション 省略

フィールド

フィールド宣言の修飾子

modifier description
public アクセス制御
protected アクセス制御
private アクセス制御
final フィールド変数への再代入禁止
static クラスフィールド
transient シリアライズの対象外
volatile スレッド間で変数の値を同期
  • publicprotectedprivate は同時に指定不可
  • finalvolatile は同時に指定不可

フィールド変数のスコープ

  • 宣言の位置にかかわらず同一クラス内のすべてのコンストラクタとメソッドから使える
  • コンストラクタ内、メソッド内以外の場所では宣言した行以降がスコープとなる
class Sample {
    // コンパイルエラー
    private final String s2 = s;

    // コンパイルエラー
    {
        System.out.println(s);
    }

    // フィールド宣言
    private final String s = "sample";
}

this参照

  • this 参照は明示的な宣言なしに使える、該当クラスのオブジェクトを参照する参照型変数
  • クラス内での変数名はフィールド変数よりローカル変数、パラメータ変数が優先される
  • これらの場合にフィールド変数を使う場合は this 参照を明示する
class Sample {
    private final String s = "sample";

    void method (String s) {
        s; // パラメータ変数
        this.s; // フィールド変数
    }

    void method2 () {
        String s = "test 2";
        s; // ローカル変数
        this.s; // フィールド変数
    }
}

メソッド

メソッド宣言の修飾子

modifier description
public アクセス制御
protected アクセス制御
private アクセス制御
abstract 抽象メソッド
final オーバーライド不可
static クラスメソッド
synchronized 同期のためのロック獲得
native ネイティヴメソッド
strictfp 浮動小数点演算を厳密に評価
  • publicprotectedprivate は同時に指定不可
  • abstractstaticfinalsynchronizednativestrictfp と同時に指定不可

同一クラス内でのメソッドのスコープ

  • クラス内でメソッド宣言より前で有効
  • クラス内で this 参照を使った呼び出しも可能

他のクラスからのメソッド呼び出し

  • アクセス制御が許せば可能
  • オブジェクト参照に . メソッドアクセス修飾子を適用する

引数

  • メソッド定義のパラメータ変数を 仮引数
  • メソッド呼び出し時に渡すパラメータを 実引数
  • メソッドが呼ばれると仮引数に実引数が代入される(call by value)
可変長引数
  • メソッド宣言の引数定義でパラメータ変数の型に ... を書く
  • 任意の数の実引数で呼び出すことができる
  • このような引数を可変長引数という
class Sample {
    void exec (String... messages) {
        for (String s : messages) {
            System.out.println(s);
        }
    }
}

Sample sample = new Sample();
sample.exec();
sample.exec("hoge");
sample.exec("hoge", "fuga", "foo");
  • 内部的には配列として引数が渡る
// 上記クラス定義と等価
class Sample {
    void exec (String[] messages) {
        for (String s : messages) {
            System.out.println(s);
        }
    }
}

返り値

  • return 文を使う
  • return はどこにいくつ書いても良い
  • return の後に実行されるような文を書くとコンパイルエラー
  • void メソッドに return を書くとコンパイルエラー
  • return が返す値が返り値の型に代入できないとコンパイルエラー

メソッド実行の終わり方

  • return 文で抜ける
  • void 型のメソッドを最後まで実行して抜ける
  • 例外を投げて抜ける

メソッドのオーバーロード

  • 同じ引数の並びで同名のメソッドの宣言はコンパイルエラー
  • 引数の型が変わると同名でもコンパイル可能
  • これをメソッドのオーバーロードと呼ぶ

メソッドのシグネチャ

  • クラスの中でメソッドを一意に特定する情報をシグネチャと呼ぶ
  • メソッドのシグネチャは以下の2つ
    • メソッド名
    • 引数の型の並び

再帰呼び出し

  • メソッドが自分自身のメソッドを呼ぶこと
  • 停止条件が必要

コンストラクタ

コンストラクタの宣言

  • オブジェクト生成時に呼ばれる
  • コンストラクタ名はクラス名と一致する
  • 修飾子に書けるのは以下の3つ
    • public
    • protected
    • private
  • コンストラクタはクラス名と同名のメソッドに見えるが言語仕様上は別物
  • コンストラクタ内に return 文を書くとコンパイルエラー
  • オブジェクトの初期化処理はコンストラクタにまとめるべき

this呼び出しとsuper呼び出し

  • this 呼び出しでコンストラクタの共通化ができる
class Sample {
    // フィールド宣言を省略

    Sample (String name, String label) {
        this(name, label, 10); // 下のコンストラクタ呼び出し
    }
    Sample (String name, String label, int level) {
        this.name = name;
        this.label = label;
        this.level = level;
    }
}
  • 継承したクラスから継承元のコンストラクタを super で呼び出す
class SubSample extends Sample {
    private final String type;

    SubSample (String name, String label, int level, String type) {
        super(name, label, level); // Sample クラスのコンストラクタ呼び出し
        this.type = type;
    }
}

デフォルトコンストラクタ

  • コンストラクタ宣言が書かれないクラスにはデフォルトコンストラクタが自動生成される
  • デフォルトコンストラクタの引数はなし、中身が空

オブジェクト初期化の順序

  1. フィールド変数にデフォルト値代入
  2. フィールド変数宣言時の初期化、初期化ブロックを上から順に実行
  3. コンストラクタ呼び出し

staticメンバ

  • static 修飾子がついたフィールド変数やメソッド
  • クラスメンバという
  • static がつかないフィールド変数、メソッドはインスタンスメンバ
  • クラスメンバは実体がクラスにしかなく、オブジェクトはコピーを持たない
  • インスタンスフィールドとクラスフィールドは同じ名前空間(同名はつけられない)

継承

  • 継承には実装の継承と振る舞いの継承の2つがある
  • 振る舞いの継承は後出の「インターフェース」
  • 実装の継承としての拡張継承は、あらかじめ意図して設計されたクラスからのみ行うべき

拡張継承の構文

  • クラス宣言時に extends を使って継承する
  • extends のないクラス宣言は暗黙に java.lang.Object を継承する
  • Java のすべてのクラスは必ず java.lang.Object を直接的または間接的に継承する
[修飾子] class クラス名 extends 基底クラス名 {
    クラス本体
}
  • 継承したクラスで継承元と同名のフィールド変数を宣言すると継承元のフィールド変数を隠蔽する
  • 継承元にあるメソッドと同じシグネチャのメソッドを定義するとメソッドをオーバーライドする
  • メソッドをオーバーライドする条件
    • 同じメソッド名
    • 引数の数と型がすべて一致
    • 返り値の型が一致、もしくは継承型
    • throws 説の例外型が一致もしくは継承した例外型
    • アクセス制御が一致もしくはより緩い

@Overrideアノテーション

  • @Override というアノテーションをつけるとオーバーライドのミスに気づける
Class Sample {
    void exec (CharSequence s) {
        System.out.println("sample:exec");
    }
}

Class SubSample extends Sample {
    @Override
    void exec (String s) {
        System.out.println("subSample:exec");
    }
}

super参照

  • オーバーライドされた元メソッドに private 修飾子がついていなければオーバーライドしたメソッド内から super 参照を通じて元のメソッドを呼び出すことができる
  • 隠蔽されたフィールド変数も同様

finalクラス

  • final 修飾子がついたクラスを final クラスと呼ぶ
  • final クラスを継承元にして extends で拡張しようとするとコンパイルエラー

抽象クラスと抽象メソッド

  • 抽象クラスはインスタンス化できないクラス
  • abstract 修飾子をつけてクラス宣言すると抽象クラスになる
  • 抽象クラスは何らかの具象クラスの基底クラスとなる(雛形の役割を担う)
  • メソッド修飾子として abstract をつけると抽象メソッドとなる
  • 抽象メソッドはメソッド本体をもたない(オーバーライド前提)
  • 抽象メソッドを一つでももつとそのクラスは抽象クラス

ネストしたクラス

  • あるクラスの下請けを担うクラスをヘルパークラスという
  • クラス内にクラスを宣言できる
  • ネストしたクラスをメンバクラスと呼ぶ
  • 外側のクラスをエンクロージングクラスと呼ぶ

staticなネストしたクラス

  • static 修飾子がついたネストしたクラス
  • private 修飾子が指定されるとエンクロージングクラスの外から見えなくなる
  • エンクロージングクラスの private フィールドや private メソッドにアクセスできる
  • エンクロージングクラスもネストしたクラスの private フィールドや private メソッドにアクセスできる

内部クラス

  • static なネストしたクラスを内部クラスと呼ぶ
  • 内部クラスのオブジェクトはエンクロージングオブジェクトへの参照を暗黙的にもつ
  • エンクロージングクラスのクラスメソッド内では内部クラスのオブジェクト生成ができない

ローカル内部クラス

  • ローカル内部クラスはメソッド内、コンストラクタ内、初期化ブロック内、 if 節などのブロック内で定義するクラス
  • ローカル内部クラスはブロックの外からはアクセスできない
  • クラスの実装をブロック内に隠蔽するときに使う

匿名クラス

  • 匿名クラスにはクラス名がない
  • オブジェクト生成は以下の構文で行う
new 基底型(実引数) {
    メソッド宣言とフィールド宣言の差分実装
}
  • 匿名クラスにはクラス名がないため、基底型名を new 演算子に渡し、基底型との差分実装を書き足す
  • 匿名クラスを使う利点
    • コンストラクタが不要
    • オブジェクト作成が1つだけ

参照

改訂2版 パーフェクトJava

パーフェクトJava読書メモ chapter 4 変数とオブジェクト

Javaを使うために改訂2版 パーフェクトJavaを読んだメモ

変数とオブジェクト

  • Javaの変数は以下の二つに分類される
    • 基本型変数
    • 参照型変数
  • オブジェクトはある体系にそってデータを表すモノであり、名前を持たない

変数

参照型変数

  • C言語のポインタ型変数の値がメモリ上のアドレス値そのものであるのに対し、Javaの参照型変数の値はオブジェクトの位置情報を指し示す抽象的な「何か」である
  • 参照型変数は名前を持ち、オブジェクトを参照することで扱いやすく橋渡しする
  • 参照型変数自体に型があり以下の3種類、これは参照しているオブジェクトの型とは別
    • クラス型
    • 配列型
    • インターフェース型

基本型変数

  • 基本型変数は値をそのまま保持する
  • 基本型の種類は以下の8つ
    • boolean
    • byte
    • char
    • short
    • int
    • long
    • float
    • double

変数の宣言

  • 変数を使うには最初に変数を宣言する
  • 変数宣言は最初に変数の型を書き、続けて変数名を書く
StringBuilder sb;
  • 基本型変数の宣言時も型名を変数名の前に書く
int i;
  • 同じスコープで同名の変数は宣言できない
void method(int i) {
    int i;
}
// コンパイルエラー

変数の初期化

  • 変数は宣言時に初期化できる
int i = 1;
  • 初期化しない場合の変数のデフォルト値は変数の型と種類に依存する

変数の修飾子

  • 変数の宣言時に修飾子を付けることができる
  • 変数に使える修飾子は以下
    • private
    • protected
    • public
    • transient
    • final
    • static
    • volatile

オブジェクト

オブジェクトの生成

  • new の後にクラス名を書き、 () で引数を指定する
  • 引数の定義はクラスごとに決まっている
new StringBuilder("012");

参照型変数への代入

  • 生成したオブジェクトは参照型変数へ参照を渡して扱う
StringBuilder sb = new StringBuilder();
  • あくまで変数が扱っているのはオブジェクトへの参照なので、以下の例では2つの変数は同じオブジェクトへの参照を持つ
StringBuilder sb = new StringBuilder();
StringBuilder sb2 = sb;
sb.append("012");
sb2.append("345");
// sb: "0123445"
// sb2: "012345"

基本型変数への代入

  • 基本型変数へ代入されるのはオブジェクトの参照ではなく値そのもの
  • 値そのものをコピーして代入する
int i = 42;
int j = i + 1;
int K = i;
// i: 42
// j: 43
// k: 42

null参照

  • null は「何も参照していない」ということを表す
StringBuilder sb = null;
nullチェック
  • 参照先がなく、 null を持つ参照型変数にたいする演算は NullPointerException
  • Object クラスに null をチェックするためのメソッドがある
method description
equals 引数のどちらかが null でも使える比較
toString null の場合の文字列を指定可能
isNull 引数が null のとき真
notNull 引数が null でないとき真
requireNonNull 引数が null だと即座に NullPointerException

Optional型

  • Optional 型は任意のオブジェクトをくるんで null かもしれない状態を表現する
  • 基本型変数のための Optional 型として以下の 3 つがある
    • OptionalInt
    • OptionalLong
    • OptionalDouble
  • Optional オブジェクトは of または ofNullable メソッドを使って任意のオブジェクトから生成できる
StringBuilder sb;
Optional<StringBuilder> osb = Optional.of(sb);
// or
// Oprional<StringBuilder> osb = Optional.ofNullable(sb);
  • get メソッドで Optional オブジェクトでくるんだオブジェクトを取得
Optional<StringBuilder> osb;
StringBuilder sb = osb.get();
  • くるんだオブジェクトが null でなければそのまま呼び出し、 null であれば引数のオブジェクトを返す例
StringBuilder sb = osb.orElse(new StringBuilder("none"));

変数を介さないオブジェクトの操作

  • 変数に参照を格納しなくてもオブジェクトを操作できる
int len = new StringBuilder("012").length();
  • 文字列リテラルでも
int len = "abc".length();
  • ドット演算子でメソッド呼び出しをつなげることをメソッドチェインという
StringBuilder sb = new StringBuilder();
int len = sb.append("012").append("345").length();
// len: 6

変数と型

  • オブジェクトの型はクラスで定義されている
  • 変数の型とオブジェクトの型が一致もしくはオブジェクトの型が変数の型の下位型であるとき変数にオブジェクトの参照を代入可能
  • 変数が参照するオブジェクトに対して行える操作は変数の型できまる
  • 下位型のオブジェクトは上位型の持つメソッドを持つことが保証されている
    • ただし実態が同一とは限らない

変数の詳細

変数の種類

type description
ローカル変数 メソッドもしくはコンストラクタ内で宣言される。メソッドやコンストラクタが終わると消滅。
パラメータ変数 メソッドもしくはコンストラクタに引数として渡る。メソッドやコンストラクタが終わると消滅。
インスタンスフィールド変数 クラスの構成要素
クラスフィールド変数 クラスの構成要素

変数のデフォルト値

type default value
参照型 null
boolean false
char "¥u0000"
byte, short, int, Long 0
float, double +0.0

変数のスコープ

ローカル変数
  • 変数を宣言した行からメソッドもしくはコンストラクタが終わるまで
ブロックスコープ
  • {} で囲ったブロックに閉じたスコープ
  • 変数を宣言した行からブロックが終わるまで
シャドーイング
  • 同一のスコープ内で同じ変数を2度以上宣言
パラメータ変数のスコープ
  • メソッド及びコンストラクタの中
フィールド変数のスコープ
  • フィールド変数のスコープはクラス内
  • 宣言した行の位置は無関係

オブジェクトの寿命

オブジェクトへの参照が外れる条件 * ローカル変数及びパラメータ変数のスコープが外れたとき * オブジェクトが消滅したとき * クラスが消滅し、クラスフィールド変数の参照が外れたとき * 変数に別のオブジェクトの参照もしくは null が再代入されたとき * 変数を介さない操作がされていた場合、式の評価が終わったとき

final変数と不変オブジェクト

  • final 修飾子を指定した変数を final 変数と呼ぶ
  • final 変数は再代入不可
  • final 修飾子が禁止するのは変数自体の値の変更であって、変数が参照するオブジェクト自体の変更ではない

参照

改訂2版 パーフェクトJava

パーフェクトJava読書メモ chapter 3 数値①

Javaを使うために改訂2版 パーフェクトJavaを読んだメモ

整数型

以下の5種類

name bit length min max
byte 8 -128 127
char 16 0 65535
short 16 -32768 32767
int 32 -2147483648 2147483647
long 64 -9223372036854775808 9223372036854775807

bit値

符号あり整数で最上位ビットが1の値は負の値となる

4bit長の符号なし整数
type value bit
min 0 0000
max 15 1111
4bit長の符号あり整数
type value bit
min bit 0 0000
max value 7 0111
min value -8 1000
max bit -1 1111

桁あふれ

整数の加算はbit値を進める処理であり、

整数の減算はbit値を戻す処理である

したがってbitの境界値をまたいで整数の加算減算を行うと予期しない結果となる場合がある

int i = Interger.MAX_VALUE;
// i: 2147483647

i++
// i: -2147483648

整数リテラル

  • L または l で終わる整数リテラルの型は long
  • それ以外の整数リテラルは int
  • int 型リテラル値を bytecharshort の型の変数に代入する場合、値が代入する先の型の範囲内であれば自動的に型が変換されて代入される
    • 代入する先の型の範囲を超えている場合はコンパイルエラーとなる

基数

  • 0b から始めると 2 進数
  • 0 から始めると 8 進数
  • 0x から始めると 16 進数

整数の演算

四則演算

演算子 演算
+
-
*
/
注意点
  • 大きな正の整数の和における桁あふれ
  • 小さな負の整数の差における桁あふれ
  • 絶対値の大きな整数同士の積の桁あふれ
  • 商の結果は切り捨て
  • 0 による割り算は ArithmeticException

剰余

  • 剰余演算子は %
  • 0 による剰余演算は ArithmeticException

符号反転

単項演算子 - は通常単純に符号を反転させる

int n = 10;
// -n: -10

しかしここにも桁あふれの問題があるので注意

int n = -2147483648;
// -n: -2147483648

仕様書によると

-x equals (~x)+1

  • ~ は bit 反転演算子
  • -2147483648 は bit に変換すると 100...0(0 が 31 個つづく)
  • これを反転させると 011...1(1 が 31 個つづく)となり、これは 2147483647 を表す
  • 2147483647 + 1 は桁あふれが起こり、 -2147483648 となる

インクリメント・デクリメント

  • ++ でインクリメント
  • -- でデクリメント

キャスト

() 付きで型を書くことで強制的に型変換する

int i = 1;
short si = (short)i;

代入先の型に収まらないとあふれたbitが切り捨てられる

short si = (short)65536;
// si: 0
  • 65536 は bit に変換すると 100...0 (0 が 16 個つづく)
  • short 型は 16 bit なので先頭の 1 があふれる

ブーリアン(真偽値)

ブーリアン型が取りうる値は truefalse のみ

※ ド・モルガンの法則の説明等

参照

パーフェクトJava読書メモ chapter 2 文字と文字列

Javaを使うために改訂2版 パーフェクトJavaを読んだメモ

文字列

"(ダブルクォート)で囲んで文字数リテラル

String クラス

  • 文字列リテラルから String オブジェクトが自動生成
  • 配列のように1文字ごとにindexが振られる

StringBuilder クラス

  • オブジェクトに対して破壊的
  • String オブジェクトは read only

文字列の結合

  • +=StringBuilder の関係
  • join メソッド

文字列の比較

  • == 演算子じゃなくて equals メソッド
    • == 比較は同一のオブジェクトへの参照かどうかの比較
  • 同じ文字列リテラルは同じ String オブジェクト
  • ただし StringStringBuilder では文字列の内容が同じでも違うオブジェクト
    • そんなときは contentEquals メソッド
  • StringBuilder 同士の文字列の内容比較もできないので、 toStringString に変換してから contentEquals 使うこと

文字列と数値の変換

数値から文字列への変換

valueOf メソッド

String s = String.valueOf(255);
// s: "255"

toString メソッドで10進数以外に直接変換する

String s = Integer.toString(255, 16);
// s: "ff"

文字列から数値への変換

parseInt メソッド

int i = Integer.parseInt("255");
// i: 255

基数を与えて10進数以外の処理

int i = Integer.parseint("ff", 16);
// i: 255

文字

  • '(シングルクォート)で囲んで文字リテラル
  • Javaの世界ではUTF-16
    • 文字を16bitの数値で表す char
  • 文字リテラルは char 型の数値
  • String オブジェクトは文字( char 型)の配列

バイト

  • 歴史的に文字とバイトは同一視されがち
  • Javaではバイトを文字と区別する
  • 8bit長の byte
  • バイト列は byte 型の配列

バイト列と文字列の変換

バイト列から文字列への変換

byte の配列を渡して String オブジェクトを生成

byte[] bytes = new byte[]{0x61, 0x62, 0x63};
String s = new String(bytes);
// s: abc
文字列からバイト列への変換

getBytes メソッドを使う

String s = "abc";
byte[] bytes = s.getBytes();

参照

改訂2版 パーフェクトJava