技術の犬小屋

Webプログラミングを餌に生きる犬のメモ帳

Posts in the プログラミング言語 category

相変わらずEffective Javaを読んでいる。自分でジェネリクスなクラスを作る場合の書式と利点が分からなかったので調べた。後から見返して分かり辛いと思ったところは加筆する予定。
 
ちなみに基本的なジェネリクスの使用方法である、リスト型 + ジェネリクスの使い方は過去の記事でまとめたので、そちらを参照してほしい。
Javaでは配列よりリスト(ジェネリクス)を選ぶ
 

型引数とは

型引数はクラス宣言・インタフェース宣言で、クラス名・インタフェース名に続いて指定する。型引数は<>で囲み、その中に1つ以上の型変数を定義する。複数個指定する場合には、コンマ(,)で区切る。

public interface Map<K, V> {
  //...
}

 
型変数の命名規則は変数の命名規則と同じだが、英大文字で1字が推奨されている。
定義した型変数は、implements句やextends句、メソッドの引数、返り値だけでなく、インスタンス変数やメソッド内部で用いることもできる。

public class Value<V> {
    private V value=null;
     
    public Value(V value){
        this.value=value;
    }
     
    public V getValue(){
      return this.value;
    }
     
    public String toString(){
        return value.toString();
    }
}

 
この例ではインスタンス変数として、型引数で指定された型のオブジェクトvalueを指定している。またtoString()メソッド内で、valueのメソッドを呼び出している。valueの型はこのクラス作成時には一意には決まっていないが、必ずjava.lang.Objectのインスタンスであるので、java.lang.Objectのメソッドだけは呼び出すことが可能である。もし次のように型引数で指定できるクラスを限定した場合には、呼び出せるメソッドも増える。

import java.util.Date;
 
public class Term<S extends Date, E extends Date> {
    private S date1 = null;
    private E date2 = null;
     
    public Term(S date1, E date2){
        this.date1 = date1;
        this.date2 = date2;
    }
     
    public long calculateSpan(){
        return date1.getTime() - date2.getTime();
    }
}

 
extends句を用いて、型引数としてjava.util.Dateのサブクラスしか認めないようにしている。この場合には、インスタンス変数date1もdate2もjava.util.Dateのインスタンスになる。従ってjava.util.Dateのメソッドも呼び出すことができる。
 
次のように指定することで、上記のジェネリクスなクラスを使用することが出来る。

Term<Time, Time> period = new Term<Time, Time>(new Time(), new Time(5000));

 
このようにすると、Termクラスの型引数S, EにDate型のサブクラスであるTime型を渡すことができ、渡されたTime型はTermクラスの内部で使用される。
 

ワイルドカードで柔軟な型引数を作る

ジェネリクスのワイルドカードは?記号で表し、メソッドの呼び出し時に型引数として利用することができる。ワイルドカードを指定することで、型変数を特定しない操作が出来るようになる。
ジェネリクスのワイルドカードは次のように利用する。

class Value<T> {
    private T value;
    public Value(T value) {
        this.value = value;
    }

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }
}

class Test {
    public void printValue(Value<?> obj) {
        System.out.println(obj.getValue());
    }

    public static void main(String args[]) {
        printValue(new Value<String>("dog house"));
        printValue(new Value<Integer>(new Integer(10)));
    }
}

 
printValueメソッドの引数に注目してほしい。引数objはワイルドカードを用いたValue型として宣言されている。この場合、Valueクラスの型引数に指定する型は問わないという意味になる。
 
printValue()メソッドでは、T型の実装に依存するような振る舞いがないということにも注目してほしい。T型の実体は少なくともObject型を継承していることは保障されているため、obj.getValue()メソッドを呼び出す操作は認められている。
 
しかし、一方でワイルドカードの型変数に対して代入する行為はコンパイラによって禁止されている。例えば、次のコードはコンパイルエラーになる。

public static void main(String args[]) {
    Value<?> obj = new Value<String>("dog house");
    obj.setValue("cat house");
}

 

上限と下限を設定する

