Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hiding a "local" type parameter in Java

Suppose I'm using an interface with a generic type parameter

interface Foo<T> {
  T getOne();
  void useOne(T t);
}

The intention is that the type T is abstract: it enforces a type constraint on implementations of Foo, but the client code doesn't care exactly what T is.

This is no problem in the context of a generic method:

public <T> void doStuff(Foo<T> foo) {
  T t = foo.getOne();
  /* do stuff */
  foo.useOne(t);
}

But suppose I want to break up the work of doStuff, saving some state in a class Bar. In this case, I seem to need to add the type parameter of Foo to Bar.

public class Bar<T> {
  private Foo<T> foo;
  private T t;

  /* ... */

  public void startStuff() {
    t = foo.getOne();
  }

  public void finishStuff() {
    foo.useOne(t);
  }
}

This is kind of weird, since the type parameter T does not appear in the public interface of Bar (i.e., it is not included in any method parameter or return type). Is there a way to "quantify T away"? I.e., can I arrange for the parameter T to be hidden in the interface of Bar, as in the following?

public class Bar {
  <T> { // foo and t have to use the same T
    private Foo<T> foo;
    private T t;
  } // T is out of scope
  ... 
}
like image 743
Chris Conway Avatar asked Mar 30 '09 23:03

Chris Conway


4 Answers

To be useful, at some point you are going to set the foo field. At that point you should know (or be able to capture) T. I would suggest doing that in the constructor, and then it would make sense for Bar to have a generic parameter. You could even use an interface so client code doesn't have to see the type. However, I assume you aren't going to take my advice and really want a setFoo. So just add a point to switchable implementation:

/* pp */ class final BarImpl<T> {
    private final Foo<T> foo;
    private T t;

    BarImpl(Foo<T> foo) {
        this.foo = foo;
    }

    public void startStuff() {
        t = foo.getOne();
    }

    public void finishStuff() {
        foo.useOne(t);
    }
}

public final class Bar {
    private BarImpl<?> impl;

    /* ... */

    // Need to capture this wildcard, because constructors suck (pre-JDK7?).
    public void setFoo(Foo<?> foo) {
        setFooImpl(foo);
    }
    private <T> void setFooImpl(Foo<T> foo) {
        impl = new BarImpl<T>(foo);
    }

    public void startStuff() {
        impl.startStuff();
    }

    public void finishStuff() {
        impl.finishStuff();
    }
}
like image 77
Tom Hawtin - tackline Avatar answered Nov 10 '22 18:11

Tom Hawtin - tackline


Your problem is similar to that solved by a "capture helper", but I'm not sure it can be applied to your second example where two separate methods are used. Your first doStuff method could definitely be better written as public void doStuff(Foo<?> foo), since it works regardless of Foo type parameter. Then, the "capture helper" pattern would be useful.


Update: after tinkering a bit, extending the idea of Goetz's capture helper, I came up with this. Inside, it looks a little messy; from the outside, you wouldn't suspect a thing.

public class Bar {
  private final Helper<?> helper;
  public Bar(Foo<?> foo) {
    this.helper = Helper.create(foo);
  }
  public void startStuff() {
    helper.startStuff();
  }
  public void finishStuff() {
    helper.finishStuff();
  }
  private static class Helper<T> {
    private final Foo<T> foo;
    private T t;
    private Helper(Foo<T> foo) {
      this.foo = foo;
    }
    static <T> Helper<T> create(Foo<T> foo) {
      return new Helper<T>(foo);
    }
    void startStuff() {
      t = foo.getOne();
    }
    void finishStuff() {
      foo.useOne(t);
    }
  }
}
like image 41
erickson Avatar answered Nov 10 '22 18:11

erickson


Why not have a three-tier hierarchy:

abstract class Foo

abstract class FooImplBase<T> extends Foo

class Bar extends FooImplBase<String>

Clients only know about Foo, which doesn't contain any generic methods. Introduce any generic methods you need in FooImplBase<T> and then the concrete class derives from it.

So in your example startStuff() and endStuff() would be abstract in Foo and implemented in FooImplBase<T>. Does that sound like it might work in your real situation? I agree it's a bit cumbersome.

like image 1
Jon Skeet Avatar answered Nov 10 '22 20:11

Jon Skeet


You are defining the Bar class. Two things are true...

1) There is no parametric type involved in Bar. That is, the foo and t members have a single type, say U, that is fixed for the definition. If you're passed a Foo that you expect to assign to foo, it must be a Foo<U>. If this is all true, then yes, it's not part of the public interface - everything has a specific type. Then, I'm not sure what you mean by "quantify", as there are no free type variables. If you mean universally quantify, how do you reconcile the fact that Bar has no parametric typing, so must have been given a concrete type for each of it's members?

2) There is a parametric type involved in Bar. It may not be obviously in the public interface, but perhaps you pass in a Foo<T>, so you want to have a Bar class be instantiated with more than a single type. Then, as stated, this is in the public interface, and you need to make Bar parametric in T with generics. This gives you some form of universal quantification for the definition, "For all types T, this definition is true".

like image 1
Adam Wright Avatar answered Nov 10 '22 20:11

Adam Wright