2

Let's say I want to call a method like:

doSomething(a.b.c.d.e.f());

I know that this might be an expensive operation and a, a.b, a.b.c, a.b.c.d, and a.b.c.d.e might all be null, however I would rather deal with all these possibilities within doSomething() is there any way I can defer that operation until I want/need it?

(I'm 99% certain the answer is 'no', but I hope to learn that I'm wrong.)

1
  • 2
    This Idea would help, though not as nice as having a shortcut like ? null checks available in .Net. – sfdcfox Feb 25 '19 at 11:44
6

Not exactly, but you can do something like lazy evaluation by making use of the Callable interface (or rolling your own). Here's an example using Callable...

To start with, a test:

@IsTest private class LazyTest {

    @TestSetup static void setup() {
        List<Account> accounts = new List<Account> {
                new Account(Name = 'A'),
                new Account(Name = 'B')};

        insert accounts;

        List<Contact> contacts = new List<Contact> {
                new Contact(LastName = '1', AccountId = accounts[0].Id),
                new Contact(LastName = '2', AccountId = accounts[1].Id)};

        insert contacts;
    }

    @IsTest static void testBehavior() {
        Callable lazyContacts = new LazyContacts(new LazyAccounts('A'));
        // No calls made yet, so I can pass this around at no great cost
        System.assertEquals(0, Limits.getQueries());

        // OK, I've decided I need those contacts now...
        List<Contact> results = (List<Contact>)lazyContacts.call(null, null);
        System.assertEquals(2, Limits.getQueries());
        System.assertEquals(1, results.size());
        System.assertEquals('1', results[0].LastName);
    }
}

In the test, lazyContacts is not yet the Contacts I'm looking for. It's like a Callable that will provide the Contacts when I actually need them. It's parameterised by another Callable, so that inner one is also not invoked until you decide you actually need it.

The actual implementations:

public with sharing class LazyContacts implements Callable {

    private Callable lazyAccounts;

    public LazyContacts(Callable lazyAccounts) {
        this.lazyAccounts = lazyAccounts;
    }

    public Object call(String param1, Map<String, Object> param2) {
        return [
                SELECT LastName
                FROM Contact
                WHERE AccountId IN :(List<SObject>)lazyAccounts.call(null, null)
        ];
    }
}

public with sharing class LazyAccounts implements Callable {

    private String accountName;

    public LazyAccounts(String accountName) {
        this.accountName = accountName;
    }

    public Object call(String param1, Map<String, Object> param2) {
        return [
                SELECT Id
                FROM Account
                WHERE Name = :accountName
        ];
    }
}

You could certainly put together something like that in Apex. And you can do similar with iterators - only iterating when the data is actually requested.

The only downside is that Apex is missing a few language niceties that exist in Java and would make the code more succinct (and type-safe if we had templates).

2
  • 2
    Thanks for the response. Interesting approach. I'm not really sure what "Callable" brings to this conversation since, a you point out, we could roll our own interface and do so in a way that doesn't have unused parameters and could have greater type safety... Also doesn't solve the problem of a, a.b, etc possibly being null (but I guess that probably isn't solvable in Apex) – Brian Kessler Feb 25 '19 at 10:55
  • 3
    The advantage of callable is that it exists already, so no extra dependencies if you're using unlocked packaging. And it does keep everything very loosely coupled. It seems like you're asking two questions, really: 1. How can I defer evaluation? A: Use callable or something similar 2. How can I avoid lots of null-checks. 1 seems like the bigger problem to me, so that's what I answered. 2 can be done by replacing null with an appropriate actual object e.g. an empty list, or string, or a Null Object. Then make sure you don't do expensive things if the incoming data doesn't need them – Aidan Feb 25 '19 at 11:05

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.