ジェネリック型

以下のような例を考える。

class Bot {
    public Object echo(Object obj) {
        return obj;
    }
}

しかし、以下のように戻り値がString型と分かっているような場合、毎回キャストするのは非常に手間だしコストにもなる。

Bot bot = new Bot();
String str = (String) bot.echo("Hello");// キャストする必要がある

型安全でなく意図した型と違う型が帰ってくる可能性もある。そこで以下のようにジェネリック型を用いるとスマートになる。

class Bot<T> {
    public <T> echo(T obj) {
        return obj;
    }
}
Bot<String> bot = new Bot<String>();
String str = bot.echo("Hello");

■例

以下のような場合、Eは型変数と呼ばれる。

class Cell<E> {
    private Cell<E> next;
    private E element;
    public Cell(E element) {
        this.element = element;
    }
    public Cell(E element, Cell<E> next) {
        this.element = element;
        this.next    = next;
    }
    public E getElement() {
        return element;
    }
    public void setElement(E element) {
        this.element = element;
    }
    public Cell<E> getNext() {
        return next;
    }
    public void setNext(Cell<E> next) {
        this.next = next;
    }
}

型変換について

慣例として以下のような法則で用いられる。

要素
E – element
キー
K – key
V – value
一般的な型
T – type

以下のように型の情報は2箇所必要になる。

Cell<String> cell = new Cell<String>("Hello");

但し、Java 7では以下のように省略することができる。

Cell<String> cell = new Cell<>("Hello");

Java 7について

switch

以下のように文字列が条件判定に使えるようになった。

switch(str) {
    case "hoge": hoge();
    case "fuga": fuga();
    case "piyo": piyo();
}
マルチキャッチ

以下のように同時に複数の例外をキャッチできる。

try {
    // code
}
catch(IOException | SQLException e) {

}
class SingleLinkQueue<E> {
    protected Cell<E> head;
    protected Cell<E> tail;
    public void add(E item) {
        Cell<E> cell = new Cell<E>(item);
        if(tail == null) {
            head = tail = cell;
        }
        else {
            tail.setNext(cell);
            tail = cell;
        }
    }
    public E remove() {
        if(head == null) {
            return null;
        }
        Cell<E> cell = head;
        head = head.getNext();
        if(head == null) {
            tail = null;
            return cell.getElement();
        }
    }
}

以下のようなクライアントコードとなる。

SingleLinkQueue<String> queue = new SingleLinkQueue<String>();
queue.add("Hello");
queue.add("World");

以下のようにするとコンパイルエラーとなる。

SingleLinkQueue<String> queue = new SingleLinkQueue<String>();
queue.add(1);

■ジェネリック型宣言

class Cell<E> {}

以下のように複数の型パラメータを含むことができる。

interface Map<K, V> {}

以下の2つはコンパイラに対して別のクラスを生成するように指示しているのではなくあくまでも同じクラスであり2つのジェネリック型の呼び出しで、2つの異なるオブジェクトに対して2つの異なる方法で適用されるに過ぎない。

Cell<String> strCell;
Cell<Number> numCell;

以下のようにクラスは”Cell”だけであり<String>や<Integer>はクラス定義ではなくオブジェクトの型についての情報を宣言しているだけである。

Cell<String> strCell  = new Cell<String>("Hello");
Cell<Integer> intCell = new Cell<Integer>(25);
boolean same = (strCell.getClass() == intCell.getClass());

制限

型パラメータをEとすると以下の場所ではEを使用することはできない。

  • staticフィールド
  • staticメソッド内
  • static初期化子内

Cell<String>やCell<Integer>は1つのクラス=>任意のstaticフィールドを共有する=>パラメータによって別の型を指定される=>任意のstaticフィールドは共有できず別のフィールドとなる=>矛盾

配列の生成にはコンパイラがどのようなコードを生成するか知っている必要があるので、型パラメータEをインスタンス化したり、Eの配列を生成したりはできない。

■境界のある型パラメータ

interface SortedCollection<E extends Comparable<E>> {
}

■ネストしたしたジェネリック型

class SingleLinkQueue<E> {
    static class Cell<E> {
        private Cell<E> next;
        private E element;
        /* ... */
    }
    protected Cell<E> head;
    protected Cell<E> tail;
    /* ... */
}

