適切なインタフェース型が存在するのであれば、パラメータ、戻り値、変数、フィールドではすべてインタフェース型を利用して宣言するべきです。
オブジェクトのクラス名を参照する必要がある唯一の箇所は、そのオブジェクトを生成するところです。
// 良い例:型としてインタフェースを利用 List<Subscriber> subscribers = new Vector<Subscriber>(); // 悪い例:型としてクラスを利用 Vector<Subscriber> subscribers = new Vector<Subscriber>();
実装の変更
このように型としてインタフェースを宣言するようにすると、実装を切り替えるのが簡単になります。
// 実装を Vector から ArrayList に切り替え List<Subscriber> subscribers = new ArrayList<Subscriber>();
この例のようにコードをほとんど変更せずに実装を変更することができます。 実際、Java ライブラリの ThreadLocal の実装に使う Map フィールドは、HashMap から IdentitiyHashMap という実装に簡単に取り替えられました。
コードが、インタフェースの契約上要求されていない特別な機能に依存している場合、新たな実装も同じ機能を提供する必要があります。
たとえば、Vector の「同期化されている」という機能に依存しているのならば、実装を ArrayList で入れ替えるのは正しくありません。そのような要求項目があるのならばドキュメント化してください。
適切なインタフェース型が存在しない場合は、クラス階層の中で必要な機能を実装している最も上位のクラスを宣言として利用しましょう。 そうしておけば柔軟性を向上させることができます。