Hiding a "local" type parameter in Java
Asked Answered
H

7

12

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
  ... 
}
Hebraist answered 30/3, 2009 at 23:7 Comment(2)
Do you know what the type T is, when writing Bar, or is it open-ended?Tongue
Could you clarify, how Bar gets foo? If it gets it as constructor parameter, then T does appear in its interface. If it get it from some other source, then T is probably defined there and in your case is something concrete like Foo<String>. How is it done in your way?Damales
C
4

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);
    }
  }
}
Cati answered 30/3, 2009 at 23:19 Comment(0)
C
6

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();
    }
}
Coterie answered 30/3, 2009 at 23:43 Comment(1)
Sorry Tom, when I came back to edit my answer somehow I didn't notice you had already posted essentially the same solution. +1 in honor of St. Brendan and the Vikings.Cati
C
4

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);
    }
  }
}
Cati answered 30/3, 2009 at 23:19 Comment(0)
P
1

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.

Pippa answered 30/3, 2009 at 23:15 Comment(0)
L
1

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".

Lynlyncean answered 30/3, 2009 at 23:16 Comment(0)
A
0

Somewhere it must been decided, what type you want to use for 'T' inside the Bar class. So either you must chose it in the definition of Bar (replacing Foo by Foo inside the class definition) or you leave it up to the client of the Bar class: In this case Bar must be made generic.

If you want to have an interface to Bar not relying on T and be able to chose different types for T, you should use a non-generic interface or an abstract base class, as in:

interface Bar {
  void startStuff();
  // ...
}

class BarImplement<T> {
  private Foo<T> foo;
  // ...
}
Anandrous answered 30/3, 2009 at 23:55 Comment(0)
B
0

If you really want to draw some ire, you can put the Bar class inside the Foo interface, and suck the T of that way. See this article for more information about classes inside interfaces. Maybe this is the one case where that makes sense?

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

  public class Bar<T> {
    private Foo<T> foo;
    private T t;
    public void startStuff() {
      t = foo.getOne();
    }
    public void finishStuff() {
      foo.useOne(t);
    }
  }
}
Bryanbryana answered 31/3, 2009 at 4:11 Comment(0)
U
-2

The parameter T in this case becomes useless as far as Bar is concerned, since it will be erased to Object at compile time. So you could as well "save yourself the trouble", and do the erasure early:

public class Bar {

    private Foo<Object> foo;
    private Object t;

  ... 
}
Unpolitic answered 30/3, 2009 at 23:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.