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
...
}
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();
}
}
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);
}
}
}
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.
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".
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With