しかし、単にワイルドカードを使用するだけでは本来ジェネリクスが持っている不変という性質の利点を捨ててしまうことになる。そこで、ワイルドカードに「上限」と「下限」を設定し、一定の範囲をワイルドカードに与えることで、特定のクラスのサブクラスであることを強要、またはスーパークラスであることを強要することができる。この「上限」と「下限」が与えられたワイルドカードを「境界ワイルドカード」と呼ぶ。
 

上限境界ワイルドカードを設定する

境界ワイルドカードのうち、指定した型と同じかその型を継承するサブクラス(サブインタフェース)であることを強要するものを「上限境界ワイルドカード」と呼ぶ。
ワイルドカードに対して上限を設定するには、extendsキーワードを使用する。例えば、Number型を上限として設定したい場合は次のようになる。
 

Value <? extends Number>

 
実際にコードに当てはめると次のようになる。

static void printValue(Value<? extends Number> obj) {
    System.out.println("type = " + obj.getValue().getClass());
    System.out.println("int value = " + obj.getValue().intValue());
    System.out.println("double value = " + obj.getValue().doubleValue());
}

public static void main(String args[]) {
    printValue(new Value<Integer>(100));
    printValue(new Value<Double>(1.23456789));
    //以下はエラーになる
    //printValue(new Value<String>("Kitty"));
}

 
この場合、Valueクラスの型変数Tは不明な型であるが、NumberクラスまたはNumberクラスのサブクラスでなければならないという境界が設定される。そのため、型変数TがString型である場合はコンパイルエラーとなる。
 

下限境界ワイルドカードを設定する

境界ワイルドカードのうち、そのクラス、またはそのクラスのスーパークラスであることを強要するものを「下限境界ワイルドカード」と呼ぶ。
ワイルドカードに対して下限を設定するには、superキーワードを使用する。例えば、String型を下限として設定したい場合は次のようになる。
 

Value <? super String>

 
実際にコードに当てはめると次のようになる。

static void printValue(Value<? super String> obj) {
    System.out.println("type = " + obj.getValue().getClass());
    System.out.println("int value = " + obj.getValue() + "\n");
}

public static void main(String args[]) {
    printValue(new Value<String>("dog"));
    printValue(new Value<Object>(new Integer(10)));
    //以下はエラーになる
    //printValue(new Value<Integer>(new Integer(10)));
}

 
この場合、Valueクラスの型変数Tは不明な型であるが、ValueクラスのT型変数の下限がString型でなければならないという境界が設定される。そのためTにString型のサブクラスや、他の関係のないクラスを指定することはできない。
 

上限と下限どちらを使うか

Java言語は、上限境界ワイルドカードと下限境界ワイルドカードの両方をサポートしているため、どちらを使うのか、そしてそれをいつ使うのかを、どのようにして知ればよいだろうか。これに関してはgetとputの原則(get-put principle)という単純なルールがあり、このルールによって、どちらの種類のワイルドカードを使うのかを判断することができる。
構造から値を取得する (get) だけの場合には 上限境界(extends)ワイルドカードを使い、構造の中に値を格納する(put)だけの場合には下限境界(super)ワイルドカードを使い、そして両方を行う場合にはワイルドカードを使ってはならない。
 
 
以上
 
 
参考
書籍 Effective Java
1. ジェネリクス (3) | TECHSCORE(テックスコア)
ワイルドカード
Java の理論と実践: Generics のワイルドカードを使いこなす、第 2 回

Effective Javaを読んでいる。Javaでは、配列は使わずにリスト(ジェネリクス)を使えということだった。
 

ジェネリクスとは

ジェネリクスとは、「総称性(Genericity)」「ジェネリック・プログラミング」とも呼ばれるプログラミング技法で、 オブジェクト指向とは異なるパラダイムからきたものである。データの型に束縛されず、型そのものをパラメータ化して扱うことができる。Javaでは主にコレクションクラスに導入されている。
 

変性とは

何故、配列よりリストを使うことが推奨されるのかを理解するために、先に変性について理解しておく必要がある。

  • 共変 (covariant): 広い型(例:double)から狭い型(例:float)へ変換すること。
  • 反変 (contravariant) : 狭い型(例:float)から広い型(例:double)へ変換すること。
  • 不変 (invariant): 型を変換できないこと。

 
