Effective Javaを読みました

Javaを扱っている人のバイブル的な本ですね。

今まで読んだ本について各項目について書いておりましたが、Effective Javaについては量が多いのでざっくりとした感想だけ書きたいと思います。

第3版を読みました。第2版と比較しましたが、ラムダとストリームという章が追加され、その他の章は大きくは変わってなさそうな印象でした。第2版はもちろん読んでません。

◾️追加された項目
第2章 オブジェクトの生成と消滅
 項目5 資源を直接結び付けるよりも依存性注入を選ぶ
 項目8  ファイナライザとクリーナーを避ける
 項目9 try-finallyよりもtry-with-resourcesを選ぶ

第4章 クラスとインターフェース
 項目21 将来のためにインターフェースを設計する
 項目25 ソースファイルを単一のトップレベルのクラスに限定する

第7章 ラムダとストリーム(※全て追加)
 項目42 無名クラスよりもラムダを選ぶ
 項目43 ラムダよりもメソッド参照を選ぶ
 項目44 標準の関数型インターフェースを使う
 項目45 ストリームを注意して使う
 項目46 ストリームで副作用のない関数を選ぶ
 項目47 戻り値型としてStreamよりもCollectionを選ぶ
 項目48 ストリームを並列化することは注意を払う

第8章 メソッド
 項目55 オプショナルを注意して返す

第11章 並行性
 項目80 スレッドよりもエグゼキュータ、タスク、ストリームを選ぶ

第12章 シリアライズ
 項目85 Javaのシリアライズよりも代替手段を選ぶ

◾️感想
正直、Javaを軽くしか触っていない自分にとっては難しいところが多々ある本でした。
といっても、メソッドやプログラミング一般などは比較的な簡単な項目があったりしたので、全部が難しいわけではないです。

この本に関して初心者に読んでほしい、初心者には向かないなど色々な意見がありますが、自分は初心者の人に関しては、読み方を気をつければ読んだほうがいいかと思います。
罠にハマらないような書き方、どうすると罠にはまってしまうのかなども記載されているため、早めに直しておきたい項目が多々あるからです。
読み方としては項目毎に軽く2ページほど読んで、理解できそうかどうか、あるいは頑張れば理解できそうだったらしっかりその項目を読んで、わからなかったところは時間が経ってから読み直すといったやり方がいいと思います。
デザインパターンを知らないのに解説もされていないデザインパターンについて言及されている項目が理解できるはずがないと思います。
そういうのが多くある場合、挫折すると思うのでここは後回しでいいと割り切っていくのがいいかと思います。
自分も読んでいて心折れかけそうなときがありました。
ですので、わからなかったときに何を勉強しておくとわかるようになるのかをメモしておいてそれについて勉強してから読むぐらいの認識でいいと思います。

内容についてですが、凄く勉強になりました。
例えば 第2章のすべてのオブジェクトに共通のメソッドについてはお恥ずかしながらJava標準でのメソッドをオーバーライドをするという発想が皆無だったのでtoStringをオーバーライドするなどは新鮮でした。
第5章のジェネリック型はよく使ってますがやっぱり便利だと感じます。ArrayListがPHPにも欲しい。
第6章のenum、第7章のラムダとストリーム、第11章の並行性はぜひ使いこなしたいです。
誤解かもしれないですが継承はもうあまり使わない方がよかったりするのかな?最近はis-aよりhas-a傾向強い気がします。

この本に関しては手元に置いておいて必要な時に何度も読み返したいと思います。

Prototypeパターン

Prptotypeパターンは生成に関するデザインパターンの1つです。 通常はnewしてインスタンスを生成しますが、Prototypeでは既存のインスタンスをコピー(clone)して新しいインスタンスを生成します。

メリット
新しいインスタンスを作成する複雑さを、クライアントから隠蔽する。
型のわからないオブジェクトの生成を行うという選択肢を、クライアントに提供する。
環境においてはオブジェクトのコピーの方が新しいオブジェクトを作成するよりも有効である可能性がある(ソースコードの管理等)。

