Interface型を何故使うのかのお話

エンジニア yoc
2022年04月19日

入社3年目に突入したひよっこエンジニアyocです。
今回は私が昔初めてJavaを触った時にinterface関連で混乱したことを思い出したので、
それを振り返りつつ纏めておこうという回です。

~~ ある日 ~~

入社当初、私はJavaなんて言語をあまり触ったことがありませんでした。
それでも既存のコードを模倣すればジグソーパズルが如くなんとかなりそうな感じだったので、
それで画面に向かってコードを読んでいました。
そこで見慣れない不思議なコード目にします。

List list = new ArrayList();

私は直感的にこう思いました。
「右と左の型が違うのはおかしい」

不思議だったので軽く検索をかけてみても、
どうやら皆同じ書き方をしているなぁと
当時はそれぐらいしかわかりませんでした。
それでも上の文をコピペすれば配列らしきものをつくれるようだったので、ひとまずは満足しました。

~~ 後日 ~~

今度は私は連結リストを作りたくなりました。
私は微妙にアルゴリズムをかじっていたので、
通常の配列では先頭に値を挿入した場合、配列の値をメモリ上で一個ずつ後ろにずらす操作をしなければならないので、
配列が長くなると非常に処理が遅くなることを知っていました。
一方連結リストと呼ばれるデータ構造は先頭に値を挿入してもメモリ上で続く値を後ろにずらす操作が発生しないため、
どれだけリストが膨らんでも安心です。
さて、先日のコードを思い出して、多分Arrayを消せば連結リストを作成出来るんじゃないかなぁ〜と以下のコードを書きました。
一応、理由としては他の今まで触ってきた言語では単にListというと連結リストを表すことが多かったので。
すると…

List list = new List();
Cannot instantiate the type List

どうして?
エラーが出ました。一体どういうことなのでしょうか?

上記のエラーは軽く訳すとListという型はインスタンス化(≒new)出来ませんよ、ということです。
実はこのListというのがinterfaceというやつだったのです。
さて、当時の私はinterfaceはnew出来ない、よくわからないけれどとりあえずそういうものなのだろうと納得したことにしました。
じゃあ結局連結リストをつくるにはどうすればよかったのか、
調べてみるとLinked Listというものがあり、以下のように作ることが出来ることがわかりました。

List list = new LinkedList();

ここでようやくなるほど、こういう宣言をする時は左はinterfaceを、右は実装したいものを書いておくのだなと、
Javaではそういう書き方をする言語なんだなぁと思いました。

さて、ここまででふと疑問が湧きます。
右にinterfaceを書くとnew出来ないよと弾かれる。
じゃあ左に実装したい型を書けば…?

LinkedList list = new LinkedList();

どうも実際に動かしても問題ないようです。
そもそも今までも

String str = "foo";

のようなコードを書いてきたわけですから、動かなければ何かおかしそうな気がします。
でもまぁ、意固地にならずにみんなが書いているように左interface右普通のクラスで書けばだいたいいいだろうと、
そういうことにしておこうとすることにしました。

そうしてようやく当初目的通りにlistの先頭に値を挿入すべく、次のコードを書きました。

List list = new LinkedList();
list.addFirst("foo");
The method addFirst(String) is undefined for the type List

?????
これは本当に混乱しました。
import文の書き損ないじゃないし、addFirst()はJava6にはまだ無かったか別の名前のメソッドかと調べてもそんなことはなく、
いや一体どうして…

実はその答えはエラー文にありました。
undefined for the type List
LinkedListには確かにaddFirst()は存在するのですが、
List、すなわちinterfaceの方にありませんよと書かれているわけです。
英語はどうしても飛ばし読みがちになって良くないですね…

さて、ここまですっかり勘違いをしてことがようやくわかりました。
List list = new LinkedList()とコードを書いた場合、
このlistはLinkedListではなく、Listとして扱わなければならないということにです。

なるほど、結局の所今回はたくさんものを並べた上で、その先頭に要素を追加することもできるものが作りたかったのでした。
しかし、Listというinterfaceは残念ながら先頭への挿入がスムーズに出来るようには設計されていません。
そのような先頭への挿入操作に優れたデータ構造を表すinterface、Dequeが存在し、
もちろんLinkedListはDequeをimplementしているので、今回の場合は

Deque deq = new LinkedList();
deq.addFirst("foo");

のようにすることが正解だったでしょう。
もちろんaddFirstメソッドはDequeのメソッドですから、undefinedなどとはエラーを出されません。

ここにきてようやく理解できました。
ArrayList list = new ArrayList()とどうしてあまり書かれないのか、
それはすなわち、数を扱う時にいつでもfloatやdoubleを使うような愚行だからです。
我々は何故静的片付け言語を用いるのか、それはなんでもかんでも変数に入り込んできたら手に負えないからです。
interface型することによって使う機能を制限するとともに、それ以外の機能は使いませんとも誓約しているわけです。
もちろんArrayListの機能をフルに使いたい場合、
それはinterfaceの型に押し込めることなくArrayList list = new ArrayList()と書くべきでしょう。
しかし、実際には必要としているのはせいぜいinterfaceのListで定義されている動作程度なので
List list = new ArrayList()
とした方がいいでしょう。
そして、今回の前方への挿入のようにそのinterfaceでは解決しない問題が出てきた時、
根本から設計を見直さなければならないということです。

2022年04月19日

エンジニア yoc |

« »
このページのトップへ