Java : 型宣言についてもう少し考えてみる

Javaには限らないけど)
http://d.hatena.ne.jp/lethevert/20080305/p2
http://cappuccino.jp/keisuken/logbook/20080307.html#p03
http://d.hatena.ne.jp/odz/20080307/1204898914
http://cappuccino.jp/keisuken/logbook/20080308.html#p02
前の話をもうちょっとよく考えてみると、もうちょっと一般的な原則に還元することができます。
まず、前提として、「アップキャストは無条件に成立するが、ダウンキャストは失敗する可能性がある。そのため、できる限りダウンキャストの発生を少なくしたい。また、同じ効果をもつコードなら、できるだけコードの再利用性が高い方が望ましい。」という判断基準は共有されているものとします。

f :: A -> B

という関数を考えたときに、型Aと型Bをどういう方針で決めれば適切か、ということを考えます。
結論は簡単で、Aはできるだけ汎用的に、Bはできるだけ特化的にすれば、できるだけダウンキャストを避けながらコードの再利用性を高めることができます。つまり、原則として「引数は汎用的に、返値は特化的に」型を決定します。
変数の場合はどうでしょう?
変数は次のようなオブジェクトに置き換えて考えてみることができます。

class Var <A>{
  void set (A val);
  A get ();
}

同じ型変数Aが引数と返値の両方に出現するので、先の原則だけでは決定することができません。そこで、文脈を考慮にいれます。

1. ローカル変数で一度だけ初期化するケース

ローカル変数の利用としては、もっとも一般的なケースです。この場合は、set()を一度だけ呼んで、その後はget()だけを使うという使われ方になります。そこで、set()よりもget()の方が支配的な関数であると考えることができるので、get()の方を優先的に考慮して型を決めることができます。
つまり、できるだけ特化的に型を決めます。次のケースは、この原則に従った例です。

final ArrayList<String> ls = new ArrayList<String>();
2. ローカル変数で何度も代入を行うケース

これは、一般的にあまり望ましいプログラミングスタイルとはいえませんが、ループなどで必要になることがあります。set()とget()が両方とも使われるため、型はバランスを考慮して決定することになります。

3. オブジェクトのフィールドであるケース

オブジェクトのフィールドの場合は、考慮すべき文脈が多岐に渡るため、一概に決めることが難しくなります。ただし、その場合も、set()とget()のどちらが支配的であるかを考えることで、型を決める際の指針とすることができます。
例えば、値を外から受け取らず、一度だけ初期化するフィールドの場合、できるだけ特化的に型を決めるのが適当です。
逆に、値を外部からセットして、内部でのみ利用するフィールドの場合、できるだけ汎用的に型を決めるのが適当です。


ところで、このように考えると、型宣言と同時に値を設定するfinalな変数は、型宣言を不要にしても全く問題にならないということが理解できます。

final ls = new ArrayList<String>();