デメリット
コピーを作る事で物事が複雑になる場合がある(これについては追々書いてみたい)。

Head First デザインパターンでモンスターを作るクラス図があったのでそれを参考に中身だけ書いてみたいと思います。
単純にモンスターをコピーできるようにするのと、ゲームを楽しむユーザーがモンスターのレベルとHPを変更できるようにしていきます。

・Monster.php

<?php
abstract class Monster
{
    private int $id; // 1種類につき与えられるモンスターのid
    private string $name;
    protected int $level;
    protected int $hp;
    private string $category;
    private bool $copied; // コピーされたものかどうか

    public function __construct(int $id, string $name, int $level, int $hp, string $category)
    {
        $this->id = $id;
        $this->name = $name;
        $this->level = $level;
        $this->hp = $hp;
        $this->category = $category;
        $this->copied = false;
    }

    abstract protected function __clone();

    public function newInstance(): \Monster
    {
        return clone $this;
    }

    public function getId(): int
    {
        return $this->id;
    }

    public function getName(): string
    {
        return $this->name;
    }

    public function getLevel(): int
    {
        return $this->level;
    }

    public function getHp(): int
    {
        return $this->hp;
    }

    public function getCategory(): string
    {
        return $this->category;
    }

    public function getCaller(): string
    {
        return $this->caller;
    }

    protected function copied(bool $copied): void
    {
        $this->copied = $copied;
    }

}

ユーザー側で値を変更できないモンスター
・WellKnownMonster.php

<?php
require_once 'Monster.php';

class WellKnownMonster extends Monster
{

    protected function __clone()
    {
        $this->copied(true);
    }

}

ユーザー側でlevelとhpを変更できるモンスター
・DynamicPlayerGeneratedMonster.php

<?php
require_once 'Monster.php';

class DynamicPlayerGeneratedMonster extends Monster
{
    protected function __clone() {
        $this->copied(true);
    }

    public function setLevel(int $level): void
    {
        $this->level = $level;
    }

    public function setHp(int $hp): void
    {
        $this->hp = $hp;
    }
}

・MonsterRegistry.php

<?php

class MonsterRegistry
{
    private array $registered;

    public function register(Monster $monster): void
    {
        $this->registered[$monster->getId()] = $monster;
    }

    public function getMonster(int $id){
        return $this->registered[$id]->newInstance();
    }
}

・ index.php

<?php
require_once 'MonsterRegistry.php';
require_once 'WellKnownMonster.php';
require_once 'DynamicPlayerGeneratedMonster.php';

$monsterRegistry = new MonsterRegistry();
$monster1 = new WellKnownMonster(1, 'ゴブリン', 4, 12, 'ゴブリン');
$monsterRegistry->register($monster1);
$monsterCopy1 = $monsterRegistry->getMonster($monster1->getId());

var_dump($monster1);
var_dump($monsterCopy1);

$monster2 = new DynamicPlayerGeneratedMonster(37, 'アイスボム', 24, 400, 'ボム');
$monsterRegistry->register($monster2);
$monsterCopy2 = $monsterRegistry->getMonster($monster2->getId());
$monsterCopy2->setLevel(99);
$monsterCopy2->setHp(9999);

var_dump($monster2);
var_dump($monsterCopy2);

コピー元となるモンスターはDBに入れそうなものですが…。 その場合は引っ張ってきてインスタンス化して必要であればコピーしていくといった方法で使うといいかな?って思います。

(参考)
Head Firstデザインパターン ―頭とからだで覚えるデザインパターンの基本
2005/12/2
Eric Freeman (著), Elisabeth Freeman (著), Kathy Sierra (著), Bert Bates (著), 佐藤 直生 (監訳), 木下 哲也 (翻訳), 有限会社 福龍興業 (翻訳)
13章 付録:残りのパターン

