0

I have the following custom iterator class that iterates over a list of Accounts (accounts):

public class CustomIterator implements Iterator<Account>{

private List<Account> accounts; private Integer currentIndex;

public CustomIterator(List<Account> accounts){ this.accounts = accounts; this.currentIndex = 0; }

public Boolean hasNext(){ return currentIndex < accounts.size(); }

public Account next(){ if(hasNext()) { return accounts[currentIndex++]; } else { throw new NoSuchElementException('Iterator has no more elements.'); } }

public void add(Account acc) {
    accounts.add(acc);
}

}

I would like to be able to insert elements into accounts after instantiating the iterator, so I defined an instance method called add(Account) that takes an Account object as its only argument and appends it to accounts.

However, calling this method results in an error:

List<Account> accs = [SELECT Id, Name, NumberOfEmployees FROM Account LIMIT 3];

Iterator<Account> iter = new CustomIterator(accs);

Account acc = [SELECT Id, Name, NumberOfEmployees FROM Account LIMIT 1];

iter.add(acc); // Error: Method does not exist or incorrect signature: void add(Account) from the type System.Iterator<Account>

Is this by design or am I missing something in the declaration of add or accounts? (A similar example makes use of the global modifier instead of public. Here, accounts is declared as a read/write property and does not have any modifiers.)

I've read that classes that implement an interface can have their own methods in addition to those declared in the interface, so I'm not sure what's causing the error.

  • Your method only exists on your custom iterator class, not on the Iterator interface. You need to have iter of the correct class to call the method. – Phil W Mar 05 '23 at 15:21
  • That said, since the iterator holds reference to the underlying list, changes to the list are immediately visible to the iterator. If, however, entries are added earlier in the list, your iterator can return the same value next time it is called since it tracks an index which doesn't change with inserts and removals... – Phil W Mar 05 '23 at 15:25

1 Answers1

1

The compiler doesn't know that Iterator<Account> is a CustomIterator. This is a feature of object-oriented programming when using polymorphism such as Interfaces or Class Inheritance. You can think of it as "all CustomIterator instances are Iterator<Account>, but not all Iterator<Account> are also CustomIterator." Since the compiler cannot statically verify that iter is actually a CustomIterator, you need to tell the compiler that iter is indeed a CustomIterator. You do this through a "cast."

To actually call a class-specific method, the compiler/runtime needs to know that the object being used is the correct type.

List<Account> accs = [SELECT Id, Name, NumberOfEmployees FROM Account LIMIT 3];
Iterator<Account> iter = new CustomIterator(accs);
Account acc = [SELECT Id, Name, NumberOfEmployees FROM Account LIMIT 1];
((CustomIterator)iter).add(acc);

Or, written out a bit more clearly:

List<Account> accs = [SELECT Id, Name, NumberOfEmployees FROM Account LIMIT 3];
Iterator<Account> iter = new CustomIterator(accs);
Account acc = [SELECT Id, Name, NumberOfEmployees FROM Account LIMIT 1];
CustomIterator it = (CustomIterator)iter;
it.add(acc);

This is to let the compiler know that we know that iter is indeed a CustomIterator. You'll get a runtime-exception if this is not true (but, of course, we know it is).

Note that when you're going from the class to an interface, you no longer need to cast:

List<Account> accs = [SELECT Id, Name, NumberOfEmployees FROM Account LIMIT 3];
CustomIterator iter = new CustomIterator(accs);
Account acc = [SELECT Id, Name, NumberOfEmployees FROM Account LIMIT 1];
iter.add(acc);
// no cast needed, compiler knows the data is compatible.
Iterator<Account> it = iter; 
sfdcfox
  • 489,769
  • 21
  • 458
  • 806
  • Thank you very much for the detailed explanation @sfdcfox. Is casting to the concrete class common in such cases? I worry it's too hacky, but I don't know enough Apex/OOP yet to know what to use in its place. – user369137 Mar 05 '23 at 18:16
  • 1
    @user369137 Casting is sometimes unavoidable, particularly when working with interfaces, but it is often possible to use the concrete class preferentially, since all concrete class instances are also instances of that interface. The last code block in my answer demonstrates this. Note that iterators specifically are usually paired with an Iterable as well; the iterable conceptually holds the data, while the iterator makes a once-over pass over that data. – sfdcfox Mar 05 '23 at 18:36