thkala
thkala

Reputation: 86333

Java generics and specialized constructors

(This is probably a duplicate, but I could not find it - feel free to point it out)

Consider the following Java class:

public class A<T0, T1> {
    public A(T0 t0, T1 t1) {
        ...
    }
}

Instantiating this class is easy using something along the lines of new A<Integer, String>(1, "X").

Suppose now that most instances of this class have a String as the second type parameter T1 and that the object of this type used in the constructor call is also pretty much standard.

If A had not been using generics, a common extension would be an additional constructor without the second argument:

public class A {
    public A(int t0, String t1) {
        ...
    }

    public A(int t0) {
        this(t0, new String("X"));
    }
}

Unfortunately, this does not seem to be possible for a class that does use generics - at least not without a forced cast:

    public A(T0 t0) {
        this(t0, (T1)(...));
    }

The reason? While this constructor only takes a single argument, it still uses two type parameters and there is no way to know a priori that whatever type T1 the user of the class supplies will be compatible with the default value used in the constructor.

A slightly more elegant solution involves the use of a subclass:

 public class B<T0> extends A<T0, String> {
     ...
 }

But this approach forces yet another branch in the class hierarchy and yet another class file with what is essentially boilerplate code.

Upvotes: 9

Views: 10089

Answers (6)

Tom Hawtin - tackline
Tom Hawtin - tackline

Reputation: 147154

Easiest method is just to add a static creation method.

public static <T0> A<T0,String> newThing(T0 t0) {
    return new A<T0,String>(t0, "X");
}

(Perhaps choose a name appropriate for the particular usage. Usually no need for new String("...").)

From Java SE 7, you can use the diamond:

A<Thing,String> a = new A<>(thing);

Upvotes: 6

Andy Thomas
Andy Thomas

Reputation: 86391

"Most instances" is the root problem.

Either T1 is a parameterized type or not. The single-argument constructor presumes both. Therein lies the problem.

The subclass solution solves the problem by making all instances satisfy T1=String.

A named constructor / factory method would also solve the problem, by ensuring T1=String.

    public static <T0> A<T0,String> makeA( T0 t0 ) {
       return new A<T0,String>( t0, "foo" );
    } 

Upvotes: 1

Cody S
Cody S

Reputation: 4824

Subclass it. As far as I've ever been taught, that's one of the great features of OOP. Enjoy it. Disk space is cheap.

If it's an issue with future maintenance of the code, consider making the original class abstract, and creating two subclasses off of it (one with the double-generic constructor, and one with the single.

Upvotes: 0

Thomas
Thomas

Reputation: 88707

You could qualify the generic types, i.e.

A<T0, T1 super MyDefaultType> {
   public A(T0 t0) {
    this(t0, new MyDefaultType());
   }
}

You can't use T1 extends MyDefaultType since if you define a subclass, a MyDefaultType instance would not be compatible with the type of T1.

Upvotes: 1

AlexR
AlexR

Reputation: 115328

Is there a way to declare a constructor that forces one or more of the type parameters to a specific type? Something with the same effects as using a subclass, but without the hassle?

I believe it is impossible. Think about this. Developer defines class that can be generic, i.e. the type of parameter is defined during creating the object. How can the developer define constructor that forces user to use specific type of the parameter?

EDIT: If you need this you have to create factory or factory method that creates instances of this class with predefined parameter type.

Upvotes: 0

Jeff Goldberg
Jeff Goldberg

Reputation: 961

As I understand it, you want to have a second constructor that (if called) would force the generic type T1 to be a String.

However, the generics are specified BEFORE you call the constructor.

That second constructor, if valid, could allow someone to do this:

B<Integer, Integer> b = new B<Integer, Integer>(5);

The error here is that you've specified the second generic type as an Integer BEFORE calling the constructor. And then the constructor would, in theory, specify the second generic type as a String. Which is why I believe it's not allowed.

Upvotes: 2

Related Questions