0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JavaでListにaddしたあとに値を変えるとListの中身も変わる

Posted at

はじめに

このまえPG中に遭遇した「ListにDTOをaddしたあと、元のDTOを変更したらListの中身も変わっていた」という事象について整理します。
私を含めた初心者の方のためにも記事を残します。

サンプルコード

Main.java
class UserDto {
    String name;
    UserDto(String name) { this.name = name; }
    public String toString() { return name; }
}

public class Main {
    public static void main(String[] args) {
        List<UserDto> list = new ArrayList<>();
        UserDto dto = new UserDto("Alice");

        list.add(dto);
        System.out.println(list); // [Alice]

        dto.name = "Bob";
        System.out.println(list); // [Bob] ← 変わってる!
    }
}
text
[Alice]
[Bob]

なぜこうなるのか

  • JavaのListに格納されるのはオブジェクトそのものではなく参照。
  • list.add(dto)を呼んだ時点で、Listは「dtoが指しているオブジェクトへの参照」を保持。
  • その後dto.name = "Bob"と変更すると、Listが保持している参照先のオブジェクト自体が変わるため、Listの中身も変わったように見える。

つまり、addした瞬間に「コピー」されるわけではなく、同じオブジェクトを指しているということです

実務でハマった例

1. DTOを一時的に加工してListに詰めるケース

  • バッチ処理で「一時DTO」を作ってListに詰める → 元の変数を触ったら意図せずList側も変わる

2. ループで同じインスタンスを使い回すケース

Sample.java
List<UserDto> list = new ArrayList<>();
UserDto dto = new UserDto("Alice");

for (int i = 0; i < 3; i++) {
    dto.name = "User" + i;
    list.add(dto);
}
System.out.println(list); // [User2, User2, User2]
text
[User2, User2, User2]
  • ループ内で同じdtoを使い回しているため、Listの中身はすべて同じ参照を指すことになる
  • その結果、最後に代入した値で上書きされてしまう

対策方法

様々な対策方法あると思いますが、一例として記載します。

1. 毎回新しいインスタンスを作る

同じオブジェクトを使い回すと、Listの中身が全部同じ参照になってしまいます。
解決策として、addするたびに新しいインスタンスを生成します。

Sample.java
List<UserDto> list = new ArrayList<>();

for (int i = 0; i < 3; i++) {
    list.add(new UserDto("User" + i)); // 毎回newする
}

System.out.println(list); // [User0, User1, User2]

2. イミュータブルにしてしまう

「後から書き換えられる」こと自体がバグの原因になるなら、そもそも変更できないようにする。

Sample.java
class UserDto {
    private final String name; // finalで不変にする

    public UserDto(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

3. コピーを作ってからaddする

「元のオブジェクトは残したいけど、似た内容をListに入れたい」場合は、コピーを作ってからaddする。

Sample.java
class UserDto {
    private final String name;

    public UserDto(String name) {
        this.name = name;
    }

    // コピーコンストラクタ
    public UserDto(UserDto other) {
        this.name = other.name;
    }

    public String getName() { return name; }
}

UserDto original = new UserDto("Alice");
List<UserDto> list = new ArrayList<>();

list.add(new UserDto(original)); // コピーをadd

まとめ

  • Listに格納されるのは「参照」である
  • そのため、元のオブジェクトを変更するとListの中身も変わる
  • 対策としては
  1. 毎回新しいインスタンスを作る
  2. イミュータブルにする
  3. コピーを作ってからaddする
  • どの方法を選ぶかは「処理の目的」と「安全性の優先度」によって決めると良い
0
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?