Search code examples
javagenericsinheritanceapache-poiparameterized-types

Java generics - parameterized class vs. typed method


I guess this is a duplicated question, but after browsing heaps of related questions, I couldn't find a matching one ... yeah, lame excuse ;)

I'm currently developing a common interface for POIs HSLF/XSLF implementations. The reason for using generics is to support the Iterable interface, where user code need not to downcast to the concrete implementation, i.e. one can decide, if he wants to use the implementation classes or the common interface. Of course without generics the return type narrowing works as expected.

My goal is to minimize the parameter declarations for the user of the classes - see main method. Internally the generic references can more complex.

So I'd like to have something like this: (for the sake of simplicity, I haven't used the Iterable interface, but a different type argument)

/* Update: added static to the classes and removed null definitions to actually have a running example */

public class GenericsTest {
    static interface SlideShow {}
    static class HSLFSlideShow implements SlideShow {}

    static interface Notes<SS extends SlideShow> {}
    static class HSLFNotes implements Notes<HSLFSlideShow> {}

    static interface Slide<SS extends SlideShow> {
        <N extends Notes<SS>> N getNotes();
        <N extends Notes<SS>> void setNotes(N n);
    }

    // compile errors
    static class HSLFSlide implements Slide<HSLFSlideShow> {
        HSLFNotes notes = new HSLFNotes();

        @Override
        public HSLFNotes getNotes() { return notes; }
        @Override
        public void setNotes(HSLFNotes n) { notes = n; }
    }

    public static void main(String[] args) {
        HSLFSlide s = new HSLFSlide();
        HSLFNotes n = s.getNotes();
        s.setNotes(n);

        Slide<HSLFSlideShow> s2 = new HSLFSlide();
        Notes<HSLFSlideShow> n2 = s2.getNotes();
    }
}

I could get it to work with ... but this seems a bit clumsy:

    static interface Slide<SS extends SlideShow, N extends Notes<SS>> {
        N getNotes();
        void setNotes(N n);
    }

    static class HSLFSlide implements Slide<HSLFSlideShow,HSLFNotes> {
        HSLFNotes notes = new HSLFNotes();

        @Override
        public HSLFNotes getNotes() { return notes; }
        @Override
        public void setNotes(HSLFNotes n) { notes = n; }
    }

    public static void main(String[] args) {
        HSLFSlide s = new HSLFSlide();
        HSLFNotes n = s.getNotes();
        s.setNotes(n);

        Slide<HSLFSlideShow,HSLFNotes> s2 = new HSLFSlide();
        Notes<HSLFSlideShow> n2 = s2.getNotes();
    }

How would you minimize the needed type parameter in the main method (minimum is JDK6)?


Solution

  • As far as I can tell, there's no reason to use generic methods here. Overriding a generic method with a non-generic one is probably not doing what you think it's doing. I've discussed this here and here. (See also the generic methods tutorial.)

    The type of a generic method is dictated by the call site. Overriding it to be non-generic is allowed for backwards-compatibility reasons. One could still come along and do something like the following:

    HSLFSlide slide = ...;
    Slide<HSLFSlideShow> oops = slide;
    
    // perfectly legal
    // probably throws a ClassCastException somewhere
    oops.<SomeOtherNotes>set(new SomeOtherNotes());
    

    Generic methods let us, for example, define a method that returns the same type that it accepts:

    static <T> T pass(T t) { return t; }
    
    String s = pass("abc");
    Double d = pass(3.14d);
    

    This is the kind of thing generic methods are useful for.

    A more typical way to do what you are trying to do would be the following:

    interface Slide<SS extends SlideShow> {
        Notes<SS> getNotes();
        void setNotes(Notes<SS> n);
    }
    
    class HSLFSlide implements Slide<HSLFSlideShow> {
        Notes<HSLFSlideShow> notes = new HSLFNotes();
    
        @Override
        public Notes<HSLFSlideShow> getNotes() { return notes; }
        @Override
        public void setNotes(Notes<HSLFSlideShow> n) { notes = n; }
    }
    

    If there is some requirement that the reference type of notes can't be the interface, you should take a look at your design again. For example, perhaps the HSLFNotes implementation has features that should be moved to the Notes interface.

    Another way would be like the following:

    interface Slide<N extends Notes<?>> {
        N getNotes();
        void setNotes(N n);
    }
    
    class HSLFSlide implements Slide<HSLFNotes> {
        HSLFNotes notes = new HSLFNotes();
    
        @Override
        public HSLFNotes getNotes() { return notes; }
        @Override
        public void setNotes(HSLFNotes n) { notes = n; }
    }
    

    And also the working example from the question:

    interface Slide<SS extends SlideShow, N extends Notes<SS>> {…}
    class HSLFSlide implements Slide<HSLFSlideShow, HSLFNotes> {…}
    

    Which is actually fine.