※並列で比較するのはちょっと変ですが、似て非なる概念として…
final
意味:参照先が変えられない。
※修飾子なので、変数やメソッドの定義の際に使用する。
日常生活に例えると
前提となるシチュエーション
お母さん「今から渡すメモに書いてあるものを買ってきて!」
あなた「ok」
※メモの内容
りんご、バナナ、みかん
これはできる
お母さん「ごめん買うもの間違えてたわ、修正するね」
あなた「ok」
※メモの内容
りんご、味噌、インスタントラーメン
これはできない
お母さん「ごめん、そのメモ裏紙にしたらだめなやつだったわ。こっちのメモにして」
あなた「いやもう家出ちゃったからメモ変えられないって」
※メモの内容(お母さんが変えたがっている新たなメモ)
りんご、バナナ、みかん
つまり、メモの内容を書き換えることはできるが、「あなたが買い物に行くときに見るメモ自体」を変えることはできない ということです。
コードで見てみる
final List<String> toBuyMemo = new ArrayList<>();//最初に渡されたメモ
//最初に書かれていた内容
toBuyMemo.add("りんご");
toBuyMemo.add("バナナ");
toBuyMemo.add("みかん");
System.out.println(toBuyMemo);//出力結果:[りんご,バナナ,みかん]
//メモの内容を修正
toBuyMemo.set(1,"味噌");
toBuyMemo.set(2,"インスタントラーメン");
System.out.println(toBuyMemo);//出力結果:[りんご,味噌,インスタントラーメン]
//メモ自体を変えようとするとコンパイルエラー
toBuyMemo = new ArrayList<>();//新しいメモ
値(メモの内容)を書き換えても、参照先(あなたが買い物に行くときに見るメモ自体)を変えているわけではないので、コンパイルエラーにはなりません。
逆に値(メモの内容)は変わらなかったとしても、参照先(あなたが買い物に行くときに見るメモ自体)を変えようとした場合、コンパイルエラーになります。
イミュータブル
意味:値が変えられない
※性質を表すものなので設定するものではない。String型のオブジェクトなどがこれにあたる。
日常生活に例えると
前提となるシチュエーション
お母さん「今から渡すメモに書いてあるものを買ってきて!」
あなた「ok」
※メモの内容
りんご、バナナ、みかん
これはできる
お母さん「ごめんそのメモ盛大にミスってるわ。こっちのメモ見て」
あなた「ok」
お母さん「間違っている方のメモは預かるわ」
※新しく渡されたメモの内容
醤油、塩、砂糖
(厳密には内容はそのままでも良い)
※メモを変えただけなので、元のメモも残っている
これはできない
お母さん「ごめんそのメモ盛大にミスってるわ。そのメモの内容修正するね」
あなた「えこれ油性だから無理だよ。修正するなら新しいメモにしなきゃ」
つまり、「見るメモを変えることはできるが、メモの内容は変えられない」 ということです。
コードで見てみる
String toBuyMemo = "りんご、バナナ、みかん";//1枚目のメモ
toBuyMemo = "醤油、塩、砂糖";//2枚目のメモ
System.out.println(toBuyMemo)//出力結果:醤油、塩、砂糖
//元のメモ "りんご、バナナ、みかん" は変更されず、メモリ上に残る
//toBuyMemoは新しいメモ "醤油、塩、砂糖" を参照するようになっただけ
※Stringを用いたのは説明を簡潔にするためです
※ガベージコレクションは考慮しません。
Aの内容がBに書き換えられたわけではなく、Aはメモリ上にそのまま残し、toBuyMemoが参照する先をBに変えたイメージです。
まとめると
- final:変数の参照先を変更できなくする。ただし、その参照先の値を変えるのは自由。
- イミュータブル:値は変更できない。ただし、変数の参照先自体の変更は自由。