4

While working on some code for a utility method to remove values from a List, I wrote this method signature, which accepts a List<Object>:

public static List<sObject> Exclude(List<sObject> records, List<Object> excluded, String field)

Later, I added another method signature to call this function using a Set<Object>:

public static List<sObject> Exclude(List<sObject> records, Set<Object> excluded, String field)

Both of these are functionally identical, except for the List/Set difference.

Trying to call the method using a Set<Id> results in this error:

Line: 4, Column: 26

Method does not exist or incorrect signature: void Exclude(List<Account>, Set<Id>, String) from the type TestUtility

However, when calling the method using a List<Id>, correctly calls the method using the List<Object> signature. Creating a new List<Id> based on the Set also calls the correct method.

Why does the behavior of apex differ between these two list types?


You can test this from your developer console using the below class, and this snippet:

List<Account> accounts = [SELECT Id, Name FROM Account]; 
Set<Id> failedIds = new Set<Id>(); 

System.debug(TestUtility.Exclude(accounts, failedIds, 'Id')); // Fails
System.debug(TestUtility.Exclude(accounts, new List<Id>(failedIds), 'Id')); // Works

TestUtility Class

public class TestUtility {
    public static List<sObject> Exclude(List<sObject> records, List<Object> excluded, String field) {
        return null;  
    }
    
    public static List<sObject> Exclude(List<sObject> records, Set<Object> excluded, String field) {
        return Exclude(records, new List<Object>(excluded), field);
    }
}

Looks like this really isn't possible without some clever type avoidance:

System.debug(someId instanceof Object); // always true 
System.debug(new List<Id>() instanceof Object); // always true 
System.debug(new List<Id>() instanceof List<Object>); // always true
System.debug(new Set<Id>() instanceof Object); // always true

System.debug(new Set<Id>() instanceof Set<Object>); // Always false  

Operation instanceof is always false since an instance of Set<Id> is never an instance of Set<Object>

4
  • any particular reason you are using Object instead of sObject for your second param type? Commented Oct 31, 2017 at 15:57
  • Fun fact: System.debug(TestUtility.Exclude(accounts, new List<Id>(failedIds), 'Id')); fails for me from DEV console
    – Raul
    Commented Oct 31, 2017 at 15:57
  • @JesseMilburn the call looks like TestUtility.Exclude(accounts, new List<String>{ 'Seattle', 'Washington, 'Austin' }, 'BillingCity'); Commented Oct 31, 2017 at 15:58
  • @RahulSharma, I'm not able to reproduce, can you be more specific about the error you get? Commented Oct 31, 2017 at 16:00

1 Answer 1

8

The Set collection type does not allow you to implicitly cast, unlike with List. You can, however, overload the signature to work around the issue. You have to do some tricky maneuvering to genericize the collection, but it can be done.

public class Demo
{
    public Boolean doStuff(Set<Object> values)
    {
        return values.isEmpty();
    }
    public Boolean doStuff(Set<String> values)
    {
        return doStuff(new Set<Object>((List<Object>)new List<String>(values)));
    }
    // same pattern for other primitive types
}

You can see an example of this logic in the Selector library (specifically the isIn/notIn filters). In fact, you could even just use that library:

List<Account> successes = Select.Field.notIn('Id', failedIds).filter(accounts);

Or with your city example:

Set<String> cities = new Set<String> { 'Seattle', 'Washington', 'Austin' };
List<Account> filterResults = Select.Field.isIn(Account.BillingCity, cities)
    .filter(accounts);
4
  • 1
    It's unfortunate that overloading is required, and it's really all because type inheritance is broken. Maybe one day they'll get around to fixing it, but they seem to have at least patched the parts that were causing exceptions (e.g. up until recently, Set<Object> a = new Set<String>() would compile but cause runtime errors if you used an invalid type). I don't see why they can't just make it work the way it does in Java.
    – sfdcfox
    Commented Oct 31, 2017 at 16:51
  • I think most of my questions are about type issues which wouldn't be a problem in other languages... for this case, I'll require the caller to cast to a list to keep the class lightweight- but next time, I think I'll rely on a library with tests included over writing my own! Commented Oct 31, 2017 at 17:55
  • Indeed this is a wheel which is already built, and well.
    – Adrian Larson
    Commented Oct 31, 2017 at 17:57
  • 2
    This answer doesn't address why generics work this way... in particular, a function that takes a Set<Object> can add arbitrary objects to it, which wouldn't be possible with a Set<Id>. sfdcfox alludes to how it works in Java, where you'd need to accept a Set<? extends Object>.
    – Dan Staley
    Commented Oct 31, 2017 at 21:47

You must log in to answer this question.

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