ページ

2014年10月21日

Javaとか

原稿の残りの部分。

コレクションAPI

Iterableインタフェースを実装してみよう

前節でIterableインタフェースを実装したオブジェクトであれば拡張forループで回すことができると書きました。つまり、自作のクラスでもIterableインタフェースを実装していれば、拡張forループに対応できます。
例えば、リストの要素をループで回すとき、一緒にインデックス番号を取得して処理したいケースがあります。インデックスアクセスをする場合は、次のように記述します。
List list = Arrays.asList("foo", "bar", "buzz");
for (int i=0; i
このコードを拡張forループで記述すると次のようになります。
List list = Arrays.asList("foo", "bar", "buzz");
int index=0;  // (1)
for (String str: list) {
    System.out.println(index + " " + str);
    index++; // (2)
}
(1)でインデックス番号を保持する変数を宣言して、(2)でインデックス番号をインクリメントしています。せっかく、拡張forループで簡潔に記 述できるようになったのに、インデックスでのアクセスのようにインデックス番号をまた管理しなければならないのは煩雑です。イテレータでループするとき に、インデックス番号も合わせて管理できれば便利じゃないですか?
ここでは、インデックス番号とリストの要素を管理できるイテレータを作ってみましょう。利用方法は次のようになります。
List list = Arrays.asList("foo", "bar", "buzz");
for (CollectionUtils.Entry entry: CollectionUtils.enumurate(list)) { // (1)
    System.out.println(entry.getIndex() + " " + entry.getValue()); // (2)
}
今回はCollectionUtilsというクラスを作成します。(1)のようにCollectionUtilsのenumurateメソッドに Iterableなオブジェクトを渡すと、インデックス番号と要素を管理するIterableなオブジェクトを返します。このIterableなオブジェ クトは、CollectionUtils.Entryオブジェクトを保持します。(2)ではentryからインデックス番号と要素の値を取り出して出力し ています。
それでは、ちょっと長いですが、このCollectonUtilsを実装したクラスが次のコードです。
import java.util.*;

public class CollectionUtil {
    // (1) Entryクラス宣言
    public static class Entry {
        int index;
        T value;
        Entry(int index, T value) {
            this.index = index;
            this.value = value;
        }

    public int getIndex() { return index; }
    public T getValue() { return value; }
     }

    // (2) enumurateメソッド
    public static  Iterable> enumurate(Iterable iterable) {
        final Iterator iter = iterable.iterator(); // (3)
        return new Iterable>() {  // (4)
            @Override
            public Iterator> iterator() { // (5)
                return new Iterator>() { // (6)
                    int index = 0; // (7)
                    @Override
                    public boolean hasNext() {  //(8)
                        return iter.hasNext();
                    }
            
                    @Override
                    public void remove() { // (9)
                        iter.remove();
                    }

                    @Override
                    public Entry next() { // (10)
                        return new  Entry(index++, iter.next());
                    } 
                };
            }
        };
}
(1)はEntryクラスの実装です。Entryクラスはインデックス番号(index)と値(value)を保持するバリューオブジェクトです。 (2)はIterableなオブジェクトを返すenumurateメソッドです。引数にはIterableなオブジェクトを受け取ります。上の例では Listオブジェクトを指定しましたが、イテレーション可能なオブジェクトであればすべて、インデックス番号付きのイテレータに変換できます。 (3)では、引数で受け取ったIterableなオブジェクトからイテレータを取得しています。(4)では、新たに新しいIterableなオブジェクト を返しています。ここでは、無名クラスとしてIterableなオブジェクトを宣言しています。
IterableインターフェースはIterator> iterator()を実装します(5)。このメソッドはIteratorを返すメソッドです。(6)でIteratorオブジェクトを無名クラスとして 宣言して返しています。この無名クラスは、内部データとしてインデックス番号を保持して(7)、enumurateメソッド内で宣言したiter変数 (Iteratorオブジェクト)を参照します。引数で渡されたイテレータと、新たに作成したイテレータの2つを同時に扱うので、少し複雑ですね。 Iteratorインターフェースは、hasNext, remove, nextの3つのメソッドを実装する必要があります((8),(9)(10))。(8)はhasNextを、(9)はremoveメソッドを実装していま す。それぞれ、(3)で設定したiter変数のhasNextとremoveメソッドを呼び出しているだけです。 (10)のnextメソッドでは、次のイテレータの要素として(1)で宣言したEntryをインデックスとオリジナルのイテレータの値 (iter.next())でインスタンス化して返しています。
これでインデックス番号と値を同時に管理できる汎用的なイテレータを利用できるようになりました。


例外処理

プログラムの実行中に異常事態が発生した場合に、Javaでは例外オブジェクトをメソッドの呼び出し元に送出します。例外オブジェクトはすべて、 Throwableクラスを継承したオブジェクトです。Throwableオブジェクトをthrow文で投げることによって、呼び出し元でエラー処理でき ます。
Throwable
  +- Exception
        +- RuntimeException
        +- RuntimeException以外の例外クラス
  +- Error
例外は実行時例外と検査例外の2つに分類されます。実行時例外は、RuntimeExceptionクラスとErrorクラス、また、それらの派生 クラスです。 Errorクラスとその派生クラスは、メモリ不足の時に発生するOutOfMemoryErrorなどJavaのVMが発生させるエラーです。深刻なエ ラーのため、通常はこのエラーはキャッチすべきではありません。 RuntimeExceptionは、配列で範囲外のインデックス値を指定した時に発生するIndexOutOfBoundsExceptionやメソッ ド呼び出しで変数がnull参照している時に発生するNullPointerExceptionなど、アプリケーションの通常の処理で発生する例外です。 この種の例外は通常はプログラム中で事前に値の検証を行って、例外を発生させないのが良いとされています。 これら2種類の実行時例外は、次に説明する検査例外と違いメソッドが送出する例外をメソッドシグネチャに含める必要はありません。
検査例外は次のようにメソッドシグネチャにthrowsに続けて呼び出し元に送出できる例外を指定します。呼び出し元では、try ... catch説で送出された例外を捕捉するか、さらにその呼び出し元に例外を送出する必要があります。
public void method() throws 例外1, 例外2 {
実行時例外と検査例外の使い分けとして、Effective Javaでは「 回復可能な条件にはチェック例外を、プログラミング・エラーにはランタイム例外を使う」と規定しています。しかし、最近は検査例外の弊害として
  • チェック例外が、実装の詳細を不用意にさらけ出している
  • 不安定なメソッド・シグニチャー
  • 読みとれないコード
  • 例外の飲み込み
  • 例外ラッピングが多すぎる
と批判して実行時例外を推奨しているフレームワークも存在します。例外についての議論はdeveloperWorksの「Javaの理論と実践: 例外をめぐる議論」( http://www.ibm.com/developerworks/jp/java/library/j-jtp05254/ )で詳しく議論されています。

try ... catch節

Javaの例外の補足はtry ... catch節を使います。catchブロックは次のように複数記述して、複数の例外を捕捉できます。finallyブロックは、例外が発生してもしなくても、常に実行されます。
少し長くなりますが、次のコードは例外補足のサンプルです。(2)のメソッドthrowExceptionでは、引数lineが文字 列"exception"の場合に、(1)で定義した例外MyExceptionを送出しています。(3)のmainメソッドの中で、ファイル in.txtを開いて((4))、(5)で一行ごとに読み込んでいます。この時、例外IOExceptionが発生する可能性があるため、(7)で補足し ています。また、このwhileループの中で(2)のthrowExceptionメソッドを呼び出しているので、例外MyExceptionが発生する 可能性があります。そのため、(8)で例外MyExceptionを補足しています。 (4)でReaderを開いているため、後片付けとして開いたreaderオブジェクトを閉じる必要があります。IOExceptionや MyExceptionが発生しても、開いたreaderオブジェクトはcloseする必要があるため、(9)のfinallyブロックでreaderオ ブジェクトのcloseメソッドを呼び出して、閉じます((10))。
import java.util.*;
import java.io.*;
  
    
public class MyTest {
    // (1)
    static class MyException extends Exception {}

    // (2)
    static void throwException(String line) throws MyException {
        if (line.equals("exception")) {
            throw new MyException();
        }
    }

    // (3)
    public static void main(String[] argv) {
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new FileReader("in.txt")); // (4)
            String line;
            while ((line = reader.readLine()) != null ) { // (5)
                System.out.println(line);
                throwException(line); // (6)
            }
        } catch (IOException e) { // (7)
            e.printStackTrace();
        } catch (MyException e) { // (8)
            e.printStackTrace();
        } finally {  // (9)
            if (reader != null) {
                try {
                    reader.close(); // (10)
                } catch (IOException e) {
                }
            }
        }
    }
}
もう一度上のコードを見てくだだい。(7)と(8)の例外発生時のコードは、スタックトレースを出力するだけの全く同じコードです。実際のアプリ ケーションでも、複数の例外の対応で同じ処理をすることも多いです。Java8では、例外クラスを|でつなげることで、複数の例外をまとめて一つの catchブロックで捕捉できます。上のmainメエソッドの例外処理をJava8の記法で記述すると次のようになります(1)。
public static void main(String[] argv) {
    BufferedReader reader = null;
    try {
        : 省略
        }
    } catch (IOException | MyException e) { // (1)
        e.printStackTrace();
    } finally {
        if (reader != null) {
            try {
                reader.close();
            } catch (IOException e) {
            }
        }
    }
}

try-with-resources文

前節のBufferedReaderのように、後処理として必ず閉じる必要があるオブジェクトがあります。そのため、finallyブロックで常に オブジェクトのnullチェックを行って、closeメソッドを呼び出すことは煩雑で、しばしば閉じ忘れが発生します。Java7からはtry- with-resources文が導入され、閉じ忘れが発生しないようになりました。
前節のサンプルコードをtry-with-resources文を使って書き換えてみましょう。
public static void main(String[] argv) {
    try (BufferedReader reader = new BufferedReader(new FileReader("in.txt"))){ // (1)
        String line;
        while ((line = reader.readLine()) != null ) {
            System.out.println(line);
            throwException(line);
        }
    } catch (IOException | MyException e) { // (2)
        e.printStackTrace();
    }
        }
    }
}
以前のコードではBufferedReaderの宣言がtry文の外にあり、変数のスコープが大きくなっていました。try-with- resources文を使うことで、BufferedReaderのスコープがtryブロックの中に限定され、コードがシンプルになります。 (1)はtry-with-resources文です。try-with-resources文では、tryのあとの()内で閉じる必要のあるオブジェク トを作成します。ここで作成したオブジェクトは、tryブロックを抜ける時に自動でcloseメソッドが呼ばれます。このため、前節のような finallyブロックが不要になります。 (2)では、catchブロックで通常のtryブロックの中で発生する例外を補足して処理できます。
上の例ではtry-with-resources文の中で、自動的に閉じられるオブジェクトはひとつだけです。次の例のように、複数個のオブジェクトを自動で閉じる場合は、「;」で連結して記述します((1))。
public static void main(String[] argv) {
    // (1)
    try (
        BufferedReader reader = new BufferedReader(new FileReader("in.txt"));
        BufferedWriter writer = new BufferedWriter(new FileWriter("out.txt"))
      ) {
        String line;
        while ((line = reader.readLine()) != null ) {
            writer.write(line);
            writer.newLine();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

AutoCloseableインタフェース

try-with-resources文のリソースとして使用できるクラスは、java.io.AutoCloseableインターフェースを実装 したクラスです。前節のBufferedReaderやBufferedWriterは、AutoCloseableインターフェースのサブインタフェー スのCloseableを実装しています。
AutoCloseable(または、Closeable)インタフェースは、次のcloseメソッドがひとつだけ定義されています。
  void close throws IOException
それでは、ストリームから一行ごとに読み込んで、各行を「,」で分割した文字列の配列を管理するCsvReaderクラスを作ってみましょう。 (1)は、CsvReaderのコンストラクタです。コンストラクタの中でBufferedReaderオブジェクトを作成しています。(2)の readLineメソッでは一行読み込んで、「,」で分割しています。(3)は、AutoCloseableのcloseメソッドの実装です。このメソッ ドでは、(1)で作成したBufferedReaderオブジェクトのcloseメソッドを読み込んでいます。
public class CsvReader implements AutoCloseable {
    private BufferedReader reader;
    public CsvReader(Reader reader) { // (1)
        this.reader = new BufferedReader(reader);
    }
    public String[] readLine() { // (2)
        String line = reader.readLine();
        return line.split(",");
    }

    @Override
    public void close() throws IOException { // (3)
        System.out.println("closing...");
        reader.close();
    }
}
次のコードは、このクラスを実際に使用しています。AutoCloseableインタフェースを実装しているので、tryブロックを抜けるときに自動的にCsvReaderのcloseが呼ばれます。
try (CsvReader csv = new CsvReader(new FileReader("test.csv"))) {
    String[] fields;
    while ((fields = csv.readLine()) != null) {
        System.out.println(fields);
    }
}

クラス定義

初期のJavaではクラスの定義は、一つのソースファイルに一つのクラスしか作成できませんでした。現在でもpublicなトップレベルクラスにはその制約はありますが、内部クラスやプライベートなクラスは、一つのソースファイルに複数記述できます。

パッケージスコープのトップレベルクラス

次の例のように、一つのソースコードPublicClass.javaファイルに、publicなクラスPublicClassとパッケージスコー プのクラスPrivateClassの2つのクラスを定義できます。パッケージスコープのクラスは、この複数定義できます。このファイルをコンパイルする と、PrivateClass.classとPublicClass.classの2つのクラスファイルが生成されます。つまり、 PublicClass.javaと一緒にPrivateClass.javaファイルで別途PrivateClassクラスを定義できません。
```java:プライベートなトップレベルクラス(PublicClass.java) class PrivateClass { // (1) }
public class PublicClass { // (2) } ```

ネストしたクラス

クラスの中にさらにクラスを定義できます。クラスの中のクラスを、ネストしたクラスと呼びます。次のコードはネストしたクラスを定義しています。 (1)はトップレベルのクラスOuterで、(2)のInnerクラスがネストしたクラスになります。ネストしたクラスは、ここではpublicスコープ ですが、スコープの範囲に応じて、privateスコープやパッケージスコープやprotectedスコープで定義できます。ネストしたクラスではクラス にstaticをつけます。staticをつける理由は、次の内部クラスで説明します。 このクラスをコンパイルすると、Outer.classとOuter$Inner.classの2つが生成されます。
public class Outer { // (1)
    public static class Inner { // (2)
    }
}

内部クラス

内部クラスは、ネストしたクラスと同様に、クラスの中にクラスを定義できます。しかし、ネストしたクラスと違い、内部クラスは外部クラスのインスタ ンスに紐付いています。ネストしたクラスを利用する場合は、new Outer.Inner()としてインスタンス化できますが、内部クラスでは外部クラスをインスタンス化して、そのインスタンスから内部クラスをインスタ ンス化します。
次のコードは内部クラスの例です。(2)で内部クラスを定義しています。内部クラスはネストしたクラスと違い、staticは付けないこと注意して ください。(3)は、内部クラスInnerのオブジェクトから、外部クラスのオブジェクトのフィールドにアクセスしています。これはネストしたクラスと違 い、外部クラスのオブジェクトに紐付けられているために可能になります。ここでは直接変数を指定していますが、外部クラスの変数を明示するために Outer.this.strと指定することもできます。 (4)では、外部クラスのメソッドから内部クラスをインスタンス化しています。 このファイルをコンパイルするとOuter.classとOuter$Inner.classの2つのファイルが生成されます。
public class Outer {
    private String str = "outer value"; // (1)
    public class Inner { // (2)
        public void print() { // (3)
            System.out.println(str);
        }
    }
    public void method() {
        Inner inner = new Inner(); // (4)
    }
}

ローカルクラス

クラスの中にクラスを定義できたように、メソッドの中にもクラスを定義できます。メソッドの中に定義したクラスは、クラスを定義したスコープに依存 します。 次のコードは、メソッドの中にローカルクラスを定義しています。(1)と(4)は実際にローカルクラスLocalClassを定義しています。同じ名前の クラスですが、それぞれ別のクラスとして認識されます。このファイルコンパイルすると、Outer.classと Outer$1LocalClass.classとOuter$2LocalClass.classの3つができていることからも、それらは別のクラスだ とわかります。(2)と(6)では、それぞれ(1)と(3)で定義したクラスをインスタンス化しています。 ローカルクラスからは、メソッド内で定義した変数にアクセスできます。アクセスできるのはfinal定義された変数だけです。そのため、メソッド内の変数 の値を変更することはできません。(5)のLocalClassのprintメソッドでは、メソッドのfinalで定義されたstr変数にアクセスしてい ます。final指定されていない変数にアクセスしようとすると、コンパイルエラーが発生します。
public class Outer {
    public void method1() {
        class LocalClass {}; // (1)
        LocalClass lc = new LocalClass() // (2)
    }
    public void method2() {
        final String str = "method2"; // (3)
        class LocalClass { // (4)
            public void print() { // (5)
                System.out.println(str);
            }
        }
        LocalClass lc = new LocalClass(); // (6)
        lc.print();
    }
}

無名クラス

無名クラスは匿名クラスとも呼ばれています。無名クラスは、インターフェースや抽象クラスを利用するときに、名前やクラス定義をせずに直接インスタ ンス化して、その実装を書くものです。メソッド内や、クラスフィールドのインスタンス化時に利用します。名前付きのクラスと違い、クラスのインスタンス化 を一箇所からしか行わない場合に無名クラスを利用することが多いです。 無名クラスもローカルクラスと同様にfinalで定義された外部の変数にアクセスできます。
次のコードは、java.lang.Runnableを無名クラスでインスタンス化したものです。(1)でRunnableインタフェースをインス タンス化しています。new Runnable()に続いて{}のブロック内で、Runnableインタフェースが持つrunメソッドを実装しています(3)。無名クラスはコンストラ クタを持てませんが、フィールドに初期値を設定できます(2)。また、ローカルクラスと同様に、finalで定義された変数にはアクセスできます。 (4)の(1)から始まった{}ブロックの最後に「;」を忘れないでください。
Runnable runnable = new Runnable() { // (1)
    String str = "running"; // (2)
    @Override
    public void run() { // (3)
        System.out.println(str);
    }
}; // (4)
new Thread(runnable).start();

可変長引数

可変長引数は、引数の数を複数指定でき、実際の引数の数はその呼び出し元によってきまります。

基本的な利用例

java.lang.Stringクラスのformatメソッドではpublic String format(String fmt, Object... args)のように定義されています。可変長引数は、引数の型宣言のあとに「...」を追加します。可変長引数を指定できるのは、一番最後の引数だけで す。 Stringクラスのformatメソッドは次のように利用します。
str = String.format("out: nothing"); // (1) "out: nothing"と出力
str = String.format("out: %d", 1); // (2) "out: 1"と出力
str = String.format("out: %d %s", 1, "foo"); // (3) "out: 1 foo"と出力
(1)はフォーマット用の文字列だけとり、可変長引数を取らないケースです。(2)は可変長引数に「1」を指定しています。(3)は可変長引数とし て「1」と「foo」の2つを指定しています。引数の数に関わらず、呼び出されるformatメソッドはひとつだけで、オーバロードされていません。

メソッドから使用例

可変長引数はメソッド内では配列として扱います。次のコードは可変長引数を出力するメソッドです。 (1)でargvを可変長引数としてとります。(2)で可変長引数を直接出力しています。[Ljava.lang.String;@15db9742のよ うに出力されます。これはargvがStringの配列を意味しています。(3)は可変長引数を拡張forループで回して、内容を出力しています。
public void print(String... argv) { // (1)
    System.out.println(argv); // (2)
    for (String s: argv) { // (3)
        System.out.println(s);
    }
}
上のメソッドは、メソッドシグネチャがpublic void print(String[] argv)とほぼ、同義です。しかし、配列で指定した場合は、argvがnullのケースがありますが、可変長引数では、必ず非null値になります。つ まり、引数に何も指定されない場合は、空の配列になります。

配列として呼び出し例

このメソッドを利用するときは、通常はprint("foo", "bar", "buzz");のように利用します。 さて、可変長引数はメソッド内で配列として扱われます。実はメソッドの呼び出し元からも配列として扱うこともできます。次の例は、可変長引数に配列として 値を指定しています。これは、print("foo", "bar", "buzz");と同義です。
String[] argv = new String[]{"foo", "bar", "buzz"};
print(argv);
ただし、配列として値を渡す場合、print((String[])null);のように配列として明示的にnullを渡すと、printメソッド の可変長引数argv自体もnullになるので気をつけてください。print(null)の場合は、可変長引数(配列)の最初の値がnullになり、配 列自体は実体があります。