Builderパターン

Buliderクラスについてサンプルを書きます。
effective javaに書いているようなBuilderとGoFのBuilderは違うのかな?その辺がよくわかりませんが、GoFの方を書いていきます。目的は一緒だと思います。
effective javaのBuilderは下をご覧ください。

Bulderパターンですが生成に関するパターンで複合構造の構築に良く使われます。
監督者が(director)、指示を出して建築者 (Builder) が構築していくイメージです。

メリット
複雑なオブジェクトの構築方法をカプセル化できること

デメリット
構築がめんどくさ(ry

他にも複数の手順の変化するプロセスで柔軟なオブジェクトを作成できる点(Factoryとは対照的)がメリットとして挙げられますね。思っていたのと違うパラメータを設定している可能性もあるので一長一短ですが。

それでは作っていきます。夕飯を作ります。

パターン1:ステーキ定食(ご飯、ステーキ、オニオンスープ、ポテトサラダ)
パターン2:パン、ハンバーグ、シーザーサラダ

・Builder.php

<?php
interface Builder
{
    public function stapleFood(string $stapleFood):void;
    public function mainDish(string $mainDish):void;
    public function soup(string $soup):void;
    public function salad(string $salad):void;
    public function getResult();
}

・DinnerBuilder.php

<?php
require_once 'Builder.php';

class DinnerBuilder implements Builder
{
    private string $stapleFood;
    private string $mainDish;
    private string $soup;
    private string $salad;

    public function stapleFood(string $stapleFood): void
    {
        $this->stapleFood = $stapleFood;
    }

    public function mainDish(string $mainDish): void
    {
        $this->mainDish = $mainDish;
    }

    public function soup(string $soup): void
    {
        $this->soup = $soup;
    }

    public function salad(string $salad): void
    {
        $this->salad = $salad;
    }

    public function getResult(): dinnerBuilder
    {
        return $this;
    }
}

・Director.php

<?php

require_once 'Builder.php';

class Director
{

    private Builder $builder;

    public function __construct(Builder $builder){
        $this->builder = $builder;
    }

    public function makeSteakSetMeal(): void
    {
        $this->builder->stapleFood("rice");
        $this->builder->mainDish("steak");
        $this->builder->soup("onion soup");
        $this->builder->salad("potato salad");
    }

    public function addStapleFood(string $stapleFood): void
    {
        $this->builder->stapleFood($stapleFood);
    }

    public function addMainDish(string $mainDish): void
    {
        $this->builder->mainDish($mainDish);
    }

    public function addSoup(string $soup): void
    {
        $this->builder->soup($soup);
    }

    public function addSalad(string $salad): void
    {
        $this->builder->salad($salad);
    }
}

・index.php

<?php
require_once 'DinnerBuilder.php';
require_once 'Director.php';

$builder1 = new DinnerBuilder();
$director1 = new Director($builder1);
$director1->makeSteakSetMeal();

var_dump($builder1);

$builder2 = new DinnerBuilder();
$director2 = new Director($builder2);
$director2->addStapleFood("bread");
$director2->addMainDish("hamburg steak");
$director2->addSalad("caesar salad");

var_dump($builder2);

Builderパターンは実はeffective javaで紹介されているような物を良く使っていてあまりこちらで書いていないので他の人がこの書き方で書いているのを見た事があって混乱した覚えがあります。BuilderといってもLombok BuilderやFluent Builderというのもあるみたいです。複雑ですね…

(参考)
Head Firstデザインパターン ―頭とからだで覚えるデザインパターンの基本
2005/12/2
Eric Freeman (著), Elisabeth Freeman (著), Kathy Sierra (著), Bert Bates (著), 佐藤 直生 (監訳), 木下 哲也 (翻訳), 有限会社 福龍興業 (翻訳)
13章 付録:残りのパターン

Singletonパターン

Singletonパターンについて書いてみます。

Singletonパターンはインスタンスが1つしか存在しない唯一のオブジェクトを生成するために使います。

オブジェクトが1つしか必要ないというケースとは ・スレッドプール ・キャッシュ ・ダイアログボックス ・プリファレンス ・レジストリの設定を処理するオブジェクト ・ロギング用のオブジェクト など。 これらのオブジェクトを二つ以上インスタンス化してしまうとプログラムの誤操作、リソースの使いすぎ、つじつまの合わない結果などの問題が出てきます。

特徴としてクラス外部からインスタンスを作成できないようにするためにコンストラクタをprivateにします。

・Singleton.php

<?php
class Singleton
{
    private static Singleton $singleton;

    private function __construct()
    {
        echo "create singleton";
    }

    public static function getInstance(): \\Singleton
    {
        if (!isset(self::$singleton)) {
            self::$singleton = new Singleton();
        }

        return self::$singleton;
    }
}

・index.php

<?php
require_once 'Singleton.php';

$singleton1 = Singleton::getInstance();
$singleton2 = Singleton::getInstance();

if ($singleton1 === $singleton2) {
    echo "同じインスタンス";
}

// new Singleton();
// Fatal error: Uncaught Error: Call to private

インスタンスをnewしようとするとコンストラクタがプライベートなのでFatal errorとなります。 インスタンスが一つしか存在しないことを保証できます


(参考)
Head Firstデザインパターン ―頭とからだで覚えるデザインパターンの基本
2005/12/2
Eric Freeman (著), Elisabeth Freeman (著), Kathy Sierra (著), Bert Bates (著), 佐藤 直生 (監訳), 木下 哲也 (翻訳), 有限会社 福龍興業 (翻訳)
5章 Singletonパターン:唯一のオブジェクト

SQLパフォーマンス詳解を読んだ

明けましておめでとうございます。今年もよろしくお願いいたします。

11月の事ですが、SQLパフォーマンス詳解を読みました。
感想などを書いていきたいと思います。

読んでみようと思ったきっかけ
かなり多くの回数呼ばれるプログラムのデバッグや後輩のコードレビューをする中でパフォーマンスを意識したSQLの知識が皆無に等しくこのままでは良くないと思ったため。また先輩から勧められたため。

感想
インデックスをどう活用するかを書いてくれててわかりやすかった。どうすればパフォーマンスが良くなるのかということでググると、「適切なインデックス」を張るということをよく目にするがいつも「どうすれば適切になるんだ」と思っていた。張り過ぎもよくないと聞くので「過ぎ」ってどの程度のことなんだろうと思ったりしていた。インデックスは作成されているけど、sqlの書き方のせいで使えていないとかもある。そういうものがわかってきたので読んで良かったと思います。MySQLのExtra列については今後しっかり勉強したい。

以下まとめ(MySQLのみ) 第4章、5章は省略

第1章 インデックスの内部構造

インデックスの最も重要な目的は、インデックスを張ったデータに対して順序をつけてアクセスできるようにすることです。

writeの処理(insert, update, delete)が走る際に、大量のデータを動かすわけにはいかない。そういう場合の解決策として双方向連結リストでインデックスリーフノードと対応するテーブルのデータを対応させている。

検索ツリー(Bツリー)
データベースがリーフノードを高速に見つけるのに役に立つ。MySQLの場合は、BTREEと表記されている。Bはバランスの略でその名の通り木構造。1~1000というidが入ったノードと1001~2000、2001~3000といったノードがあって20xxというidを検索するとき、1から順に検索しなくて良いので速くなる。1001以上かどうか、2001以上かどうかなどを調べて該当のノードの中を確認する。 ツリーが深さは対数的に増え、リーフノードの増加に対してツリーの深さの増大は非常に遅い。

インデックスによる検索は、以下の3ステップで行われる。

  1. ツリーを走査
  2. リーフノードチェーンをたどる
  3. テーブルからデータを読み出す

UNIQUEなSCANは1しか行わないため高速。

第2章 WHERE句

前提としてSQLを発行する際には実行計画(EXPLAIN)は必ず行う。 MySQLにおける実行計画で最低限注意して見るべき点は下の記事を参考にしています。

https://nippondanji.blogspot.com/2009/03/mysqlexplain.html

複合インデックス定義する際に考えるべき最も重要なのは、そのインデックスを使えるSQL文ができるだけ多くなるように、列の順番を決めることです。

a, b, c, dという4つの列の複合インデックスがあった場合は、a,bを条件に検索するときも使えるので先に定義しておく。cを条件にすることが多い場合は、c, a, b, dなどの順で定義するといい。こうすることで新たなインデックスの作成を減らせる。インデックスの数が多いと更新処理のパフォーマンスに影響する。

WHERE句はインデックスの列の順に合わせて使う。 OR検索やWHERE INなどは場合によっては実行計画が変わるので出来るだけUNIONやJOINをうまく使って出来るだけ使わないようにしたほうがいいかもしれない。データ数が少ないテーブルの場合はそんなに気にしなくていいとは思うが。

パラメータ化クエリはちゃんと使う。SQLインジェクションを防ぐために。残念ながらMySQLはされていないが、SQLによってはパラメータ化クエリを使うことでキャッシュされたSQLが使える。

・範囲検索(大なり、小なり、BETWEEN) なるべく先に狭い範囲になるように検索してから残りを検索する。

・LIKE ワイルドカード(%)を先頭に出来るだけ使わない。 使ったことなかったけどmatchやagainstを使えるなら使う。

カラムはしっかり指定して*で取得するのはやめる。

インデックスは2つ使うより、1つだけ使う方が高速

第3章 パフォーマンスとスケーラビリティ

データ量が多くなるにつれパフォーマンスは悪くなる。

注意深い実行計画の調査結果は、うわべだけのベンチマークよりも信用のおけるものです。完全な負荷テストは意味のあることですが、そのための手間はかかります。

本番のデータとテスト時のデータ、開発時のデータは全く違うものだと思うし、似たようなテストをするにしても大変な手間がかかる。大量データの場合は特に、適切なインデックスが張られているかを注意する。ただそれだけデータがあるということはそれだけ更新処理があると思うので張りすぎにも注意する。

リレーショナルSQLデータベースかリレーショナルでないシステムに関わらず、正しいインデックスを作ることは、クエリの応答時間を短くする唯一の方法です。

第6章 ソートとグルーピング

order by句がインデックスによる順序付けと一致している場合、 データベースは明示的なソート処理を省略できます。

order by句でASK, DESCが混在している場合はインデックスが効かない。ASC,ASCやDESC,DESCだったら効く。混在させる場合はインデックスを定義する必要がある。

第7章 部分的結果

・LIMIT

データベースは、部分結果のみを取得することを事前に知っている場合のみ、部分結果のみ、部分結果向けにクエリを最適化できます。

実際に事前に知れているかどうかは実行計画を確認する。ただMySQLの場合は、Extra列に「using filesort」がなければ部分結果の取得が行われている。

・ページングについて

ページングの際は、並べ替えの順序は確定的である必要があります。

ページングの際はoffsetを使うのが一般的だがそのテーブルにリアルタイムで行が挿入される場合は、挿入されたデータが最初に来る場合があるので、ずれてしまう。

第8章 挿入、削除、更新

インデックスが作られるとデータベースはインデックスを自動的にメンテナンスする。 簡単にいうとinsertするとインデックスのノードも更新するため処理が重くなる。 インデックスを張りすぎると良くないのはこのため。insertが圧倒的に多いテーブルにはインデックスは出来るだけ少なくする必要がある。

インデックスは注意深くかつ慎重に使い、かつ、可能な限り冗長なインデックスは使わないようにしましょう。これは、delete文やupdate文を使う際にも同じ事が言えます。

deleteやupdateにも実行計画はあります。