質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

オブジェクト指向

オブジェクト指向プログラミング(Object-oriented programming;OOP)は「オブジェクト」を使用するプログラミングの概念です。オブジェクト指向プログラムは、カプセル化(情報隠蔽)とポリモーフィズム(多態性)で構成されています。

Q&A

4回答

2075閲覧

イミュータブルなオブジェクト同士が相互参照している場合はどのように対処すれば良いのか?

cn202k

総合スコア0

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

オブジェクト指向

オブジェクト指向プログラミング(Object-oriented programming;OOP)は「オブジェクト」を使用するプログラミングの概念です。オブジェクト指向プログラムは、カプセル化(情報隠蔽)とポリモーフィズム(多態性)で構成されています。

0グッド

1クリップ

投稿2020/07/31 15:24

編集2020/07/31 15:34

オブジェクトをイミュータブルにすることに対する肯定的な意見はネット上に溢れているし、それには僕自身も納得しているのですが、次の例のようにオブジェクト同士が相互参照している場合はどのように対処すればよいのでしょうか?

例えば、次のようにイミュータブルなPersonPetクラスがあったとします(擬似コードです)。

class Person { final String name; final int age; final ImmutableList<Pet> pets; } class Pet { final String name; final Person owner; }

これを使って次のような処理をします。

me = Person(name:'yamada', age:30, pets:[]); cat = Pet(name:'dora', owner:me); // me.petsにcatを追加 // immutableなので me!=me_1 me_1 = me.copyWith(pets: me.pets + cat);

すると、me_1catは相互参照している状態になります。このあとme_1.ageを30から40に変更しようと思ったら

me_2 = me_1.copyWith(age: 40);

とすれば良いわけですが、これだけだとcat.ownerが古い方(me_1)を参照したままになってしまうので、そちらの方も書き換える必要があります。

cat_1 = cat.copyWith(owner: me_2);

このようにイミュータブルなオブジェクトが相互参照していると、片方が変化するたびに(参照の整合性を保つために)もう片方も修正する必要があります。今回の例はシンプルな相互参照でしたが、この参照の関係がもっと複雑になればなるほど整合性を保つのが大変になってしまうはずです。このような場合、どのようにして対処すればよいのでしょうか?それとも、イミュータブルなオブジェクト同士が相互参照するような設計は避けるべきでしょうか?

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

miyabi_takatsuk

2020/07/31 15:26

言語によって変わる部分なので、 言語の質問タグも追加しましょう。 Javaですか?
cn202k

2020/07/31 15:34

javaです。タグ追加しました。
raccy

2020/07/31 22:14 編集

例示のコードでは`cat.owner == me_1`が`false`になるのでそもそも相互参照していません。
guest

回答4

0

このあとme_1.ageを30から40に変更しようと思ったら

この時点でイミュータブルをやめます。

イミュータブルなオブジェクト同士が相互参照するような設計は避けるべき

ケースバイケースです。今回のPersonPetの関係であれば相互参照をして良いと思います。
しかし、イミュータブルである必要性はありません。

ですから、

イミュータブルなオブジェクト同士が相互参照している場合はどのように対処すれば良いのか?

に対する回答は「イミュータブルにすることをやめて、 me.age = 40;と書く」 です。

me.copyWith(pets: me.pets + cat);
me_1.copyWith(age: 40);

meme_1me_2のように変更のたびインスタンスのコピーを生成していてはまるでたまたま遺伝子が同じのコピー人間を生成しているようです。

今回のケースでは、姓名が変わろうが歳を取ろうがその山田さんは同一人物でしょう。

その山田さんをコピーをしていては姓名と年齢と遺伝子の1部がたまたま一致した別人の山田さんを生成しているようで、違和感を感じます。生成された meme_1me_2は それぞれ 別人 だと思います。catcat_1についても同じです。

meme_1me_2catcat_1 が同一の人物を指しているのであれば PersonPetはミュータブルである方が都合が良いのではないでしょうか。


現実世界のオブジェクトでたとえ話をするのは混乱を招くような気がしますが…、イミュータブルであるべきオブジェクトの例を1つ挙げるなら 硬貨 でしょうか。

硬貨の金銭的価値(フィールドの値)は最初(インスタンス生成時)に決めると思いますが、途中でその価値が変化すると困る場合があります。いえ、おそらく困ります。

明日から我々が使っている100円玉の価値が50円になったら困りますよね。硬貨に 100 と書いてあるはずなのに金銭的価値は 50 円です。私なら困ります。半額で自動販売機でジュースが買えたり、アーケードゲームが遊べて一部の人たちはラッキーかもしれませんが、それ等で商売している人たちはきっと困るでしょうね。

ですから、硬貨をオブジェクトとして表現する際はイミュータブルである方が都合が良いでしょう。

投稿2020/07/31 17:03

編集2020/07/31 17:32
BluOxy

総合スコア2663

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

