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

StackOverflow https://stackoverflow.com/questions/2195074

  •  25-09-2019
  •  | 
  •  

Question

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.

Was it helpful?

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);

OTHER TIPS

For something this simple, you're better off just providing the getters and setters. You're not saving much by refusing to duplicate one line of code. Even better, just get your IDE to write it for you.

You could handle your objects with Introspector like in this SO answer.

public <T > List<T > getMemberList( List<? > beanList, 
        String propertyName, Class<T > resultClass ) throws Exception {
    List<T > result = new ArrayList<T >();
    for( Object bean : beanList ) {
        result.add( (T )new PropertyDescriptor( 
                propertyName, bean.getClass() ).getReadMethod().invoke( bean ) );
    }
    return result;
}

Then you can do a call like this:

List<String > result = getMemberList( phonenumberList, "number", String.class );

You can use reflection (java.lang.Class and java.lang.reflect.*), to get method names; those beginning with "get" are properties, whose name follows "get".

This is a pretty good case for closures in Java; it's coming, but it's not here yet.

In the meantime, you can do this with reflection, although reflection always has a performance penalty. One of the things reflection can do is to let you call a method on an object, or retrieve a property, by specifying the name of the method/property as a string; in your case you would want to do getListOfProperties(AddressList, "city") and within that method use reflection to retrieve the specified property.

This type of thing is simplified dramatically by using the Apache Commons BeanUtils library.

import org.apache.commons.beanutils.BeanUtils;

static List<String> getListOfProperties(List beans, String propertyName) {
    List<String> propertyValues = new ArrayList<String>();
    for (Object bean:beans) {
         propertyValues.add(BeanUtils.getProperty(bean, propertyName));
    }
    return propertyValues;
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top