ここにおける「広い」「狭い」とは、機能が「広い」「狭い」という意味で、例えば、double型(64bitまで表現可能な小数を定義できる型)をfloat(32bitまで表現可能な小数を定義できる型)に変換する場合などは、共変 (covariant)である。逆にfloatをdoubleに変換する場合などは、反変 (contravariant)である。不変 (invariant)については、そのままの意味なので説明は省く。
 

配列よりリストを選ぶ

Javaの配列とリスト(ジェネリクス型)の最も大きな違いは、次の点である。

  • 配列は共変である。
  • リスト(ジェネリクス型)は不変である。

 
以下より、それぞれの点について詳しく説明する。
 

配列は共変

配列は共変という性質を持っているので、Object[]にString[]を代入することが出来る。つまり、String[]はObject[]のサブクラスである。

String[] strArray = {"test1", "test2"};
Object[] objArray = strArray; // 配列は共変なので代入可能
objArray[0] = new Integer(3); // java.lang.ArrayStoreException

 

リスト(ジェネリクス型)は不変

一方の、リスト(ジェネリクス型)は不変なので、Object[]にString[]を代入することが出来ない。

List<String> strList = new ArrayList<String>();
strList.add("test1");
strList.add("test2"); 
List<Object> objList = new ArrayList<Object>();
objList = strList; // コンパイルエラー

 
配列やリストでは、型の混入は避けたい現象である。配列の場合は、実行時に不正な型が混入した場所で例外(java.lang.ArrayStoreException)を投げてくれる。一方、リスト(ジェネリクス型)では、コンパイル時にエラーとなる。実行時ではなく、コンパイル時にきちんと例外を投げてくれると、バグの混入場所が特定が容易である。
 
上記を踏まえて、Javaでは配列ではなく、リスト(ジェネリクス型)を使用することを推奨する。
  
 
以上
 
 
参考
書籍 Effective Java
共変性と反変性 (計算機科学) – Wikipedia
今まで知らなかった 5 つの事項: Java コレクション API の場合: 第 1 回
なぜ Java の配列は共変で、Generics は共変ではないのか – sinsengumi血風録

最近はEffective Javaをずっと読んでいる。Javaでは定数宣言にpublic static final intを使わず、enumを使えとのことだったのでメモしておく。
 

従来の定数宣言

従来は、Javaで定数宣言をするときは、以下のように宣言していた。

//色の黒と白
public static final int COLOR_BLACK = 0;
public static final int COLOR_WHITE = 1;

//幼女向けアニメ「プリキュア」のブラックとホワイト
public static final int PRECURE_BLACK = 0;
public static final int PRECURE_WHITE = 1;

 
この方法は、int enumパターンとして知られており、以下のような多くの欠点を持っている。

  • 名前空間を持たないので、変数名に接頭辞(COLOR・PRECURE)を多用することになり、冗長である。
  • コンパイル時に呼び出した場所へインライン展開されてしまう(例:Integer apiVersion = Api.VERSION;がコンパイルされると、Integer apiVersion = Integer.valueOf(10);になる)ため、再コンパイルの必要がある。
  • 定数をデバッガから表示しても見えるのは数字であり、あまり役に立つ情報ではない。
  • グループ内の定数を全てイテレートしたり、グループの大きさを得たりする信頼できる方法が提供されていない。

 
int enumパターンの代わりに、enum型を使用することでこれらの欠点を全て無くすことが出来る。
 

enum型とは

enum型は、あるカテゴリーに属した複数の定数をひとまとまりとして取り扱うための仕組みである。J2SE 5.0のenumは、CやC++のenumのように内部的に整数で定数を保持するものではなく、タイプセーフenumと呼ばれるパターンを実現したものになっている。
 

単純なenum

まず初めに一番単純なenumの定義方法を例示する。

public enum Color { RED, BLUE, YELLOW, BLACK, WHITE }

 
enumは内部的にはクラスである。enum 型を定義する場所はクラスを定義する場所と同じ。enum型もクラス同様パッケージに属する。基本的には1ファイルにひとつのenum型を定義するが、内部クラスのように他のクラスの内部にも定義することができる。アクセス権やアクセス方法もクラスの場合と同じである。
 