以下のように内部クラスである場合、直接型変数にアクセス可能である。

class SingleLinkQueue<E> {
    class Cell {
        private Cell<E> next;
        private E element;
        /* ... */
    }
    protected Cell<E> head;
    protected Cell<E> tail;
    /* ... */
}

この場合、内部クラスをジェネリック型にすると外部の型変換は隠蔽されるので好ましくない。

■サブタイプとワイルドカード

static double sum(List<Number> list) {
    double sum = 0.0;
    for(Number n : list) {
        sum += n.doubleValue();
    }
    return sum;
}

IntegerはNumberのサブタイプであるがList<Integer>はList<Number>のサブタイプではないので以下のように記述することはできない。

double sum = sum(new ArrayList<Integer>());

以下のようにすることで、この問題を解決できる。

static double sum(List<? extends Number> list) {
    double sum = 0.0;
    for(Number n : list) {
        sum += n.doubleValue();
    }
    return sum;
}

?は型引数の境界ワイルドカードでありNumberは上限境界である。

下限境界ワイルドカード

以下のようにすると?はIntegerもしくはそのsuperクラスである。

List<? super Integer>

非境界ワイルドカード

List<?>
List<? extends Object>

ワイルドカードの制限

以下のように型が分かっていないとできないことはできない。

SingleLinkQueue<?> strings = new SingleLinkQueue<String>();
strings.add("Hello");

一方で下限境界ワイルドカードでは問題ない。

static void addString(SingleLinkQueue<? super String> sq) {
    sq.add("Hello");
}

■ジェネリックメソッドとジェネリックコンストラクタ

以下のように記述した場合、SingleLinkQueue<String>のインスタンスに対して、E[]はStringの配列となるが、Objectの配列に格納することはできない。

public E[] toArray(E[] ary) {
    int i = 0;
    for(Cell<E> c = head; c != null && i < ary.length; c = c.getNext()) {
        ary[i++] = c.getElement();
    }
    return ary;
}

以下のようにすることで解決できる。

public <T> T[] toArray(T[] ary) {
    Object[] tmp = ary;
    int i = 0;
    for(Cell<E> c = head; c != null && i < ary.length; c = c.getNext()) {
        tmp[i++] = c.getElement();
    }
    return ary;
}

■ジェネリック呼び出しと型推論

以下のようにメソッドを定義する。

<T> T passThrough(T obj) {
    return obj;
}

以下のようにして呼び出すことができる。

String str1 = "Hello";
String str2 = this.<String>passThrough(str1);

ちなみに以下のようにメソッド名だけを使用する呼び出しはできない。

String str1 = "Hello";
String str2 = <String>passThrough(str1);

メソッド名は適切に修飾する必要がある。

しかし、以下のようにして型の記述を省略することができる。

String str1 = "Hello";
String str2 = this.passThrough(str1);

これが型推論である。

String str1 = "Hello";
Object o1 = passThrough(s1);// T => String
Object o2 = passThrough((Object) s1);// T => Object

■ワイルドカードキャプチャ

以下のように記述するとコンパイルすることはできない。

SingleLinkQueue<?> strings = new SingleLinkQueue<String>();
strings.add("Hello");

SingleLinkQueueの推定される型は<capture of ?>である。従って、addへのパラメータも<capture of ?>であるがStringと互換性はないのでエラーとなる。

■イレイジャと原型

コンパイラがコンパイルされたクラスから全てのジェネリック型の情報を消す処理をイレイジャという。ジェネリック型あるいはパラメータ化された型のイレイジャは飾りのない型名で原型と呼ばれる。型変数のイレイジャは最初の上限境界となる。

オーバーロードとオーバーライド

以下のように記述すると同じシグネチャをもつメソッドとみなされるために、オーバーロードすることはできない。

public class Sample<E> {
    void m(E t){}
    void m(Object t){}
}

以下のようにしてオーバーライドすることができる。

public class Sample<E> {
    void m(E t){}
    class Hello extends Sample<Object> {
        @Override
        void m(Object t){}
    }
}

一見すると同じシグネチャかどうか見分けにくいので注意が必要である。

■クラス拡張とジェネリック型

以下のようにすることはできる。

