Reputation: 86333
(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.
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?
Is there something fundamentally wrong in my understanding of generics and/or my design? Or is this a valid issue?
Upvotes: 9
Views: 10089
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
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
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
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
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
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