DATE : 2006/04/03 (Mon)
java.util.List は、Java プログラミングの中でも頻繁に使用します。特に List の中でも、java.util.ArrayList を使う場面は結構あります。
しかし、List を実装したクラスとして、java.util.LinkedList もあります。
ということで、ArrayList と LinkedList の使い分けをメモしておきます。
ArrayList を使う場合
ArrayList は java.util.RandomAccess を実装しています。
RandomAccess インタフェースを実装しているリストは、ランダムアクセス(適当な位置の要素にアクセスする処理)を行った場合に、一定時間内に要素へアクセスできることを保証しています。
つまり、ランダムアクセスが行われる可能性がある場合は、 ArrayList を使います。
ただし、 ArrayList には弱点もあります。
ArrayList の内部は配列になっています。そのため、要素の追加や削除を行うと、配列の挿入部分を確保するためや空き部分を埋めるために、他の要素の移動を行います。また、あらかじめ指定した容量を超えて要素を追加すると、超えた分量を格納できるだけの配列を新たに生成します。
さらに、大きいオブジェクトを扱う際には、余分な空き領域も大きくなってしまいます。
そのため、大量に要素の追加・削除を行う場合には、処理が遅くなることがあります。
LinkedList を使う場合
ArrayList に対して LinkedList は RandomAccess インタフェースを実装しません。
その代わり、要素の追加・削除には強い構成となっています。
LinkedList の内部はリスト構造になっています。要素ごとに前の要素と後ろの要素を記録することでリストを構成しているため、前後になる要素の記録をいじれば追加や削除は完了します。また、ArrayList のように余分な空き領域はできません。
ただし、先頭や最後尾以外の要素にアクセスする場合は、要素を一つずつ辿っていく必要があります。つまり、ランダムアクセスを行う場合には処理に時間がかかります。
そのため、要素の追加・削除を大量に行う場合でランダムアクセスを行う可能性が低い場合は、 LinkedList を使います。最終的にどれだけの要素を追加するのか不透明な場合や大きなオブジェクトを扱う際には特に有効でしょう。
リストの受け渡しは List で
今回の記事では、 ArrayList と LinkedList の使い分けについてメモしました。
しかし、次のようなコードは避けたほうが無難です。
public LinkedList createList() {
// リストの生成処理
}
public void execute(ArrayList list) {
// リストで何か処理
}
もしも、createList で生成されるリストを ArrayList に変更した場合はどうなるでしょうか。createList で生成されたリストを受け取る側のコードも変更する必要が出てくるかもしれません。
また、 execute メソッドに LinkedList を渡したい場合はどうすればよいでしょうか。わざわざ LinkedList から ArrayList に要素を格納し直すのは大変です。
冒頭の通り、ArrayList や LinkedList は List インタフェースを実装しています。ですので、次のようにすれば ArrayList や LinkedList を意識せずにリストをやりとりできます。
public List createList() {
// リストの生成処理
}
public void execute(List list) {
// リストで何か処理
}
ただし、そのリストに特有のメソッドを使いたい場合や、処理速度・メモリ容量を意識する場合は、あえて ArrayList や LinkedList でやりとりする必要があります。
ちなみに、List は java.util.Collection を実装しています。もしも、リストだけでなくセット(java.util.Set)も扱えるのであれば、Collection でやりとりするのもいいかもしれません。
DATE : 2006/04/02 (Sun)
JUnit という、単体テストを行うフレームワークがあります。また、Java SE 5 からは、列挙型が導入されました。
Java の列挙型は型の保証や名前空間があってとても便利です。また、メソッドも定義できるため、多態性も容易に実現できます。
しかし、列挙型を JUnit でテストしようとした時にはたと困ってしまいました。
列挙型に定義したメソッドは通常通りにテストできます。しかし、列挙型定数の数が増えた場合には、未テストの定数が出てしまいます。もちろん、列挙型定数の数が増える前に、増えた分のテストコードを書けば問題ないのでしょうが、それを忘れてしまう可能性もあります。
そこで、列挙型のテストクラスには、列挙型定数の数をテストするコードを書くようにしました。
具体的には、次の1行をテストコードに追加します。なお、列挙型定数が4つある列挙型 EnumExample をテストすることを想定しています。
assertEquals(4, EnumExample.values().length());
values メソッドを使うと、EnumExample 内の列挙型定数が配列として定義順に返されます。この配列の要素数を利用することで、列挙型定数の数をテストするわけです。
参考文献
DATE : 2006/04/02 (Sun)
Swing を使っていると、なぜかボタンなど GUI の反応が鈍いという現象が発生します。
Swing は比較的簡単にコードで GUI を作ることが出来ます。しかし、簡単だからといって適当に作ってしまうと落とし穴にはまってしまいます。
ここでは、その落とし穴の中から、GUI の反応が鈍い現象を取り上げてみます。
アプリケーションスレッドと AWT イベントディスパッチスレッド
通常、私達が組んだプログラムを実行するのは、アプリケーションスレッドと呼ばれるスレッドです。それに対して、Swing の処理は AWT イベントディスパッチスレッドというスレッドで行われます。
AWT イベントディスパッチスレッドは Swing の処理の他にも、ボタンが押された場合などのイベントリスナを呼び出す処理も行っています。
例えば次の execute メソッドは、action メソッドから呼ばれた場合はアプリケーションスレッドで、Swing によって actionPerformed メソッドから呼ばれた場合は AWT イベントディパッチスレッドで処理されます。
(このクラスは ActionListener を実装します)
public void action() {
execute();
}
public void actionPerformed(ActionEvent e) {
execute();
}
public void execute() {
// 処理
}
もしここで、execute メソッドの中で重い処理を行った場合はどうなるでしょうか。当然のことながら、 action メソッド、actionPerformed メソッドともに、メソッドが終了するまでにかなりの時間がかかります。
Swing が actionPerformed メソッドを呼び出せば、execute メソッドが終わるまでの間、 Swing の処理が止まります。
重い処理は別のスレッドで
そのため、 AWT イベントディスパッチスレッドで実行される可能性のある処理は、速めに終えなければなりません。
具体的には、処理の中でスレッドを生成して、重い処理はそのスレッドの中で行うようにします。
(このクラスは ActionListener を実装します)
public void action() {
execute();
}
public void actionPerformed(ActionEvent e) {
Thread t = new Thread(new Runnable() {
public void run() {
execute();
}
});
t.start();
}
public void execute() {
// 処理
}
このようにすれば、AWT イベントディスパッチスレッドはスレッドを実行した直後に終了し、Swing の処理が再開されます。
ただし、今回の例のように別々のスレッドから同じメソッドが呼ばれるような場合は、何かしらの排他制御を行う必要がでてくるかもしれません。
参考文献
- How to Use Threads(英語)(;^ω^)ほとんど読んでいませんが
DATE : 2006/04/01 (Sat)
私用で Java のコマンドラインアプリケーションを作っています。
コマンドラインアプリケーションといえば、「-a」や「--version」、「-D 1234」などのオプション。
これをわざわざ自前で解析するのは非常に面倒です。
そこで、Apache Jakarta Commons-CLI (以下、CLI)というライブラリを使ってみました。
とりあえず、使ってみた範囲でメモしておきます。
なお、CLI のバージョンは1.0です。
ちなみに、英語の分かる方は公式ページを見たほうが正確ですし、有意義だと思います。(ちなみに私は英語が分からないので、このような記事を書いているわけです(;^ω^))
コマンドラインオプションの準備
まず、どのようなコマンドラインオプションを用意するかを定義します。
ここでは、次のようなオプションを考えます。
(・∀・)オプションの内容に深い意味はありません。
- -a, --all
- 全てを出力する。
- -n <番号>
- 番号を指定する。(2番を指定する例:-n 2)
- -help
- ヘルプを出力する。
オプションが用意できたら、CLI にその定義を教えます。
ここでは、 org.apache.commons.cli.Option を使います。
Option all = new Option("a", "all", false, "全てを出力する。"); Option n = new Option("n", true, "番号を指定する。(2番を指定する例:-n 2)"); n.setArgs(1); n.setArgName("番号");
Option help = new Option("help", "ヘルプを出力する。");
Option には3つのコンストラクタがあります。ですので、それぞれ違ったコンストラクタで生成してみました。
コンストラクタでは、オプションの名前(「a」や「n」など)やその説明などを定義しています。
コンストラクタの中で true や false と指定している部分は、そのオプションに引数があるかないかを定義しています。-a オプションには引数がないため false 、-n オプションにはあるため true となります。
前述の通り、-n オプションには引数があります。そこで、Option#setArgs(int) で引数の数を、Option#setArgName(String) で引数の説明を設定しています。
なお、ここで定義したオプションは全て、 org.apache.commons.cli.Options に登録しておきます。
Options options = new Options(); options.addOption(all); options.addOption(n); options.addOption(help);
ちなみに、org.apache.commons.cli.OptionGroup を使えば、どちらか一方しか指定できないオプションを定義できるようです。
コマンドラインパーサの準備
コマンドライン引数の解析方法を定義するコマンドラインパーサを準備します。「-a -b -c 100 -d」というオプションがあった場合に、「 -a オプション、-b オプション、 -c オプションの引数100、-d オプションがある」と解析するわけです。
コマンドラインパーサは、 org.apache.commons.cli.CommandLineParser インタフェースですので、自作することも可能です。
しかし通常は、すでに実装されているパーサを使用することになると思います。
用意されているパーサは以下の通りです。
- org.apache.commons.cli.BasicParser
- org.apache.commons.cli.GnuParser
- org.apache.commons.cli.PosixParser
ここでは、 GnuParser を使うことにします。
CommandLineParser parser = new GnuParser();
ちなみに、「-a」、「-b」オプションを同時に「ab」と指定できるコマンドラインアプリケーションがあります。そのようなアプリケーションを作る場合は、PosixParser を使うようです。
コマンドライン引数の解析
実際にコマンドライン引数を CLI に解析させてみます。
CommandLine commandLine = parser.parse(options, args, true);
先程指定した GnuParser インスタンスの parse メソッドを呼び出しています。
args は、 main メソッドの引数です。main メソッドの引数 args には、コマンドライン引数が格納されているので、そのまま CommandLineParser#parse(org.apache.commons.cli.Options, String[], boolean) に渡しています。
最後の引数(boolean)は、定義されていないオプションがある場合に、解析を中止するかどうかを指定しています。ここでは、とりあえず true を指定しています。
オプションがあるかどうか調べる
コマンドライン引数にオプションがあるかどうかを調べてみます。
オプションがあるかどうかを調べるには、org.apache.commons.cli.CommandLine#hasOption(String) を使います。引数にオプションを指定して、そのオプションがコマンドライン引数にあれば true 、なければ false を返します。
今回の例では、次のようなコードになります。
if (commandLine.hasOption("a")) { // -a オプションがある場合の処理 } if (commandLine.hasOption("n")) { // -n オプションがある場合の処理 } if (commandLine.hasOption("help")) { // -help オプションがある場合の処理 }
オプションの引数を取り出す
例の中で、-n オプションは「-n <番号>」という形で番号を指定できました。
この引数を取り出すには、 CommandLine#getOptionValue(String) を使います。
String argument = commandLine.getOptionValue("n");
引数が指定されていない場合は、 null が返ります。
引数が複数ある場合は、CommandLine#getOptionValues(String)を使うとよいかもしれません。
また CLI には、引数がない場合の標準値を設定できるメソッドもあります。
ヘルプを表示する
Option オブジェクトを生成した際に、コンストラクタでそのオプションの説明も付け加えました。
( ・∀・)これは、ヘルプを表示するための布石だったりします。
CLI には、ヘルプを出力する機能もあります。
ヘルプを表示するには、 org.apache.commons.cli.HelpFormatter を使います。
HelpFormatter formatter = new HelpFormatter(); formatter.printHelp("example [option]...", options);
HelpFormatter#printHelp メソッドにはいくつか種類があります。この他にも、使い方を指定できたり、ヘルプの前や後に好きな文字列を表示するように指定できるメソッドもあります。
ちなみに上のコード例を実行すると、次のようなヘルプが表示されます。
usage: example [option]... -a,--all 全てを出力する。 -help ヘルプを出力する。 -n <番号> 番号を指定する。(2番を指定する例:-n 2)