次のように記述することで、上記のColorクラスを利用することが出来る。

import define.common.Color;
public class EnumTest {
    public static void main(String[] args) {
        Color color = Color.WHITE;
        System.out.println("Color is " + color); //"Color is WHITE"を出力する。 
    }
}

 
上記のenumをjavapコマンドで逆コンパイルすると、以下のように表示される。

Compiled from "Color.java"
public class Color extends java.lang.Enum{
    public static final Color RED;
    public static final Color BLUE;
    public static final Color YELLOW;
    public static final Color BLACK;
    public static final Color WHITE;
    static {};
    public static final Color[] values();
    public static final Color valueOf(java.lang.String);
}

 
逆コンパイルしたColorクラスは以下のような特徴を持っている。

  • java.lang.Enumクラスを継承している。
  • 各メンバはpublic static finalな定数である。
  • valuesメソッドは宣言された定数全てを含む配列を返すメソッドである。
  • valueOfメソッドは文字列から定数インスタンスを取得するメソッドである。

 

enum型のクラスにカスタムメソッドを追加して更に便利にする

以下は、enum型のクラスにカスタムメソッドを追加する例である。

public enum Precure {
    BLACK("なぎさ"),
    WHITE("ほのか");

    private final String name;

    //コンストラクタ
    public Precure(String name) {
        this.name = name;
    }

    public String name() {
        return this.name;
    }
}

 
enum内の各定数はインスタンスなので、実行時には暗黙的にコンストラクタが呼び出される。このコンストラクタには引数を持たせることができ、定数ごとに異なる引数を渡すことができる。
 
上記のコードは次のように呼び出して使用することが出来る。

System.out.println(Precure.BLACK.name()); // 「なぎさ」と表示される。

 

定数ごとに異なる振る舞いを持たせる

以下のように記述することで、定数ごとに異なる振る舞いを設定することが出来る。

public enum Precure {
    BLACK() {
        public String apply(String s) {
            return "<span style=\"color:#000000\">" + s + "</span>";
        }
    },
    WHITE() {
        public String apply(String s) {
            return "<span style=\"color:#FFFFFF\">" + s + "</span>";
        }
    };

    //抽象メソッドで、それぞれの定数にapplyメソッドの実装を強制する
    abstract public String apply(String s);
}

 
上記のコードは次のように呼び出して使用することが出来る。

System.out.println(Precure.BLACK.apply("キュアブラック!")); // 「<span style=\"color:#FFFFFF\">キュアブラック!</span>」と表示される。

 
 
以上
 
 
参考
書籍 Effective Java
4. enum | TECHSCORE(テックスコア)
J2SE 5.0 Tiger 虎の穴 Typesafe Enum

JavaにもPHPのcloneメソッドのような機能があり、PHPのcloneメソッドのような使い勝手を期待したが、あまり良いものではなかった。使い方が結構特殊だったのでメモを残しておく。
 

2種類のコピー方法

オブジェクトのコピーにはシャローコピーとディープコピーの2種類があり、cloneメソッドの使い方を学ぶ上では、前提知識として持っておくべきである。以下より、それぞれのコピーについて説明する。
 

シャローコピー

シャローコピー(浅いコピー)は、コピー元のオブジェクトとコピー先のオブジェクトがメモリ上の同じデータ(インスタンス変数)を参照している状態である。
コピー元のオブジェクトに対してインスタンス変数に変更を加えると、コピー先のオブジェクトが参照しているデータが同じ物なので、コピー先のオブジェクトから見たインスタンス変数も変更されることになる。
つまり、シャローコピーは参照のコピーのみを行う。
 

ディープコピー

ディープコピー(深いコピー)は、オブジェクトのみのコピーではなく、オブジェクトとメモリ上のデータ(インスタンス変数)の両方をコピーする。
二つのオブジェクトが参照しているデータは別々のものなので、一方のオブジェクトのインスタンス変数に変更を加えても、もう一方のオブジェクトには影響を与えない。
 

cloneメソッドとは

