Objects are indeed more expensive, but it's often worth it for maintenance reasons:
Your class encapsulates data which would be tedious to pass to a static method. E.g. if 'login' method needs to lookup user data from MySQL database & Mongo - then it's tedious to pass DB connections to each call:
public class LoginService{
public static login(String user,String password, DataSource mySQLConnections, MongoClient mongoClient) ...
This signature is hard to read, and worse: it's not flexible to polymorphism. if we ever migrate user data from mySQL+mongo to LDAP, then we'll need to fix each call to "login(user,password, LDAPClient)"
Now, you may still argue this can be solved statically:
public class LoginService{
private static DataSource mySQLConnections;
private static MongoClient mongoClient;
public static login(String user,String password) ...
// and if we ever migrate to LDAP, we just change the internal statics, without affecting the public signature:
public class LoginService{
private static LDDAPClient;
public static login(String user,String password) ...
Sometimes that's just good enough, but many programmers still feel it's limiting. For details read about Spring and "Dependency Injection", but the bottom line is - I might want to play around with LoginService, have several instances (one that uses LDAP, another that uses MySQL+Mongo), and switch them based on some administration command, or even just for testing. This boils down to an interface with different implementing classes, each encapsulating its required helpers:
public interface LoginService{
public login(String user,String password) ...
}
public class LoginServiceDb implements LoginService{
private DataSource mySqlConnections;
private MongoClient mongoClient;
public login(String user,String password)
// use the databases
...
}
public class LoginServiceLDAP implements LoginService{
private LDAPClient ldap;
public login(String user,String password)
// use LDAP
...
}
Also note: effect on the garbage collection might not be that bad (depending on your needs an how much 'real time' you are). Some Objects might be cached and re-used. Some objects are used so briefly that they remain in the "infant" generation which the GC handles reasonably well