北海道苫小牧市出身の初老PGが書くブログ

永遠のプログラマを夢見る、苫小牧市出身のおじさんのちらしの裏

ArrayListとperlの配列の違い 最終夜

perlっぽいListを作ったわけですが、3つほど考えておくべき点があります。

『AbstractListを使ったこと』

AbstractListで楽チン実装してしまいましたが、パフォーマンス的にはあまりよくないかもしれません。PerlLikeListの全てのメソッドはPerlLikeListのget, add, set, remove, sizeに集中し、それは結果としてラップされたListのget, add, set, removeに集中することとなります。よって、ラップされたListの他のメソッドが呼ばれることはありません*1。5つのメソッドに処理を集中出来ることは、ラップの確実性を高めるためには有利です。

しかし、AbstractListの実装はこれら5つのメソッドを組み合わせただけなので、パフォーマンス的にはあまりよくありません。例えば、clear()はremove()を繰り返し呼ぶだけです。ラップされたListのclear()は素晴らしい実装をしているかもしれませんが、それは生かされなくなってしまいます。

車輪の再発明

Jakarta Commons Collectionsに、似たような発想のクラスがすでにあります。get()に関する拡張はLazyList、set()系に関する拡張はGrowthListがやってくれます。

import org.apache.commons.collections.functors.ConstantFactory;
import org.apache.commons.collections.list.GrowthList;
import org.apache.commons.collections.list.LazyList;

List<Integer> data = LazyList.decorate(new GrowthList(), 
                                       ConstantFactory.NULL_INSTANCE);

こんな感じなのですが、get()しただけで配列サイズが大きくなってしまう点と、負の値を受け入れない点で作成したPerlLikeListとは動作が違います。

負値に関しては、微妙に違いますがLoopingListIteratorなんてものがあります。これは別に負値のindexを扱うクラスではないですが、添字0の要素から最後の要素へ戻ることが出来る循環型のイテレータです。

『ListインタフェースとPerlLikeListクラス』

この実装を書くきっかけとなった最初のコードに従えば、

List<Integer> data = new PerlLikeList<Integer>();
System.out.println(data.get(-2));

とPerlLikeListを使うことになります。これは、できるだけ上位のインタフェースで変数を定義すると言うセオリーに従った物ですが、この場合はあまり良くないんじゃないかと思います。

なぜなら、Listインタフェースは負の値が来ることを想定していないため、通常の実装ではget(-2)はExceptionを吐くからです。つまり、このロジックはdataをListとして定義しておきながら、PerlLikeList以外では決して動作しません。それならいっそのこと、

PerlLikeList<Integer> data = new PerlLikeList<Integer>();
System.out.println(data.get(-2));

と書いた方が、混乱が起こらなくていいのではないでしょうか?*2

なんでもかんでも上位のインタフェースで定義するのではなく、上位のインタフェースで定義するからにはその作法は守り、守れないときは無理に上位インタフェースとしないほうがいいと思います。


そんなわけで、無駄に4回に別れたシリーズもこれで完結です。たかがListの実装ですが、色々と考察してみるのは楽しいものですね(ノ*゜ー゜)ノ

*1:これらのメソッドの実装によっては間接的にはありえます

*2:念のため言っておくと、これとは逆にPerlLikeListをListを想定して書かれている他のパッケージの引数として与えたりするのは多いに結構なこと