cloneメソッドとは、オブジェクトのコピーを返すメソッドである。Javaのcloneメソッドはかなり特殊で、Java以外の言語(PHPやRubyなど)のcloneメソッドではディープコピーを提供するが、Javaのcloneメソッドでは、シャローコピーが提供される。しかし、メソッドの使用者がcloneメソッドに対して期待する動作はディープコピーであるため、Javaのcloneメソッドでは、不変オブジェクト(状態が変更されないオブジェクト)のみをシャローコピーとして提供し、可変オブジェクト(状態が変更されるオブジェクト)をディープコピーとして提供出来るように書き換える必要がある。
 

Javaのcloneメソッドの使い方

Cloneableインターフェースは、Javaの中では異端なインターフェースである。Cloneableインターフェースは、メソッドを含んでいないにも関わらず、Object.clone()のオーバーライドを必要とする(全てのクラスは暗黙的にObjectクラスを継承している)。逆に、Object.clone()は、継承したクラスがCloneableインターフェースを実装(implements)していなければ、実行できず、CloneNotSupportedExceptionをスローする。
 

cloneメソッドを実装する際のルール

  • Cloneableインターフェースを実装(implements)する。
  • Object.clone()をオーバーライドし、cloneメソッドをpublic、もしくはprotectedで宣言する。
  • cloneメソッドにより生成されたオブジェクトではコンストラクタが実行されない。
  • Cloneableインターフェースを実装するクラスのフィールドが、不変オブジェクト(プリミティブ型、String型、またはプリミティブ型のラッパークラス型)の場合は、cloneメソッドの中身はsuper.clone()を呼び出して、その戻り値を返すだけの単純な実装になる。
  • Cloneableインターフェースを実装するクラスのフィールドが可変オブジェクト(配列、コレクション、クラス型)を持つ場合は、そのフィールドを複製する実装をcloneメソッドに記述し、更にsuper.clone()を呼び出し、それぞれの戻り値を一緒に返す必要がある。

 
1つ目~3つ目のルールについては、書いてある通りなのでこれ以上の説明は省くとして、注目して欲しいので4つ目と5つ目である。
 
4つ目では、不変オブジェクトのみを扱う場合は、シャローコピーのみを行う。その理由は、不変オブジェクトは内部の状態が変わることが無いので、ディープコピーを行う必要が無いためである。
 
5つ目では、可変オブジェクトを扱う場合は、そのオブジェクトのディープコピーを行う。その理由は、可変オブジェクトをシャローコピーすると、コピー元のオブジェクトとコピー先のオブジェクトがメモリ上の同じデータを参照するため、予想が付かない振る舞いを引き起こしかねないからである。
 

cloneメソッドの実装例

クラスに不変オブジェクトしかない場合

public class DataBox implements Cloneable {
    private int historyNO = 0;
    private Integer value = 0;
    private String data   = "a";

    @Override
    public DataBox clone() {
        try {
            return (DataBox)super.clone();
        } catch() {
            throw new AssertionError();
        }
    }
}

 

クラスに可変オブジェクトがある場合

public class SmokyDog implements Cloneable {
    private String name = "SmokyDog";
    private Date birthDate;

    private setBirthDate(Date b) {
        this.birthDate = b;
    }

    @Override
    public SmokyDog clone() {
        try {
            SmokyDog result = (SmokyDog)super.clone();
            //ディープコピー
            result.setBirthDate(new Date(this.birthDate.getTime()));
            return result;
        } catch() {
            throw new AssertionError();
        }
    }
}

 
 
Javaでは、Cloneableを実装する以外にもオブジェクトのコピーを作成する方法がある。以下の2つがそうなのだが、長くなりそうなので今回は紹介だけに留める。

  • コピーコンストラクタ
  • コピーファクトリー

 
 
以上
 
 
参考
Object (Java Platform SE 8 )
書籍 Effective Java
最新Javaコーディング作法  プロが知るべき、107の規約と21の心得
Javaにおけるオブジェクトの複製(clone) – Object#cloneのオーバーライド
シャローコピーとディープコピーの違い – くろの雑記帳

Effective Javaを読んでいたところ、勉強になったことがあったのでメモしておく。
 

コンポジションとは

