Type aliases for Java generics

16,839

Solution 1

If you want full type safety, I don't think you can do better without some kind of wrapper classes. But, why not make those classes inherit/implement the original generic versions, like this:

public class FooBBQ extends Foo<Bar<Baz,Qux>> {
...
}

This eliminates the need for toGeneric() method, and it is more clear, in my opinion, that it is just a type alias. Also, generic type can be cast into FooBBQ without a compiler warning. It would be my personal preference to make Foo, Bar, Baz... interfaces, if possible, even if some code duplication would occur in implementation.

Now, without knowing concrete problem domain, it is hard to say whether you need, say FooBBQ, like in your example, or perhaps a:

public class FooBar<X, Y> extends Foo<Bar<X, Y>> {
...
}

On the other hand, have you thought about simply configuring Java compiler not to show some of the generic warnings, and simply omit the parts of generic definition? Or, use strategically placed @SuppressWarnings("unchecked")? In other words, can you make DoableImpl only "partly genericized":

class DoableImpl implements Doable<Foo<Bar>>,Foo<Bar>> {
    Foo<Bar> doIt(Foo<Bar> foobar) { ... } 
}

and ignore the warnings for the sake of less code clutter? Again, hard to decide without a concrete example, but it is yet another thing you can try.

Solution 2

Scala has nice support for type aliases. For example:

type FooBBQ = Foo[Bar[Baz,Qux]]

I realize that this answer won't be helpful if you don't have the option of switching to Scala. But if you do have the option of switching you might have an easier time.

Solution 3

Maybe having this many generic parameters flying around is just a bad idea and I need to re-think the problem?

Very probably. Do need to specialise 'Doit' in 8 dimensions?

In a lot of cases, these types don't exist in a vacuum and you should be thinking what domain objects your 'wrapper' represents rather than using them as a coding convenience.

Solution 4

Well, Java has no type aliases so you're out of luck. However, type aliases sometimes can be replaced with type variables! So we solve the problem of too many generics with even more generics!

As you've stripped all content from your example I can't guess where or whether it makes sense to introduce additional type variables, but here is one possible decomposition:

class DoableImplFoo<A,B> implements Doable<Foo<A>,Foo<B>> {
  public DoableImplFoo(SomePropertyOf<A,B> aAndBAreGoodEnough) { ... }
  Foo<A> doIt(Foo<B> fooB) { ... }
}

When you instantiate A to Bar<Baz,Qux> and B to Bar<Zot,Qux> later you may find that there is some boilerplate again, but it could be less than what you originally had.

Share:
16,839
Chris Conway
Author by

Chris Conway

I write code. I read code. I run code.

Updated on July 03, 2022

Comments

  • Chris Conway
    Chris Conway almost 2 years

    I have a fairly complicated set of generic classes in Java. For example, I have an interface

    interface Doable<X,Y> {
      X doIt(Y y);
    }
    

    and the implementation

    class DoableImpl implements Doable<Foo<Bar<Baz,Qux>>,Foo<Bar<Zot,Qux>>> {
      Foo<Bar<Baz,Qux>> doIt(Foo<Bar<Zot,Qux>> fooBZQ) { ... }
    }
    

    In the real implementation, Doable has quite a few methods and so Foo<Bar<Baz,Qux>>, etc., appear over and over again. (Believe it or not, the generic types are quite a bit more painful than this. I've simplified them for the example.)

    I'd like to simplify these, to save myself typing and to ease the strain on my eyes. What I'd like is to have a simple "type alias" for Foo<Bar<Baz,Qux>>, etc., say FooBBQ and FooBZQ.

    My current idea is to define wrapper classes:

    class FooBBQ { 
      public static FooBBQ valueOf(Foo<Bar<Baz,Qux>> fooBBQ) { 
        return new FooBBQ(fooBBQ); 
      }
      private Foo<Bar<Baz,Qux>> fooBBQ;
      private FooBBQ(Foo<Bar<Baz,Qux>> fooBBQ) { 
        this.fooBBQ = fooBBQ; 
      }
      public Foo<Bar<Baz,Qux>> toGeneric() {
        return fooBBQ;
      }
    }
    
    class FooBZQ { /* pretty much the same... */ }
    
    class DoableImpl implements Doable<FooBBQ,FooBZQ> { 
      FooBBQ doIt(FooBZQ fooBZQ) { ... }
    }
    

    This works well, but it has a few drawbacks:

    1. We need to define separate wrappers for each generic instance. The wrapper classes are short and stylized, but I can't figure out a way to macro-ize them.
    2. We have the translation overhead (conceptually, if not operationally) of calling valueOf and toGeneric to convert between FooBBQ and Foo<Bar<Baz,Qux>>. For example, if doIt calls into some library routine that expects a Foo<Bar<Zot,Qux>> (which the real implementation does), we end up with something like

      return FooBBQ.valueOf( libraryCall( fooBZQ.toGeneric() ) )
      

      where we would originally have had

      return libraryCall(fooBZQ);
      

    Is there some other way to get the "type alias" behavior I want here? Perhaps using some third-party macro toolset? Or do I need to accept that I'm going to have to do a lot of typing, one way (using the generic types in the implementation) or the other (writing wrappers for them)? Maybe having this many generic parameters flying around is just a bad idea and I need to re-think the problem?

    [UPDATE] OK, I'm banning any further "don't do that" answers. Take it as a given that Foo<Bar<Baz,Qux>> has genuine value in my problem domain (Pete Kirkham may be right that it has enough value to get a proper wrapper class with a descriptive name). But this is a programming problem; don't try to define the problem away.

  • Chris Conway
    Chris Conway about 15 years
    Extending Foo<Bar<Baz,Qux>> gets you one-way translation free (i.e., implicit up-casting) at the cost of having to write more complicated constructors (for explicit down-casting). It's not clear to me that that's a win. I'd like to avoid @SuppressWarnings annotations as much as possible.
  • Eddie
    Eddie about 15 years
    I disagree. It may not be overuse of generics ... it may be just an unclear use of generics. If it provides additional type safety, then it may be worth the pain, but there also must be a way to do this more clearly.
  • Chris Conway
    Chris Conway about 15 years
    Would you believe me if I said it's actually fairly clear (just extremely verbose) in the real code? Obviously there's no point in parameterizing your objects by Foo, Bar, and Qux...
  • TofuBeer
    TofuBeer about 15 years
    I have seen (and done) worse, there are times when it makes sense... but they can be hard to follow. However the typesafety can be worth it.
  • starblue
    starblue about 15 years
    I'm toying with algebra (Rings, Fields, various ways to construct them) in Java and I'm getting close to this level of generics. So it might be really needed. But one should try hard to keep it as simple as possible.
  • Simon Forsberg
    Simon Forsberg over 10 years
    @JesperE Do you have any design patterns or tips that could help, any suggestions on how to avoid overusing generics?
  • JesperE
    JesperE over 10 years
    Without knowing more about the particular problem domain, no. One rule of thumb which I think very often comes in handy is that you should not attempt to write any "generic" code unless you have at least three particular use cases for that code. If you create a generic class, you should be able to point out three different ways the types can be parametrized. If you can't, chances are good that you won't need the parametrization.
  • beluchin
    beluchin over 4 years
    omit the parts of the generic definition (+1) strategically i.e. keep them at the API level, consider omitting it elsewhere in the implementation.