tags:

views:

1620

answers:

10

My class is implementing a super-class method which which returns List<JComponent>. The list being returned is read-only:

public abstract class SuperClass {
    public abstract List<JComponent> getComponents();
}

In my class, I want to return a field which is declared as List - i.e. a sub-list:

public class SubClass extends SuperClass {
    private List<JButton> buttons;
    public List<JComponent> getComponents() {
        return buttons;
    }
}

This generates a compiler error, as List<JButton> is not a subtype of List<JComponent>.

I can understand why it doesn't compile, as it shouldn't be allowed to add a JTextField to a List of JButtons.

However, as the list is read-only, then "conceptually" this should be allowed. But, of course, the compiler doesn't know that it is read-only.

Is there any way to achieve what I want to achieve, without changing the method declaration in the super-class, and the field declaration in the sub-class?

Thanks, Calum

+6  A: 

Declare getComponents() as:

public List<? extends JComponent> getComponents()
Adam Rosenfield
I gave you +1, but note that generally it's bad form to put ?-expressions in return values because it requires callers to follow suit. That is, this doesn't compile: List<JComponent> foo = obj.getComponents().
Jason Cohen
Instead the caller also has to declare his list as List<? extends JComponent> and so on. Yukky.
Jason Cohen
See my alternate answer.
Jason Cohen
"Instead the caller also has to declare his list as List<? extends JComponent> and so on. Yukky." But it is good practice for the caller to have done that anyway. Since the caller is not adding any elements to the list (the OP said it is meant as read-only), the caller shouldn't care about the actual parameter of the list, just as long as it extends JComponent. For abstraction, the caller should have used the least restrictive type that he/she requires.
newacct
Using wildcard is the correct solution. Speaking of yukky - returning JComponent[] and JButton[] will give the sought covariance, but it is otherwise degenerate.
Yardena
A: 

To do this you need to widen the generics thingy. :)

public abstract class SuperClass {
    public abstract List<? extends JComponent> getComponents();
}


public class SubClass extends SuperClass {
    private List<JButton> buttons;
    public List<? extends JComponent> getComponents() {
        return buttons;
    }
}
Guðmundur Bjarni
A: 

Hmm, not sure

The usual way to set that up would be to have the super look like

public abstract class SuperClass {
    public abstract List<? extends JComponent> getComponents();
}

Not sure how to do it without changing that around.

madlep
A: 

Thanks for all the answers, regarding using wildcards in the super-class. I know about that option, but I'm trying to avoid it, as I'm writing an API for non-expert Java users; this would force them to declare their variables using wildcards, which might scare them away...

If the scenario was that the super-class was from a third-party library, so I couldn't change it, then what would be my options? Would it be possible to have a List<JButton> field in my sub-class, or would I have to resort to having a List<JComponent> field, and casting where required to JButton internally?

Remember that the List is externally read-only, so it should be "conceptually" possible; except that the compiler doesn't know this.

Thanks again, Calum

A: 

You could do the cast with @SuppressWarnings. I believe that would be appropriate in this case, just make sure you document why in a comment.

Alternately, do the following:

public List<JComponent> getComponents()
{
  return new ArrayList<JComponent>( buttons );
}

Yes I know this makes a copy and the list is already read-only. But until the profiler tells you otherwise, I would assume the penalty is small.

@Calum: I agree that using ?-expressions in return types is bad form because calling code is unable to do this for example:

List<JComponent> list = obj.getComponents();
Jason Cohen
A: 

@Jason: thanks for the follow-up.

I can't use @SuppressWarnings, as it's a compiler error.

I also came up with using new ArrayList<JComponent>, and agree that the performance penalty is probably small.

However, I want to be able to modify my list internally, and provide an unmodifiable view of it externally (as List<JComponent>). My use of "read-only" was probably a bit loose...

So new ArrayList<JComponent> wouldn't track any changes in the internal list.

Thanks, Calum

+1  A: 

I don't think you can do what you want without changing either the super-class method signature or the sub-class list declaration. The super-class is rigidly defining the return type to be JComponent. There's no way to make your return type anything but JComponent.

If it's read-only, I'd do something like:

public class SubClass extends SuperClass {
    private List<JComponent> buttons = new ArrayList<JComponent>();
    public void addButton(JButton button) {
     buttons.add(button);
    }
    public List<JComponent> getComponents() {
     return Collections.unmodifiableList(buttons);
    }
}

If you can modify the super-class, you could try something like:

public abstract class SuperClass<E extends JComponent> {
    public abstract List<E> getComponents();
}

public class SubClass extends SuperClass<JButton> {
    private List<JButton> buttons = new ArrayList<JButton>();
    public List<JButton> getComponents() {
     return Collections.unmodifiableList(buttons);
    }
}
Instantsoup
I don't understand how this answer isn't accepted as the correct one since it does answer the original question, without the wildtype clutter. Anyone care to elaborate please?
Cue
+2  A: 

I think I've found the answer which I'm looking for...

return Collections.<JComponent>unmodifiableList(buttons);

I had previously tried:

return Collections.unmodifiableList(buttons);

but this was being type-inferenced to List<JButton>.

Putting in the explicit type parameter allows the List<JButton> to be treated as a List<JComponent>, which is allowed by unmodifiableList().

I'm happy now ;-) and I've learned something thanks to the discussion.

Calum

This is only what you want if it is acceptible that the list returned is unmodifiable. Of course, returning a List<? extends JComponent> is also a list which is unmodifiable (well, you can't add anything to it, without reverting to raw types, anyway)
oxbow_lakes
Yes - my original question was regarding returning a read-only (or unmodifiable) list, in which case I think this solution works.
I think that they are changing the type-inference rules in JDK7 to infer things like this (that is, "return Collections.unmodifiableList(l)" will be inferred from the assignment, in this case the return-type of the method, so you won't have to add the <JComponent> bit in the future
oxbow_lakes
A: 

The whole point of generics is to provide compile-time type-safety fo things such as the "type" of a collection. Unfortunately for you, a List<JButton> is not a sub-type of a List<JComponent>. the reason for this is simple and is as follows:

List<JComponent> jc;
List<JButton> jb = new ArrayList<JButton>();
//if List<JButton> was a sublcass of List<JComponent> then this is a valid assignment
jc = jb;
jc.add(new JCheckBox()); //valid, as jc accepts any JComponent

JButton b = jb.get(0); //this will throw a ClassCastException, rendering Generic type-safety pointless

Generics are contra-variant in this manner, your original desire might as well have been to override a method which returned a String with one which returned a Float

I think it's a good general rule to be very careful when designing a generic class. Do you really need generics? How will it get used? Some generic designs end up with awful structures and mean users have to revert to the raw version. JTable's filtering mechanism is an excellent case in point: when you want to implement a filter-chain (which you certainly will!), the whole thing falls apart. The fact is; in this case, generics were added at a cost and with practically no benefit.

oxbow_lakes
Hi ChrisIn my case, it's a read-only copy of the view which is being returned. So conceptually, a read-only List<JButton> should suffice as a read-only List<JComponent>, as trying to add a JCheckBox would cause an exception to be thrown.
Then at least returning a List<? extends JComponent> would explicitly (i.e. at compile-time) be a read-only view of the collection. In an API, a method as you have declared it is only found out to be read-only at run-time!
oxbow_lakes
A: 

Returning a generic type with wildcard is not a good idea as it forces the client to think aoubt what's being returned ... It'll also make the client code more cumbersome and difficult to read.