Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

Java Java Data Structures Efficiency! Building the Model

When you have a number of class variables, can a catch all get() and set() method be used to access them?

I wonder if it's acceptable to use an enum consisting of member variable names as the argument for a single get() and a single set() method, as follows:

public class MyClass {
   private String mVariable1;
   private String mVariable2;
   private String mVariable3;
  public enum Attribute {VARIABLE1, 
                                        VARIABLE2, 
                                        VARIABLE3}

 public Object get(Attribute attribute) {
    switch (attribute) {
        case VARIABLE1:
             return mVariable1;
             break; //is this needed since return exists the method?
        case VARIABLE2:
             return mVariable2;
             break;
        case VARIABLE3:
            return mVariable3;
            break;
 }/switch(attribute)
 }//get(Attribute attribute)

public Object set(Attribute attribute, Object value) {
    switch (attribute) {
        case VARIABLE1:
             mVariable1 = value;
             break; 
        case VARIABLE2:
             mVariable2 = value;
             break;
        case VARIABLE3:
            mVariable3 = value;
            break;
 }/switch(attribute)
 }//set(Attribute attribute, Object value)
}//MyClass

I realize that if the various member variables are of different type the get() cannot return multiple data types. Might it be possible to use generics to return the appropriate data type? For example, if we add a fourth member variable of type int to the class, perhaps we could code the get() with generics, like this:

    private int mVariable4;
    public enum Attribute {VARIABLE1, 
                                        VARIABLE2, 
                                        VARIABLE3,
                                        VARIABLE4}


public <T> get(Attribute attribute) {
    switch (attribute) {
        case VARIABLE1:
             return mVariable1;
             break; //is this needed since return exists the method?
        case VARIABLE2:
             return mVariable2;
             break;
        case VARIABLE3:
            return mVariable3;
            break;
 }/switch(attribute)
 }//get(Attribute attribute)

I assume I haven't implemented generics properly here but is the basic principle a viable one? Thank you

1 Answer

Dan Johnson
Dan Johnson
40,532 Points

It's an interesting idea, but it runs into issues due to type checking and readability (and with ambiguity with getters so I'll leave them out entirely). Here's an example class I implemented using both methods, starting with the "traditional" model:

class UserTraditional {
    private String username;
    private Date lastActive;
    private short age;

    public void setUsername(String username) {
        if(!onlyLetters(username))
            throw new IllegalArgumentException("Username can only contain letters.");

        this.username = username;
    }

    public void setLastActive(Date lastActive) {
        if(lastActive.compareTo(Application.WEBSITE_DEPLOYED) == -1)
            throw new IllegalArgumentException("Last time active can't be before the launch of the website.");

        this.lastActive = lastActive;
    }

    public void setAge(short age) {
        if(age < 13)
            throw new IllegalArgumentException("Age requirement not met.");

        this.age = age;
    }

    private boolean onlyLetters(String username) {
        for(Character letter : username.toCharArray())
            if(!Character.isLetter(letter))
                return false;
        return true;
    }
}

And then with the setters consolidated into one method:

class UserConsolidated {
    private String username;
    private Date lastActive;
    private short age;

    public enum Attribute {
        USERNAME,
        LAST_ACTIVE,
        AGE
    }

    public <Type> void set(Attribute attribute, Type value) {
        switch(attribute) {
            case USERNAME:
                if(!onlyLetters((String)value))
                    throw new IllegalArgumentException("Username can only contain letters.");

                this.username = (String)value;
                break;
            case LAST_ACTIVE:
                if(((Date)value).compareTo(Application.WEBSITE_DEPLOYED) == -1)
                    throw new IllegalArgumentException("Last time active can't be before the launch of the website.");

                this.lastActive = (Date)value;
                break;
            case AGE:
                if((short)value < 13)
                    throw new IllegalArgumentException("Age requirement not met.");

                this.age = (short)value;
                break;
        }
    }

    private boolean onlyLetters(String username) {
        for(Character letter : username.toCharArray())
            if(!Character.isLetter(letter))
                return false;
        return true;
    }   
}