class RemoteEventService extends AbstractEventService {}
class LocalEventService<T extends Event> extends AbstractEventService {}

しかし、以下のように記述するとすることはできない。Comparable<Value>とComparable<ExtendedValue>を実装することになりコンパイルすることはできない。

class Value implements Comparable<Value> {}
class ExtendedValue extends Value implements Comparable<ExtendedValue> {}

アーカイブをのぞいてみる

以下のようなファイルが存在するとする。

lib.a

以下のコマンドを実行し元のファイル名に分解できる。

ar -t lib.a

但しファイルによっては以下のように表示される。

ar: lib.a is a fat file (use libtool(1) or lipo(1) and ar(1) on it)

アーカイブには複数のアーキテクチャ用のコードが含まれているためであり、以下のコマンドを実行して単一のアーキテクチャのファイルを抽出した後、arコマンドを実行する。

lipo lib.a -thin armv6 -output libarm6.a
ar -t libarm6.a

gitignoreのテンプレート

■CakePHPのプロジェクト

色々付け足して以下のようになった。

.gitignore

# Windows
Thumbs.db

# MAC
.DS_Store

# Eclipse
/.cache
.project
/.settings
.buildpath

# Netbeans
nbproject/*

# CakePHP tmp files
/app/tmp/*
!/app/tmp/

/app/tmp/cache/*
!/app/tmp/cache/
!/app/tmp/cache/empty

/app/tmp/cache/models/*
!/app/tmp/cache/models/
!/app/tmp/cache/models/empty

/app/tmp/cache/persistent/*
!/app/tmp/cache/persistent/
!/app/tmp/cache/persistent/empty

/app/tmp/cache/views/*
!/app/tmp/cache/views/
!/app/tmp/cache/views/empty

/app/tmp/logs/*
!/app/tmp/logs/
!/app/tmp/logs/empty

/app/tmp/sessions/*
!/app/tmp/sessions/
!/app/tmp/sessions/empty

/app/tmp/tests/*
!/app/tmp/tests/
!/app/tmp/tests/empty

■XcodeでiPhoneアプリ開発

.gitignore

build/*
*.pbxuser

# MAC
.DS_Store

.gitattribute

*.pbxproj -crlf -diff -merge

-crlf -diffはバイナリファイルとして扱う指定である。

This will mean…

  • all files with a .foo extension will not have carriage return/line feed translations done, won’t be diffed
  • merges will result in conflicts leaving the original file untouched.

マージしようとするとオリジナルファイルが外されるとのこと。

■eclipseでandroidアプリ開発

.gitignore

bin/*
dist/*
gen/*

再帰的にimportをつかう

再帰インポートのお話。

■サンプルコード

便宜上ちょっと無茶なコードです。。。

#import <Foundation/Foundation.h>
#import "Child.h"
@interface Parent : NSObject {

}
@end

以下のようにして継承する。

#import <Foundation/Foundation.h>
#import "Parent.h"
@interface Child : Parent {

}
@end

ビルドしようとすると以下のエラーが表示される。

Cannot find interface declaration for ‘Parent’, superclass of ‘Child’

■解決策

#import <Foundation/Foundation.h>
@interface Parent : NSObject {

}
@end
#import "Child.h"
#import <Foundation/Foundation.h>
#import "Parent.h"
@interface Child : Parent {

}
@end

その場しのぎの策に走ってました。ちゃんとやるんだったら以下のように設計を改める。

#import <Foundation/Foundation.h>
@interface Parent : NSObject {

}
@end
#import <Foundation/Foundation.h>
#import "Parent.h"
@interface Child1 : Parent {

}
@end
#import <Foundation/Foundation.h>
#import "Parent.h"
@interface Child2 : Parent {

}
@end

audio APIを使ってみる

HTML5の要素の一つとして新しくaudio要素が加わった。このaudio要素を使用することでプラグインを使用することなく音源を再生することができる。(●´ω`●)

■HTML

以下のようにしてaudio要素を使用できる。

<audio src="http://upload.wikimedia.org/wikipedia/commons/0/03/262Hz.ogg" controls></audio>

また以下のようにしてaudio要素に対応していないブラウザに対しての表示を行うことができる。

<audio src="http://upload.wikimedia.org/wikipedia/commons/0/03/262Hz.ogg" controls>
最新のブラウザを使ってくれよな
</audio>

以下のような表示となる。

ogg形式に対応していないブラウザ用にmp3の音源を使用してみた。

PCブラウザの対応状況

Chrome 9
両方再生可能だ
Firefox 3.6
MP3形式の音源が再生できない
IE 9
MP3形式のみ再生が可能

スマートフォンブラウザの対応状況

iPhone4のsafari
audio要素を理解できるが、ogg形式の音源を再生することはできず、MP3は再生することができた
android 2.1・2.2
audio要素を理解することができなかった。androidのOpera mobileでも再生することはできない
android 3.0
エミュレータ上ではあるが両方再生可能

androidのブラウザには本当に頑張ってほしい限りだ。(☍﹏⁰)

■JavaScript

以下のようにJavaScriptだけで音源を再生することもできる。

var audio = new Audio('http://upload.wikimedia.org/wikipedia/commons/0/03/262Hz.ogg');
audio.play();

再生時間の表示

以下のようにして再生時間を表示することができる。

var audio = new Audio('http://upload.wikimedia.org/wikipedia/commons/0/03/262Hz.ogg');
audio.addEventListener(
    'timeupdate',
    function(){
        document.getElementById('content').innerHTML = audio.currentTime;
    },
    false
);
audio.play();

以下のようにaudio要素をcreateElementした場合、そのオブジェクトに対して上述と同様のメソッドが使用できる。

var audio = document.createElement('audio');
audio.setAttribute('src', 'http://upload.wikimedia.org/wikipedia/commons/0/03/262Hz.ogg');
audio.setAttribute('controls', 'true');
document.getElementById('content').appendChild(audio);
audio.play();

■参考

audio – MDC Doc Center

GNU helloをビルドする

wget http://mirrors.kernel.org/gnu/hello/hello-2.7.tar.gz
tar xvzf hello-2.7.tar.gz
cd hello-2.7
./configure --help=short

以下のように表示される。

Configuration of GNU Hello 2.7:

Optional Features:
  --disable-option-checking  ignore unrecognized --enable/--with options
  --disable-FEATURE       do not include FEATURE (same as --enable-FEATURE=no)
  --enable-FEATURE[=ARG]  include FEATURE [ARG=yes]
  --disable-dependency-tracking  speeds up one-time build
  --enable-dependency-tracking   do not reject slow dependency extractors
  --disable-nls           do not use Native Language Support
  --disable-rpath         do not hardcode runtime library paths

Optional Packages:
  --with-PACKAGE[=ARG]    use PACKAGE [ARG=yes]
  --without-PACKAGE       do not use PACKAGE (same as --with-PACKAGE=no)
  --with-gnu-ld           assume the C compiler uses GNU ld default=no
  --with-libiconv-prefix[=DIR]  search for libiconv in DIR/include and DIR/lib
  --without-libiconv-prefix     don't search for libiconv in includedir and libdir
  --with-libintl-prefix[=DIR]  search for libintl in DIR/include and DIR/lib
  --without-libintl-prefix     don't search for libintl in includedir and libdir

Some influential environment variables:
  CC          C compiler command
  CFLAGS      C compiler flags
  LDFLAGS     linker flags, e.g. -L<lib dir> if you have libraries in a
              nonstandard directory <lib dir>
  LIBS        libraries to pass to the linker, e.g. -l<library>
  CPPFLAGS    (Objective) C/C++ preprocessor flags, e.g. -I<include dir> if
              you have headers in a nonstandard directory <include dir>
  CPP         C preprocessor

Use these variables to override the choices made by `configure' or to help
it to find libraries and programs with nonstandard names/locations.

Report bugs to <bug-hello@gnu.org>.
GNU Hello home page: <http://www.gnu.org/software/hello/>.
General help using GNU software: <http://www.gnu.org/gethelp/>.

ふむふむ。

./configure --prefix=/opt/hello-2.7
make
sudo make install

■実行

以下のコマンドを実行する。

hello

以下のように表示される。

Hello, world!

以下のコマンドを実行する。

env LANG=ja_JP.UTF-8 hello

以下のように表示される。

世界よ、こんにちは!