Id
, you can use multiple fields combined as a key. Imagine needing to track records based on both AccountId
and ContactId
—a wrapper lets you do that.When working with Apex in Salesforce, Maps (key-value pairs) are incredibly useful for storing and retrieving data efficiently. But have you ever tried using a custom wrapper class as a key in an Apex Map? That’s where things get really interesting. Let’s break it down.
Why use a wrapper class as a key instead of a standard type like String
or Id
? Here’s why:
Id
, you can use multiple fields combined as a key. Imagine needing to track records based on both AccountId
and ContactId
—a wrapper lets you do that.Id
isn't enough. A wrapper ensures uniqueness based on multiple attributes.Let’s start with a basic wrapper class. Suppose we need to map Salesforce Contacts to their corresponding Accounts using a combination of AccountId
and ContactId
.
public class AccountContactKey {
public Id accountId;
public Id contactId;
public AccountContactKey(Id accountId, Id contactId) {
this.accountId = accountId;
this.contactId = contactId;
}
public Boolean equals(Object obj) {
if (obj instanceof AccountContactKey) {
AccountContactKey other = (AccountContactKey) obj;
return this.accountId == other.accountId && this.contactId == other.contactId;
}
return false;
}
public Integer hashCode() {
return (String.valueOf(accountId) + String.valueOf(contactId)).hashCode();
}
}
accountId
and contactId
.accountId
and contactId
are considered equal.accountId
and contactId
to work correctly as a key in a Map.Now that we have our wrapper class, let’s see it in action.
Map<AccountContactKey, Contact> contactMap = new Map<AccountContactKey, Contact>();
List<Contact> contacts = [SELECT Id, FirstName, LastName, AccountId FROM Contact WHERE AccountId != NULL];
for (Contact con : contacts) {
AccountContactKey key = new AccountContactKey(con.AccountId, con.Id);
contactMap.put(key, con);
}
// Retrieve a specific contact
Id testAccountId = '001XYZ000123';
Id testContactId = '003XYZ000123';
AccountContactKey searchKey = new AccountContactKey(testAccountId, testContactId);
if (contactMap.containsKey(searchKey)) {
Contact retrievedContact = contactMap.get(searchKey);
System.debug('Contact found: ' + retrievedContact.FirstName + ' ' + retrievedContact.LastName);
}
AccountId
.AccountContactKey
for each contact and use it as a key in the contactMap
.AccountContactKey
to look up a specific contact.Imagine needing to map Opportunities based on both AccountId
and StageName
. Instead of using a complex nested map (Map<Id, Map<String, Opportunity>>
), a wrapper makes it simpler:
public class AccountStageKey {
public Id accountId;
public String stageName;
public AccountStageKey(Id accountId, String stageName) {
this.accountId = accountId;
this.stageName = stageName;
}
public Boolean equals(Object obj) {
if (obj instanceof AccountStageKey) {
AccountStageKey other = (AccountStageKey) obj;
return this.accountId == other.accountId && this.stageName == other.stageName;
}
return false;
}
public Integer hashCode() {
return (String.valueOf(accountId) + stageName).hashCode();
}
}
// Usage Example
Map<AccountStageKey, List<Opportunity>> oppMap = new Map<AccountStageKey, List<Opportunity>>();
List<Opportunity> opps = [SELECT Id, Name, AccountId, StageName FROM Opportunity];
for (Opportunity opp : opps) {
AccountStageKey key = new AccountStageKey(opp.AccountId, opp.StageName);
if (!oppMap.containsKey(key)) {
oppMap.put(key, new List<Opportunity>());
}
oppMap.get(key).add(opp);
}
Using custom wrapper classes as keys in Apex Maps is a game-changer. It allows for complex multi-field keys, improves code readability, and prevents collisions when working with related records. Whether you're mapping opportunities by stage, tracking unique relationships, or ensuring efficient bulk processing, wrapper keys make your life easier. Try them out in your next project, and you'll wonder how you ever managed without them!