Search code examples
javagenericscode-duplication

Java Prevent code duplication when getting a list of a property in each Object from a list of Objects


I have several objects that look like this:

class PhoneNumber
{
   String   getNumber();
   String   getExtension();
   DateTime getLastCalled();
}
class Address
{
   String getCity();
   string getState();
   int    getZip();
}

I'd like to be able to take a List of any one of those objects and get a list of a particular property. This is a trivial operation if I were to write a function for each property, but as I understand it, it is bad practice to duplicate code so many times.

So I would like to be able to do something like this for any property in either object:

List<Address> AddressList = getAddresses();
List<String> CityList = getListOfProperties( AddressList, Address.getCity /*<--Instruction as to what property to populate the returned list with */ )

I've racked my brain trying to do this with generics. I'm not even sure that this is possible, but as I understand it, a lot of magical things can be done with the Java programming language.


Solution

  • You can do this with generics and a utility class to do the work. I like this a little bit better than using reflection because you get compile time checking that the property you're retrieving exists (and isn't misspelled etc.).

    First a class that does the actual conversion. Note the abstract "get" method which we'll override in an anonymous inner class when we call getListOfProperties().

    public abstract class ListPropertyGetter<R,T>
    {
      /** Given a source object, returns some property of type R. */ 
      public abstract R get(T source);
    
      /** Given a list of objects, use the "get" method to retrieve a single property
          from every list item and return a List of those property values. */
      public final List<R> getListOfProperties(List<T> list)
      {
        List<R> ret = new ArrayList<R>(list.size());
        for(T source : list)
        {
          ret.add(get(source));
        }
        return ret;
      }
    }
    

    Then you use it like this. Not a pretty one liner, but compile time checking goodness:

    List<PhoneNumber> phoneNumbers = new ArrayList<PhoneNumber>();
    // add some PhoneNumbers to the list
    List<String> extensions =
      new ListPropertyGetter<String, PhoneNumber>()
      {
        @Override
        public String get(PhoneNumber source)
        {
          return source.getExtension();
        }
      }.getListOfProperties(phoneNumbers);