Javaだと標準でimmutableなリストがないのでKotlinでサンプルを書いてみました。

Kotlin

1class Person(val name: String, val age: Int, petNames: List<String>) { 2 val pets: List<Pet> = petNames.map { Pet(it, this) } 3 fun growOld(year: Int): Person { 4 return Person(name, age + year, pets.map { it.name }) 5 } 6} 7 8class Pet(val name: String, val owner: Person) 9 10fun main(args: Array<String>) { 11 val me = Person( 12 "yamada", 13 30, 14 listOf("dora", "neko") 15 ) 16 println(me.age) 17 println(me.pets[0].owner == me) 18 val me1 = me.growOld(10) 19 println(me1.age) 20 println(me1.pets[0].owner == me1) 21}

一口にimmutableなオブジェクトといってもその方法には言語によって違いがあります。

  1. オブジェクトが生成された段階でimmutableである。(HaskellやElixir等がたぶんそう)
  2. コンストラクタでのみインスタンス変数への初期代入が許される。(JavaのfinalやC#のreadonly等)
  3. 任意のタイミングでオブジェクトを凍結(freeze)できる。(JavaSriptのOjbect.freezeやRubyのObject#freeze等)

最初の1.は原理的に相互参照はできません。AとBというオブジェクトを相互に参照しようとしても、Aを作成するときに、Aを参照するBが必要ですが、Aはまだ作成されていないから、Aを参照するBというものを作成することが不可能です。対して、2.の場合は、上のコードにあるように、コンストラクタ内ではすでに仮状態でAが作成されているため、Aを参照するBを作成することができます。ただ、全ての処理をコンストラクタで行う必要があります。最後の3.の場合は、さらに自由なタイミングで凍結(immutable化)できますので、それに合わせて作れば良いだけです。ただ、一度凍結されたオブジェクトは解凍(mutable化)はできませんので、mutableなままのコピーとか作れば簡単かも知れません。なお、相互参照状態の時は、参照先も凍結させるdeep_freezeのような関数を別途用意する必要があるかも知れません。

結局の所、どうやって相互参照しているimmutableなオブジェクト群を作るのかというのがわかっていれば、一部を変更する場合でも、同じように、一部だけ違う相互参照しているimmutableなオブジェクト群を作れるはずです。なので、まずはそこができるようになってからだと思います。

投稿2020/08/01 02:32

raccy

総合スコア21735

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

xebme

2020/08/01 04:18

Kotlinを始めて1週間たたない初心者です。Kotlinの遅延初期化を使うと相互参照できそうな気がするのですが、どうお考えですか。(そういえば、Scalaにも遅延初期化がありました)
raccy

2020/08/01 04:54

`lateinit`は`val`な変数にはつかえず、`var`な変数のみなので、今回は使えませんでした。
guest

0

Javaで相互参照状態を作るにはコンストラクターで全てを作るしかなさそうです。

イミュータブルか状態か

問題は、イミュータブルと言っていながらageの状態を考えていることにあります。設計矛盾。

年齢が変化するのなら、生年月日を持たせて、日付から年齢を導出するのが関数型プログラミングにふさわしい。しかし、まだ問題があります。Petが増えたり減ったりする。

状態変化するオブジェクトをイミュータブルとして扱いたければ、状態が変化したときにオブジェクト全体を作り直すしかありません。リッチ・ヒッキーの言うように、歴史のある時点のスナップショットとしてオブジェクトを捉えて、タイムスタンプ付きでログファイルに記録して凍結するような例えになります。

状態変化を外に出す

相互参照と状態変化により利便性が損なわれる。解決策はデータベースの設計が参考になります。PersonとPetが相互参照しない構造にする。PersonのフィールドのPetコレクションを外に出す。Petのownerを廃止する。こうすることでPetが死んでも、所有権が変わっても問題なくなります。

所有関係をミュータブルに管理

  • Person/Petの組み(タプル)の集合を作る
  • PetのリストをPersonをキーにしてMapに格納

  MapのキーにするためにはPersonがイミュータブルであること
equals/hashCodeが変わるとMapから取り出せなくなる

変化する部分をミュータブルなオブジェクトにするのがポイント。

言語実装の問題

Javaはイミュータブル性を保証できない言語です。イミュータブル性を保証する言語なら、古いオブジェクトから新しいオブジェクトを生成するには差分だけを作り、新旧オブジェクトの共通部分はメモリ上で共有できます。Javaは全体をディープコピーして差分を変更することしかできません。
そのほか、上のMapのキー問題や、メソッドの戻り値としてコレクションを返すときの防衛的コピーの問題、スレッドの同期化など、イミュータブル性を保証できないための弊害は言い尽くされています。

投稿2020/08/02 22:59

編集2020/08/03 21:29
xebme

総合スコア1083

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

Ownerインターフェスを作って、PersonがOwnerになればいいのでは?

投稿2020/07/31 15:59

shiracamus

総合スコア5406

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問