The logic for all the different attributes all gets thrown in one area, making it a bit difficult to follow.

You'll also notice the casts. Because Type is unknown the compiler can't be certain that the assignment would work. For a similar reason you'll run into issues like this:

// This won't compile.
userTraditional.setAge("12");
// This will, but it'll fail at runtime with a ClassCastException.
userConsolidated.set(UserConsolidated.Attribute.AGE, "12");

Since set is a method using generics, it'll accept any type. And since you could have any number of different types of attributes, it'd be very hard to restrict the types you could pass in and still allow for full assignment.

So I'd argue for the traditional model. There might be a better way to do a generic setter/getter but I'm not aware of it.

Thank you for explaining this issue and providing a detailed example. Is a consolidated get() or set() method more appealing/viable in the case where we have a class whose member variables are all of one and only one type, say String or int? Or consider the case where we have a class whose member variables are of various primitive types (int, short, long, double etc and String) yet a consolidated get() method which accepts only strings and does the validation followed by casting as needed to update said member variables is employed. Would that work? Is there a preferred programming patter to use when a class has a relatively large number of member variables, say more than 10?

With regards to polymorphism, why do some Java classes have a put() method and some have a set() method. Shouldn't one or the other always be used?

Thanks again.

Dan Johnson
Dan Johnson
40,532 Points

I'm thinking if you're dealing with large sets of homogeneous data you'd probably work with a collection, so methods like add and addAll will serve that purpose. Or if it is its own type, it might be time to break it into smaller classes. But if not, it would certainly make it easier to implement:

class Title {
    private String firstName;
    private String middleName;
    private String lastName;
    private String prefix;
    private String nickname;

    public enum Attribute {
        FIRST_NAME,
        MIDDLE_NAME,
        LAST_NAME,
        PREFIX,
        NICKNAME
    }

    public void set(Attribute attribute, String value) {
        switch(attribute) {
            case FIRST_NAME:
                firstName = value;
                break;
            case MIDDLE_NAME:
                middleName = value;
                break;
            case LAST_NAME:
                lastName = value;
                break;
            case PREFIX:
                prefix = value;
                break;
            case NICKNAME:
                nickname = value;
                break;
        }
    }

    public String get(Attribute attribute) {
        switch(attribute) {
            case FIRST_NAME:
                return firstName;
            case MIDDLE_NAME:
                return middleName;
            case LAST_NAME:
                return lastName;
            case PREFIX:
                return prefix;
            case NICKNAME:
                return nickname;
            default:
                return "";
        }
    }

    @Override
    public String toString() {
        String format = "\"%s\", %s %s %s %s";

        return String.format(format, nickname, prefix, firstName, middleName, lastName);
    }
}

If you avoid the use of generics there is a fairly simple way to implement a generic get method, though you still have the issues of type casting after each call:

class Amalgamation {
    private double myDouble;
    private char myChar;
    private String myString;
    private List<Map<String, String>> myListOfMaps;

    public Amalgamation() {
        myDouble = 10.0;
        myChar = 'A';
        myString = "Words";

        myListOfMaps = new ArrayList<Map<String, String>>();
        Map<String, String> element = new HashMap<>();
        element.put("Key", "Value");
        myListOfMaps.add(element);
    }

    public enum Attribute {
        DOUBLE,
        CHAR,
        STRING,
        LIST_OF_MAPS
    }

    public Object get(Attribute attribute) {
        switch(attribute) {
            case DOUBLE:
                return myDouble;
            case CHAR:
                return myChar;
            case STRING:
                return myString;
            case LIST_OF_MAPS:
                return myListOfMaps;
            default:
                return null;
        }
    }
}

I'd still go for the traditional route of named getters and setters but if your system calls for it, there are options like these.

You'll see put used in Java for associating key and value pairs (e.g. Map implementations), where setters encompass setting an attribute/property directly. This won't have much impact in the form of polymorphism though. You won't be overriding accessor methods too terribly often.