Reputation: 75406
I am working on a small library which emulates multi-value return values in Streams. For this to work well, I would like to be able to use StreamTuple::new
instead of StreamTuple::create
or id -> new StreamTuple<>(id, id)
. I tried various modifications, but my generics-fu is not good enough to figure out how to change the source to allow this.
public class StreamTuple<L, R> {
protected final L left;
protected final R right;
public StreamTuple(L left, R right) {
this.left = left;
this.right = right;
}
/**
* Suitable for method::references. Value is set to id.
*/
public static <L> StreamTuple<L, L> create(L left) {
return new StreamTuple<>(left, left);
}
....
Calling code snippet:
Map<String, String> m = Stream.of("1", "2", "3")
// .map(StreamTuple::create)
.map(id -> new StreamTuple<>(id, id))
.collect(toMap(StreamTuple::left, StreamTuple::right));
Suggestions? Or can't it be done?
EDIT: I would like an additional constructor that only takes one argument and returns a StreamTuple where L and R is the same type. The current code does not reflect that because it was a while ago I couldn't make it work and I thought I replicated it. I can only find out how to provide one of L / R and that leaves the other one open. How to write a single argument constructor for this?
Upvotes: 4
Views: 141
Reputation: 4115
StramTuple::new
will be translated differently based on the FunctionalInterface expected by the accepting method.
expecting Function (T -> R) ==> ::new translates to new StreamTuple(t)
expecting BiFunction (T,U -> R) ==> ::new translates to new StreamTuple(t,u)
expecting Supplier (() -> R) ==> ::new translates to new StreamTuple()
Since Stream#map is expecting a Function, this cannot translate to the 2-args constructor. So your create
factory method seems a good way to go.
Having a 1-arg contructor (like the below) behaving like the create
method is not possible since compiler will complain because R is not bound
public StreamTuple(L both) { //will fail compilation
this.left = both;
this.right = both;
}
BUT since you want a 1-arg constructor and its argument must be assignable to both R and L, then you have 2 options: 1) one extends the other (e.g. R is assignable to L)
public class StreamTuple<L, R extends L> {
protected final L left;
protected final R right;
public StreamTuple(R both) {
this.left = both;
this.right = both;
}
}
2) if R and L are not related, then you need Object with casts on getters:
public class StreamTuple<L, R> {
protected final Object left, right;
public StreamTuple(Object o) {
this.left = this.right = o;
}
public <L> L getLeft() { return (L) left; }
public <R> R getRight() { return (R) right; }
}
Upvotes: 1
Reputation: 5948
Have you considered extending StreamTuple
in order to define single argument constructor?
Map<String, String> m = Stream.of("1", "2", "3")
.map( UnaryStreamTuple<String>::new )
.collect( Collectors.toMap(UnaryStreamTuple::left, UnaryStreamTuple::right) );
public class UnaryStreamTuple<T> extends StreamTuple<T, T> {
public UnaryStreamTuple(T left) {
super(left, left);
}
public UnaryStreamTuple(T left, T right) {
super(left, right);
}
}
Upvotes: 2
Reputation: 37875
There's no way to do this with a constructor, because there's no way to specify that the argument is a subtype of both L
and R
(i.e. conjunction/intersection). The language has a construct for such a requirement, but an intersection between type variables is not allowed:
// not allowed (causes a compilation error)
// vvvvv
public <LR extends L & R> StreamTuple(LR val) {
this.left = val;
this.right = val;
}
That might work in a future version of Java, however.
You have to use a factory method or, I suppose, what tsolakp suggested. I think the factory makes more sense.
Upvotes: 1
Reputation: 361909
It can't be done since you're passing id
twice. StreamTuple::create
is the way to go.
Upvotes: 1
Reputation: 178303
Stream's map
method requires a Function
that takes in exactly one parameter and returns something else.
However, what you are attempting to use is StreamTuple::new
, and that takes in 2 parameters, e.g. a BiFunction
, so that doesn't match.
I suppose you could use flatMap
to have two copies of each stream element, then write a custom Collector
to collect these items 2 at a time, but that sounds like a lot more work than referencing your create
method, which already matches.
What will work best is what you've done -- a factory method that takes exactly one parameter and returns the desired StreamTuple
.
Upvotes: 1