tags:

views:

98

answers:

2

Hi Attempting to generics-ify some legacy code, I'm stuck. I have a ParentObject which wraps a ChildObject to provide group-type operations on the ChildObject. Most usefully, it allows iteration over the collection of child objects. When I try to add some generics to the mix, I can't work out how to make it play friendly with the iterator method without either a "naming clash" error, or in the example below, a "The return type is incompatible with Iterable.iterator()" error. Any suggestions? (Bonus question - is there a better way to write the avoid thegetChildObjectByIndex() method to avoid type erasure complier warning other than suppressing the warnings?) Thanks a lot in advance for any help

public class ParentObject implements Iterable<ChildObject> {  
    protected List<? super ChildObject> theChildObjects;  

    @SuppressWarnings("unchecked")  
    public <T extends ChildObject> T getChildObjectByIndex(int idx) {  
        return (T)theChildObjects.get(idx);  
    }  

    public Iterator<? super ChildObject> iterator() {  
        return java.util.Collections.unmodifiableCollection(this.theChildObjects).iterator();  
    }  

}
+3  A: 

If ParentObject only contains one subtype of ChildObject, you could parametrize ParentObject on that type:

public class ParentObject<T extends ChildObject> implements Iterable<T> {
    protected List<T> theChildObjects;

    public T getChildObjectByIndex(int idx) {
        return theChildObjects.get(idx);
    }

    public Iterator<T> iterator() {
        return java.util.Collections.unmodifiableCollection(this.theChildObjects).iterator();
    }
}
ILMTitan
That's the answer!For my first dabbling with generic classes, my mistake was not getting my head around the fact that you define the generic type at the class level just the once. I had started on the List and worked upwards, getting totally confused.
Chris
A: 

Seems like in your example ParentObject is some sort of container class that really has no relationship to ChildObject. Does ChildObject extend ParentObject? If so, that seems less than ideal. Also seems like you should be using "extends" in the generics instead of super, unless I'm just misunderstanding what you're trying to do. What about the following? Or is that too simple for what you want to do?

public class WidgetContainer<T> implements Iterable<T> {
    protected List<T> theWidgets;

    public T getWidgetByIndex(int idx) {
        return theWidgets.get(idx);
    }

    public Iterator<T> iterator() {
        return Collections.unmodifiableCollection(theWidgets).iterator();
    }
}

WidgetContainer<SomeWidget> myWidgetContainer = new WidgetContainer<SomeWidget>();

Also I might cache a copy of the unmodifiable collection and use it each time you need an iterator instead of constructing one on the fly.

Regarding caching the unmodifiable collection - all that Collections.unmodifiableCollection() does is give you a pointer to the original object in memory, but one which throws an unsupported exception when you call one of the mutating collection methods. The collection object isn't cloned or copied, so it's an inherently cheap operation.As my List is mutable, I think the overhead of managing the cached copy to keep it in synch would be more expensive than getting a new unmodifiable collection each time.Any thoughts?
Chris
It is my understanding that the Collections.* methods return new collections that *wrap* the original collection, throwing exceptions all mutating operations and delegating the rest to the original collection. If that's the case, then you're essentially creating a new wrapper class each time someone calls your iterator() method.If you're going to be mutating the collection, my suggestion would be to store two copies: the original un-wrapped version which you'd use when you need to change it, and the wrapped immutable version you'd use whenever you need to pass back an iterator.
Side note: it might be easier to just have your container class extend one of the collections classes, like ArrayList, then override the methods you feel like changing (e.g. iterator). Your class could store an immutable "version" of itself internally for use whenever it needs to pass back an immutable iterator. Since the immutable wrapper is backed by the original collection, it will reflect any changes you make to the original.