コンポジション(Composition)は、日本語で「混合物」を意味する単語である。あるクラスの機能を持つクラスのことを指す。
特定のクラスの機能を、自分が作るクラスにも持たせたい場合に、継承を使わずフィールドとしてそのクラスを持ち、そのクラスのメソッドを呼び出すメソッドを持たせること。そうすることで、クラスに他のクラスの機能を組み込むことができる。
厳密なオブジェクト指向では、継承は「機能の継承」を目的とせず、「スーパークラスはサブクラスの一種である」といういわゆる「is-a」の関係を持たなければならない。そのため、単に機能を持たせたい場合には、継承ではなくコンポジションとすることが推奨される。
 

継承とコンポジションをどう使い分けるか

継承を使ったほうが良い場合とコンポジションを使ったほうが良い場合があるが、具体的に何を基準にして使い分ければ良いだろうか。
この問題を解決するために、一般的な物の考え方の一つに「分類」と「分割」がある。
 
分類とは、A is a B.のことであり、日本語だと「AはBである」と表現できる。これをis-aの関係と呼ぶ。
例えば、「犬は動物である」「猫は動物である」「人間は動物である」・・・など、これらは分類で表現することが出来る。
is-aの関係で表せるものは、継承で表現するのが都合がいい。
 
分割とは、A has a B.のことであり、日本語だと「AはBを含んでいる」と表現できる。これをhas-aの関係と呼ぶ。
例えば、「パソコンはCPUを含んでいる」「学校は先生を含んでいる」「自転車はサドルを含んでいる」・・・など、これらは分割で表現することが出来る。
has-aの関係で表せるものは、コンポジションで表現するのが都合がいい。
 

継承とコンポジションの違い

ここで改めて、継承とコンポジションの違いを明確にするために、コードを例に挙げて説明する。
まず、親となるPersonクラスを定義する。Personクラスは「氏名」と「年齢」を保持し、要求に応じてそれを返す。

public class Person {
    private String name;
    private int age;

    public Person(String n, int a) {
        name = n;
        age  = a;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

 
ここでPersonクラスを元にして、新たに「学年(grade)」を保持し、要求に応じてそれを返すことが出来るクラスを作りたい場合を考えてみる。
以下より、継承とコンポジション、それぞれを使用した場合の違いをコードで説明する。
 

継承を使った場合のコード

public class Student extends Person {
    private int grade;

    public Student(String n, int a, int g) {
        super(n, a);
        grade = g;
    }

    public int getGrade() {
        return grade;
    }
}
Student yamada = new Student("山田", 20, 2);
String name = yamada.getName();
int age     = yamada.getAge();
int grade   = yamada.getGrade();

 

コンポジションを使った場合のコード

public class Student {
    private Person person;
    private int grade;

    public Student(Person p, int g) {
        person = p;
        grade  = g;
    }

    public String getName() {
        return person.getName();
    }

    public int getAge() {
        return person.getAge();
    }

    public int getGrade() {
        return grade;
    }
}
Person y       = new Person("山田", 20, 2);
Student yamada = new Student(y, 2);
String name = yamada.getName();
int age     = yamada.getAge();
int grade   = yamada.getGrade();

 
上記の例では、継承とコンポジション、どちらを使っても自然に記述することが可能だが「継承とコンポジションをどう使い分けるか」の項目で説明した通り、どちらか一方では上手く記述できない場合がある。そういったときは、一度立ち止まってis-aとhas-aのどちらに当てはまるかを考えてみることが必要だ。
 
書籍「Effective Java」の「項目16 継承よりコンポジションを選ぶ」には、今回載せたコンポジションのコードよりも頑強な設計のコードが載っている。しかし、少々理解し辛い部分があったので、富士通さんの記事に載っていたコードを引用させていただいて説明した。気になった方はEffective Javaを読んでみると良いと思う。
 
 
以上
 
 
参考
書籍 Effective Java
コンポジションとは : JavaA2Z
オブジェクト指向プログラミングへの道 5日目:オブジェクトコンポジション : 富士通
オブジェクト思考: is-a関係とhas-a関係: 継承と包含
is-a – Wikipedia
has-a – Wikipedia