LoginSignup
1
1

More than 1 year has passed since last update.

[Apex] Set<SObject> より Map<Id, SObject> を使おう

Posted at

Set<SObject>を使うべきでない理由

重複のないコレクションを作成するためにSetを使うことがありますが、同じ使い方をSObjectで行うと想定と異なる結果になることがあります。
Apex開発者ガイドにはSObjectのSetについて下記のように記述されています。

セットには一意の要素が含まれます。sObject の一意性は、オブジェクトの項目の比較によって判断されます。

SObjectが同一とみなされるかどうかは「項目が一致しているかどうか」になります。

別のインスタンスでも項目が一致していれば重複とみなされる

新たにnewしても項目が同じであれば重複とみなされてしまいます。

Account acc1 = new Account(Name = 'テストA');
Account acc2 = new Account(Name = 'テストA');

Set<Account> accSet = new Set<Account>{ acc1 };
System.assertEquals(true, accSet.contains(acc2)); // 項目が一致しているので重複とみなされています
accSet.add(acc2);
System.assertEquals(1, accSet.size());

同一IDのインスタンスでも同一とみなされない

同じIDのSObjectインスタンスを重複とみなしたいことがあるかと思いますが、項目が異なる場合、Idが同じでも別のものとみなされてしまいます。

Account acc = [SELECT Id FROM Account LIMIT 1];
Account acc1 = new Account(Id = acc.Id, Name = 'テストA');
Account acc2 = new Account(Id = acc.Id, Name = 'テストA', Phone = '03-0000-0000');

Set<Account> accSet = new Set<Account>{ acc1 };
System.assertEquals(false, accSet.contains(acc2)); // Idが一致していても他の項目が異なれば重複とみなされません
accSet.add(acc2);
System.assertEquals(2, accSet.size());

addしたあとの項目変更が反映されないケースがある

Set<SObject>重複してSetに追加された場合、1つ目のインスタンスがSetに格納されるようです。そのため、2つ目以降のインスタンスの項目を変更してもSetの内容には反映されません。

Account acc1 = new Account(Name = 'テストA');
Account acc2 = new Account(Name = 'テストA');
Set<Account> accSet = new Set<Account>();
accSet.add(acc1);
accSet.add(acc2);
System.assertEquals(1, accSet.size());

// acc1の項目を変更します
acc1.Name = 'テストB';
System.debug(accSet); // 当然、Setの要素に変更が反映されます: {Account:{Name=テストB}}
System.debug(acc2); // acc2の値は変わりません: Account:{Name=テストA}
System.assertEquals(false, accSet.contains(new Application(Name = 'テストA')));
Account acc1 = new Account(Name = 'テストA');
Account acc2 = new Account(Name = 'テストA');
Set<Account> accSet = new Set<Account>();
accSet.add(acc1);
accSet.add(acc2);
System.assertEquals(1, accSet.size());

// acc2の項目を変更します
acc2.Name = 'テストC';
System.debug(accSet); // Setの要素に変更が反映されません!: {Account:{Name=テストA}}
System.debug(acc1); // acc1の値も変わりません: Account:{Name=テストA}
System.assertEquals(true, accSet.contains(new Account(Name = 'テストA')));

代わりにMap<Id, SObject>を使う

同一Idのインスタンスを同じものとして扱いたいときはSet<SObject>の代わりにMap<Id, SObject>を使用できます。

Map<Id, Account> accMap = new Map<Id, Account>();
for (Opportunity opp : oppList) {
    if (opp.IsSomething__c && opp.AccountId != null) {
        accMap.put(opp.AccountId, new Account(Id = opp.AccountId, IsSomething__c = true));
    }
}
update accMap.values();

参考

1
1
0

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
1
1