2

Assigning a sobject map to a specific sobject-type is not possible, i.e

Map<Id,SObject> sobjectById = new Map<Id,SObject>();
sobjectById.put(a.Id,a);

Map<Id,Account> accountById = (Map<Id,Account>) sobjectById;

This is expected and a solution is documented here

However, it is possible to assign a map of sobject lists to a map of sobject-specific types, for example

Map<Id,List<SObject>> sobjectListsById = new Map<Id,List<SObject>>();
sobjectListsById.put(a.Id,new List<SObject>{a});

Map<Id,List<Account>> accountListsById = sobjectListsById;

Why is the latter allowed?

EDIT

To add more details, I think this is related to lists. For example, I cannot assign an sObject variable to an Account variable

Account a = [SELECT Id FROM Account LIMIT 1];
Sobject s = a;
Account a2 = s;//this will thrown an error unless I cast s to an Account

However, I can assign a list of sobjects to a list of accounts

List<SObject> sList = new List<sObject>();
List<Account> aList = sList;

Again, why is this possible? Shouldn't we need to cast sList to List?

5
  • Type casting the ( Map<Id,List<Account>>)sobjectListsById should work did it not?
    – Abhijith E
    Mar 12, 2020 at 11:05
  • It works with or without casting. That's the question. Why does this work? If we look at the first example (map to sobject instead of map to sobject list), type casting doesn't work. Mar 12, 2020 at 11:07
  • I am able to typecast with the same scenario as the first example, second scenario doesnot need type casting... I have noticed this with List<sObject> which would not need type casting but Map<Id,sObject> would need it I would also be intrested in knowing why...
    – Abhijith E
    Mar 12, 2020 at 11:22
  • I think it works because it's easier, kind of. This way you don't need to cast Trigger.new for example
    – ytiq
    Mar 12, 2020 at 11:39
  • 1
    Semi-related, I went in to further details about how Types are actually broken, and have been since time immemorial. You'll probably want to read this. Be careful when using that technique, as it can easily cause runtime exceptions (e.g. trying to put a Contact into an Account list that was stored as an sObject list).
    – sfdcfox
    Mar 12, 2020 at 12:37

3 Answers 3

4

The question answer you linked was for when Key in Map was String, if its id then

Even the first one is allowed. Conversion of generic Sobject to the proper object

Account acc = new Account(Id='0014H00002LlXAj');
Map<Id,SObject> sobjectById = new Map<Id,SObject>();
sobjectById.put(acc.Id,acc);

Map<Id,Account> accountById = new Map<Id,Account>((List<Account>) sobjectById.values());
4

Generics in Apex are really rather "smoke and mirrors" - they are not applied fully and consistently, and you've found somewhere where the language effectively ignores the typing.

Another example is the fact that you can do something like:

public with sharing class MyBatch implements Database.Batchable<SObject> {
    ...
    public void execute(Database.BatchableContext context,
            List<MyCustomObject__c> objects) {
      // Note that the class is templated on SObject but this method is
      // templated on MyCustomObject__c. Logically this should fail to "compile"
    }
}
3

There seems to be a misunderstanding of the answer you referenced. What was done there worked because up-casting. The type when returned in his example is still of Map<String,Account> it just referenced as Map<String,SObject>. The fact the key is a string is inconsequential. Let me show you a simplified example of what was done without the dynamic work which isn't necessary to show my point.

Map<Id,Account> m = new Map<Id,Account>();
Account a = new Account(Name = 'number 1');
insert a;
m.put(a.Id, a);
System.debug(m);
Map<Id,SObject> mm = (Map<Id,SObject>)m;
Map<Id,Account> accountById = (Map<Id,Account>) mm;

What you are doing is this..

Map<Id,SObject> m = new Map<Id,SObject>();
Account a = new Account(Name = 'number 1');
insert a;
m.put(a.Id, a);
System.debug(m);
Map<Id,Account> mm = (Map<Id,Account>)m;

This will not work because you are up-casting to not its declared type. Down-casting as I said is safe because Account is an SObject but an SObject is not a Account. In short up-casting is always safe, down-casting is inherently unsafe and will only work if the underlying type is the type you are casting to. Other languages allow you to down-cast more freely (though its not recommended unless you know what you are doing).. might be a java thing someone might be able to tell me.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .