views:

174

answers:

6

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.

+2  A: 

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 );
tangens
+2  A: 

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.

Jon
As soon as you provide accessor methods, you are inviting code duplication.
Dave Jarvis
+1  A: 

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".

Software Monkey
+1  A: 

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;
}
JacobM
+1  A: 

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);
Jemiah
Man, I was so close to something like that before I gave up and posted this question. Thanks a million!
DutrowLLC
Your `ListPropertyGetter` class is general-purpose enough to perform any operation on the input item to produce an output item, not just getting a property. This is usually called `map` (except in Linq, where it's called `Select`).
Daniel Earwicker
+1  A: 

The question should not be about duplicating code through the creation of multiple accessor methods. The question should be about what behaviours you want to assign to your classes.

public class PhoneNumber {
  private String number;
  private String extension;
  private DateTime lastCalled;

  public String getNumber() {
    return this.number;
  }

  public String getExtension() {
    return this.extension;
  }

  public DateTime getLastCalled() {
    return this.lastCalled;
  }
}

There are many problems with the code, including:

  • Breaking encapsulation.
  • Lending itself to breaking the Law of Demeter.
  • Introducing a dependency between clients of this class and DateTime.
  • Brittleness.
  • Not being object-oriented.

Breaking Encapsulation

If you think of classes in terms of a capsule, the inner workings of that capsule should remain unknown to its clients. The data types that PhoneNumber uses should not impose extra dependencies on its clients. What happens of you wanted to rewrite PhoneNumber to use something other than Strings or DateTime instances? All your clients have to change, as well, and that is a fancy term for unmaintainable code.

Law of Demeter

The following code becomes trivial to write (and it will be written):

public void printPhoneNumber( PhoneNumber pn ) {
  System.out.println( pn.getLastCalled().getTime() );
}

This introduces a potential NullPointerException. It can be rewritten:

public void printPhoneNumber( PhoneNumber pn ) {
  printDateTime( pn.getLastCalled() );
}

public void printDateTime( DateTime dateTime ) {
  System.out.println( dateTime.getTime() );
}

This avoids breaking the Law of Demeter, but the NullPointerException problem remains. Since you want to avoid duplicating code, the last thing you want to do is codify it as:

public void printDateTime( DateTime dateTime ) {
  if( dateTime != null ) {
    System.out.println( dateTime.getTime() );
  }
}

Because that only exacerbates the problem of duplicated code (if( dateTime != null )).

Imposed Dependency

Imagine you have three classes: A, B, and C. A uses B and C uses A. Why should C, then, be burdened with the knowledge of class B? Yet this is exactly what has been done by exposing the DateTime class through the getLastCalled() accessor.

Brittle

What happens when you want to add area code to the PhoneNumber? What about dialing prefixes? What about pauses to get to an external line? What about long distance numbers? What if you wanted to ensure that certain numbers remain undisclosed (e.g., unlisted numbers)?

While these features might lie beyond the problem domain, by exposing the inner workings of the class in such a fashion, you have indirectly introduced a tight coupling of the PhoneNumber class with system. That is, any clients of the PhoneNumber class now know that a PhoneNumber consists of a number and extension. What if three or four different classes were displaying, saving, printing, or transmitting a PhoneNumber on behalf of the user?

If you add the area code, you now have four places to change.

What if you wanted to have a history of the last called numbers?

Again, and again the clients of the class are now broken.

Not Object-Oriented

Object-orientation is about behaviours, first and foremost, with attributes being ancillary -- almost an after thought. Read the article Tell, Don't Ask.

Implementation

There is another way. Describe how the PhoneNumber should behave. What operations should it, if you pardon the word-play, answer?

public class PhoneNumber extends Observable {
  private String line;
  private String external;
  private String prefix;
  private String countryCode;
  private String areaCode;
  private String digits;
  private String extension;

  public Call call() {
    // Inform observers that a telephone call was made.
    //
    notifyObservers();
  }

  public String toString() {
    // Return a human-friendly version of this telephone number.
    //
  }
}

public class LittleBlackBook implements Observer {
  private AddressBook numbers;
  private List<CallHistory> callHistory;

  public void write( PhoneNumber number ) {
    getNumbers().add( number );
    number.addObserver( this );
  }

  private List<PhoneNumber> getNumbers() {
    if( this.numbers == null ) {
      this.numbers = createAddressBook();
    }

    return this.numbers;
  }

  protected AddressBook createAddressBook() {
    return new AddressBook();
  }

  // Implement the update method ...
}

public class CallHistory {
  private PhoneNumber pn;
  private DateTime called;
}

Decoupling the knowledge of what information is inside a PhoneNumber from how the underlying implementation works opens up so many more possibilities.

Dave Jarvis