7

Set implements the Iterable Interface based on the latest Salesforce release (API 58).

I tried to refactor my code to make it simpler and use Iterable<Object> as a supertype for a List<Object> and Set<Object>.

Step 1

I tried do do something like that:

public interface Filter {
    Filter isIn(Iterable<Object> inList);
}

public class QFilter implements Filter {

    public Filter isIn(Iterable<Object> inList) {
        return this;
    }
}

Filter myFilter = new QFilter();

myFilter.isIn(new List<String>{ 'Test1', 'Test2' }); // ERROR: Method does not exist or incorrect signature: void isIn(List<String>) from the type Filter
myFilter.isIn(new List<Decimal>{ 1, 2 }); // ERROR: Method does not exist or incorrect signature: void isIn(List<Decimal>) from the type Filter
myFilter.isIn(new List<Object>{ 1, 2, 'Test1', 'Test2' }); // WORKS AS EXPECTED

myFilter.isIn(new Set<String>{ 'Test1', 'Test2 '}); // WORKS AS EXPECTED
myFilter.isIn(new Set<Decimal>{ 1, 2 }); // WORKS AS EXPECTED
myFilter.isIn(new Set<Object>{ 1, 2, 'Test1', 'Test2 ' }); // WORKS AS EXPECTED

I got Method does not exist or incorrect signature errors.

Step 2

In the next step I added isIn(List<Object> inList), which make my code working.

public interface Filter {
    Filter isIn(Iterable<Object> inList);
    Filter isIn(List<Object> inList); // NEW METHOD
}

public class QFilter implements Filter {
    // Method below covers isIn(Iterable<Object>) and isIn(List<Object>)
    public Filter isIn(Iterable<Object> inList) {
        return this;
    }
}

Filter myFilter = new QFilter();

myFilter.isIn(new List<String>{ 'Test1', 'Test2' }); // WORKS AS EXPECTED
myFilter.isIn(new List<Decimal>{ 1, 2 }); // WORKS AS EXPECTED
myFilter.isIn(new List<Object>{ 1, 2, 'Test1', 'Test2' }); // WORKS AS EXPECTED

myFilter.isIn(new Set<String>{ 'Test1', 'Test2 '}); // WORKS AS EXPECTED
myFilter.isIn(new Set<Decimal>{ 1, 2 }); // WORKS AS EXPECTED
myFilter.isIn(new Set<Object>{ 1, 2, 'Test1', 'Test2 ' }); // WORKS AS EXPECTED

Step 3

I checked instanceof for all of the examples.

Map<String, Boolean> caseToIsInstance = new Map<String, Boolean>{
    'List<String> instanceOf List<Object>' => true, // Operation instanceof is always true since an instance of List<String> is always an instance of List<Object>
    'List<Decimal> instanceOf List<Object>' => true, // Operation instanceof is always true since an instance of List<Decimal> is always an instance of List<Object>
    'List<Object> instanceOf Iterable<Object>' => true, // Operation instanceof is always true since an instance of List<Object> is always an instance of System.Iterable<Object>
    'List<String> instanceOf Iterable<Object>' => new List<String>() instanceOf Iterable<Object>,
    'List<Decimal> instanceOf Iterable<Object>' => new List<Decimal>() instanceOf Iterable<Object>,
    'Set<String> instanceOf Set<Object>' => false, // Operation instanceof is always false since an instance of Set<String> is never an instance of Set<Object>
    'Set<Decimal> instanceOf Set<Object>' => false, // Operation instanceof is always false since an instance of Set<Decimal> is never an instance of Set<Object>
    'Set<Object> instanceOf Iterable<Object>' => true, // Operation instanceof is always true since an instance of List<Object> is always an instance of System.Iterable<Object>
    'Set<String> instanceOf Iterable<Object>' => true, // Operation instanceof is always true since an instance of Set<String> is always an instance of System.Iterable<Object>
    'Set<Decimal> instanceOf Iterable<Object>' => true // Operation instanceof is always true since an instance of Set<Decimal> is always an instance of System.Iterable<Object>
};

System.debug(LoggingLevel.DEBUG, ': ' + JSON.SerializePretty(caseToIsInstance));
{
  "Set<Decimal> instanceOf Iterable<Object>" : true,
  "Set<String> instanceOf Iterable<Object>" : true,
  "Set<Object> instanceOf Iterable<Object>" : true,
  "Set<Decimal> instanceOf Set<Object>" : false,
  "Set<String> instanceOf Set<Object>" : false,
  "List<Decimal> instanceOf Iterable<Object>" : true,
  "List<String> instanceOf Iterable<Object>" : true,
  "List<Object> instanceOf Iterable<Object>" : true,
  "List<Decimal> instanceOf List<Object>" : true,
  "List<String> instanceOf List<Object>" : true
}

But still e.g List<String> cannot be assigned to Iterable<Object>.

Iterable<Object> test1 = new Set<Decimal>(); // WORKS
Iterable<Object> test2 = new Set<String>(); // WORKS
Iterable<Object> test3 = new Set<Object>(); // WORKS
Iterable<Object> test4 = new List<Decimal>(); // Illegal assignment from List<Decimal> to System.Iterable<Object>
Iterable<Object> test5 = new List<String>(); // Illegal assignment from List<String> to System.Iterable<Object>
Iterable<Object> test6 = new List<Object>(); // WORKS
List<Object> test7 = new List<Decimal>(); //  WORKS
List<Object> test8 = new List<String>(); //  WORKS

Conclusion & Question

The Iterable<Object> and List<ConcreteType> have weird behavior. List<String> is instance of Iterable<Object>, but still those types are not compatible (cannot be assigned directly).

It looks that List<String> needs to be converted to List<Object> and after assignment to Iterable<Object> will work.

// ***** LIST *****
// DOES NOT WORK
Iterable<Object> iterableList = new List<String>();

// WORKS
List<Object> genericList = new List<String>();
Iterable<Object> iterableList = genericList;

// ***** SET *****
// WORKS
Iterable<Object> iterableSet = new Set<String>()

How is is possible? What am I missing? Why Set have different behavior? Is it just Apex thing?

1
  • 2
    Apex's built-in generics (not just collections either) are littered with inconsistencies. This will be one of the big headaches Salesforce will need to address if the introduce full support for generics as they plan.
    – Phil W
    Commented Jun 11, 2023 at 12:02

1 Answer 1

5

It has nothing to do with iterable, but rather to do with the fact that the type system is broken with regards to collections. I'm not going to reiterate the entire Q&A here, nor do I think this question is strictly a duplicate, but what you've run into is simply an artifact of Apex not always being able to determine if one collection type is a supertype of another type. Hopefully, this will be fixed around the time that Generics are due to come out.

Generics, in case you're unfamiliar with them, allow developers to specify types with any data type. For example, in C++ and other languages, we can write something like:

class Example<T> {
  T property;
}

Which we then use with a concrete type during usage:

Example<int> value = new Example<int>();
value.property = 42;

For now, you can use casting:

Iterable<Object> iterableList = (Iterable<Object>)new List<String>();

Or even:

myFilter.isIn((Iterable<Object>)new List<String>{ 'Test1', 'Test2' });

This will work fine, but be aware that Salesforce allows you to perform illegal assignments that can cause runtime errors:

List<Object> values = new List<String>(); // legal
values.add(42); // TypeException runtime error
1
  • Thank you @sfdcfox for clarification, it helped me a lot. Commented Jun 11, 2023 at 18:40

You must log in to answer this question.

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