4

Grouping records is a pretty common pattern, enough so to write a simple library for it. Something like:

public class GroupBy
{
    public Map<Id, List<SObject>> ids(SObjectField field, List<SObject> records)
    {
        Map<Id, List<SObject>> grouped = new Map<Id, List<SObject>>();
        for (SObject record : records)
        {
            Id parentId = (Id)record.get(field);
            if (!grouped.containsKey(parentId))
                grouped.put(parentId, new List<SObject>());
            grouped.get(parentId).add(record);
        }
        return grouped;
    }
}

That's all well and good, but I recently found someone who had abused this code (likely accidentally) to produce a statement I was sure should produce a runtime exception:

List<ThatObject__c> those; // = ...;
Map<Id, List<ThisObject__c>> byParent = GroupBy.ids(ThatObject__c.Parent__c, those);

So now we have a series of List<ThisObject__c> whose contents are all ThatObject__c records!


You can even simplify the behavior and run the following in Execute Anonymous without issue:

static List<SObject> genericize(List<SObject> input)
{
    List<SObject> output = new List<SObject>();
    for (SObject record : input) output.add(record);
    return output;
}    
List<ThisObject__c> these = genericize([SELECT Id FROM ThatObject__c]);

This sure seems like it should generate a runtime exception. Again, this boils down to the platform allowing a List<SomeType> where none of the elements are actually an instance of SomeType. I imagine you could extend the finding to interfaces and abstract classes. Is this behavior a bug?

4

It's mostly "working as designed."

Logically, there's no problem saying this:

SObject[] c = new Contact[0];

This allows you to work on any type of record without knowing in advance that they're contacts. It's convenient to do that. In fact, that's the primary reason why this is supported. It is expected that you'll check the data type of each record before you do anything with it, or use the generic SObject methods.

The inverse is also true:

Contact[] c = new SObject[0];

This is convenient because it lets you convert from a generic list.

Contact[] a = new Contact[0];
SObject[] b = a;
Contact[] c = b;

However, note that specific list types will cause exceptions if they don't match:

Contact[] a = new Contact[0];
SObject[] b = a;
Account[] c = b; // Error here, Contacts are not Accounts.

In other words, the system is mostly functional.

The generic SObject[] type can always be assigned from any type of record, can hold any type of record, and can be assigned to a specific type of record list so long as the list is either generic or matches the specific type of the list being assigned to.

For that reason, I usually recommend that you don't write functions that build generic lists. It can introduce an entire class of errors. Instead, I prefer to use a specific type, when possible:

static SObject[] doWork(SObject[] records, Type listType) {
    SObject[] results = (SObject[])Type.newInstance();
    for(SObject record: records) {
        results.add(record);
    }
    return results;
}
// This produces an error
That__c[] records = doWork([SELECT Id FROM This__c], List<This__c>.class);

Alternatively (my preferred method):

static SObject[] doWork(SObject[] records) {
    SObject[] results = records.clone();
    results.clear(); // Retains the type of records
    for(SObject record: records) {
        results.add(record);
    }
    return results;
}
// This produces an error
That__c[] records = doWork([SELECT Id FROM This__c]);

Just remember that bugs like this can happen, mostly because without these considerations in place, it'd be harder to write generic algorithms.

Avoid creating an actual data type of SObject[] if you can avoid it. Obviously, it has real uses, like being able to insert disparate types of records in fewer DML statements, like this:

insert new SObject[] { new Contact(LastName='Test'), new Account(Name='Test') };

This only counts as one DML statement, even though we inserted two types of records.

Keep in mind that even in your example, though, the moment you try to loop over those records or do pretty much anything to them, you'd either end up with compiler errors or runtime errors. You'll want to experiment to see the many ways your code can crash.

4
  • * The generic SObject[] type can always be assigned from any type of record, can hold any type of record, and can be assigned to a specific type of record list so long as the list is either generic or matches the specific type of the list being assigned to.* But...that last is what this question just disproved, no? – Adrian Larson Sep 30 '16 at 20:19
  • @AdrianLarson No, it didn't disprove anything. Like I said, List<SObject> is a generic data type; it has special properties that the specialized types do not have. You can always assign a true List<SObject> to a specialized type, but the moment you try to do anything with an invalid type of record, it'll crash. If they fixed this logic, it'd break all the legitimate uses for SObject. – sfdcfox Sep 30 '16 at 20:28
  • sfdcfox -- the alternative example last line -- should be ?? That__c[] records = doWork([SELECT Id FROM This__c]); ?? – cropredy Sep 30 '16 at 22:37
  • @cropredy Yeah, I obviously copy-pasted and missed a part. I went ahead and fixed it. – sfdcfox Sep 30 '16 at 22:39
-1

Well, by doing assignment for these you just assigning a reference to output list and it keeps the same type of List<SObject>. Try to do List<Account> these = new List<Account> (genericize([SELECT Id,StageName FROM Opportunity LIMIT 10])); and you'll get Invalid initial value type List<SObject> for List<Account> error

2
  • I don't think this answer really addresses the question. I'm never using a constructor like new List<SomeType>(...) anywhere above, only empty constructors. – Adrian Larson Sep 30 '16 at 17:32
  • @AdrianLarson Now I get this :) System.debug(these[0].getSObjectType()); giving an error, so I'd say it's a bug. – o-lexi Sep 30 '16 at 19:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.