function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
TehNrdTehNrd 

Any negatives to declaring class variables as static?

I feel like a total n00b asking this question as the concept of public, global, static keywords seems pretty basic but are there any negatives to declaring class variables as static?

 

I have an asynchronous method that needs to access variables in the class but the only way for this static @future method to see the variables is to make them static. Is this acceptable? Is there a better way? See example below.

 

public class EmailDomains {

//Only way for @future method can see these variables is to make then static
static List<Account> accountsToUpdate = new List<Account>();
static Set<String> currentEmailDomains;
static Set<String> emailDomainsAfterClean;

//Called from trigger, visualforce, etc
@future
public static void rescanAccounts(Set<Id> acctIDs){
//Do cool stuff
//need to access list variables above
}
}

 

I suppose when you are self taught you miss some programming basics.

 

Thanks,

Jason

Message Edited by TehNrd on 02-05-2010 01:08 PM
Best Answer chosen by Admin (Salesforce Developers) 
AcronymAcronym

Here is maybe a different approach.

 

 

public with sharing class TehNerd { //Called from batch, takes list of Accounts public static void updateDomainOnAccount(List<Account> accts){ if (accts.size() > 0) { Database.update(processAccounts(accts)); } } //Called from trigger, visualforce, etc, takes Set of Account Ids @future public static void rescanAccounts(Set<Id> acctIDs){ if (acctIDs.size() > 0) { Database.update(processAccounts( [select Id, Email_Domains__c, (Select Email from Contacts where Email != null) from Account where Id IN :acctIDs] )); } } public static List<Account> processAccounts(List<Account> accounts) { List<Account> accountsToUpdate = new List<Account>(); if (accounts.size() > 0) { for (Account acct : accounts) { //Reset variables for each account iteration Set<String> currentEmailDomains = new Set<String>(); Set<String> emailDomainsAfterClean = new Set<String>(); //Loop through all contacts for(Contact c : acct.Contacts){ //Add stuff to accountsToUpdate //Also access sets and lists above } } } return accountsToUpdate; } }

 

 

 

All Answers

TehNrdTehNrd

I do know that making them static wouldn't allow them to be visible from another class. For example, the code below won't work but are there any other restrictions I need to be aware of?

 

//This won't work if accountsToUpdate is declared static

EmailDomains job = new EmailDomains();
job.accountsToUpdate.clear();

Message Edited by TehNrd on 02-05-2010 02:50 PM
cpetersoncpeterson

Even the value of static variables doesn't persist by the time a @future method gets around to executing. Your static variables are going to be empty sets to the rescanAccounts method. With @future you have to pass in all variables you want to use within the method, more like this:

 

public class EmailDomains { //Only way for @future method can see these variables is to make then static List<Account> accountsToUpdate = new List<Account>(); static Set<String> currentEmailDomains; static Set<String> emailDomainsAfterClean; //Called from trigger, visualforce, etc @future public static void rescanAccounts(Set<Id> acctIDs, Set<String> currentEmailDomains, Set<String> emailDomainsAfterClean){ //Do some SOQL on the IDs passed in//Do cool stuff //need to access list variables above }//A nice method out of my Util class public static Set<id> getIds(sObject[] objList){ Set<id> retval = new Set<id>(); for(sobject thisObj: objList){ retval.add(thisObj.id); } return retval; }}

 

 

As for your reply post:

Static variables, and static methods can't be called from an instance. This doesn't mean they can't be accessed from another class (unless they're private).

 

E.g. to access accountsToUpdate in your example you'd do:

EmailDomains.accountsToUpdate.clean();

 

Static methods are similar, EmailDomains.staticMethod(args);  

Interesting, if I put linebreaks into the code sample I get

Request Entity Too Large

The requested resource
/sforce/board/post
does not allow request data with GET requests, or the amount of data provided in the request exceeds the capacity limit.
Sorry about the formatting! 
Message Edited by cpeterson on 02-05-2010 12:52 PM
TehNrdTehNrd

Yes, they will be empty when rescanAccounts startes to execute which is fine as they need to start out empty, but it appears the code inside the rescanAccounts() method can populate, re instantiate, access values in these sets and lists with no issues. If they were declared static the code inside the rescanAccounts() method would not be able to access them.

 

This appears to work okay, so I think I am good.

 

public class EmailDomains {

//Only way for @future method can see these variables is to make then static
static List<Account> accountsToUpdate = new List<Account>();
static Set<String> currentEmailDomains;
static Set<String> emailDomainsAfterClean;

//Called from trigger, visualforce, etc
@future
public static void rescanAccounts(Set<Id> acctIDs){
currentEmailDomains = new Set<String>();
currentEmailDomains.add('blah');
accountsToUpdate.add(new Account(Name='test'));

  update accountsToUpdate;

}
}

 

-Jason
Message Edited by TehNrd on 02-05-2010 02:51 PM
AcronymAcronym

Hey TehNerd,

 

Why aren't the list defined at the method scope?

TehNrdTehNrd

@ I knew this question was coming. Here is a more encompassing view of the class.

 

This thread also includes some info as to why it is setup like this:

http://community.salesforce.com/sforce/board/message?board.id=apex&thread.id=20347&view=by_date_ascending&page=2

 

public class EmailDomains {

static List<Account> accountsToUpdate = new List<Account>();

//Called from batch, takes list of Accounts
public static void updateDomainOnAccount(List<Account> accts){
for(Account a : accts){
processAccount(a);
}
update accountsToUpdate;
}

//Called from trigger this takes Set of Account Ids
@future
public static void rescanAccounts(Set<Id> acctIDs){
for(Account a : [select Id, Email_Domains__c, (Select Email from Contacts where Email != null) from Account where Id IN :acctIDs]){
processAccount(a);
}
update accountsToUpdate;
}

//Didn't want to duplicate this code in each method above.
public static void processAccount(Account acct){
//Loop through all contacts
for(Contact c : acct.Contacts){
//Add stuff to accountsToUpdate

}
}
}

 

Related Batch class:

global class EmailDomainBatch implements Database.Batchable<sObject>{

public string query = 'select Id, Name, Email_Domains__c, (Select Email from Contacts where Email != null) from Account';

global Database.QueryLocator start(Database.BatchableContext bc){
return Database.getQueryLocator(query);
}

global void execute(Database.BatchableContext bc, List<sObject> objects){
List<Account> accts = new List<Account>();
for(sObject s : objects){
Account a = (Account)s;

/*Only process acounts that have contacts, there is a SFDC issue with .size() on list of child
records that causes it to fail with large lists */
try{
if(a.Contacts[0] != null){
accts.add(a);
}
}catch(exception e){}
}

//Send Accounts to EmailDomains.updateDomainOnAccount() for processing
if(accts.size() > 0){
EmailDomains.updateDomainOnAccount(accts);
}
}

global void finish(Database.BatchableContext bc){
system.debug('All done.');
}
}

 

- Jason
Message Edited by TehNrd on 02-05-2010 02:28 PM
Mike LeachMike Leach

Not sure of the specifics of class/variable state when using @future. But in general, mixing anything async with static is not thread safe and will likely break when concurrency is introduced (#CodeSmell).

 

Can you create an instance of EmailDomain and pass in context to a constructor? 

TehNrdTehNrd

I've tweeked the code above to remove the string Sets as those are only relevant for the processAccounts() context.

 

I know the code above is a bit if an eye sore so I will try to add some more details. I have a method that looks at accounts and processes all related contacts. This can be called from two places; batch apex and triggers. 

 

With batch apex I can send over all of the accounts and related contacts for processing and everything works great. Issues start to arise when I want to invoke the same logic from a trigger. I want this to be asyncronous for higher limits. @future methods must be static. Follow object oriented techniques I wanted to keep all of the processing code seperate so it wouldn't be duplicated in the updateDomainOnAccount() and rescanAccounts() methods. Problem is the processAccount() needs to add Accounts to a list for updating and the only way the @future method can see this variable is to make it static.

 

Thats long winded, sorry it's not easier to understand.

 

-Jason

 

AcronymAcronym

Here is maybe a different approach.

 

 

public with sharing class TehNerd { //Called from batch, takes list of Accounts public static void updateDomainOnAccount(List<Account> accts){ if (accts.size() > 0) { Database.update(processAccounts(accts)); } } //Called from trigger, visualforce, etc, takes Set of Account Ids @future public static void rescanAccounts(Set<Id> acctIDs){ if (acctIDs.size() > 0) { Database.update(processAccounts( [select Id, Email_Domains__c, (Select Email from Contacts where Email != null) from Account where Id IN :acctIDs] )); } } public static List<Account> processAccounts(List<Account> accounts) { List<Account> accountsToUpdate = new List<Account>(); if (accounts.size() > 0) { for (Account acct : accounts) { //Reset variables for each account iteration Set<String> currentEmailDomains = new Set<String>(); Set<String> emailDomainsAfterClean = new Set<String>(); //Loop through all contacts for(Contact c : acct.Contacts){ //Add stuff to accountsToUpdate //Also access sets and lists above } } } return accountsToUpdate; } }

 

 

 

This was selected as the best answer
TehNrdTehNrd
Thanks! That is much cleaner. Sometimes the update will execute on an empty list but that is ok.