0

Effectively I need to take a subset of data out of a function return, but I don't actually need the returned object. So, is there a way to simply take what I need with a Stream or a Lambda-like syntax? And yes, I understand you CAN use stream().map() to put values into an array, and then decompose the array into named variables, but that's even more verbose for functionally equivalent code.

In C++, this would be called Structured Binding, but I seem to be missing the Java term for it when I search.

Procedural code I seem to be forced to write:

HandMadeTuple<Integer, Integer, ...> result = methodICurrentlyCall(input);
int thing1 = result.getFirst();
int thing2 = result.getSecond();
...
//thingX's get used for various purposes. Result is never used again.

Declarative code I'd like to be able to write:

int thing1, thing2;
(methodICurrentlyCall(input)) -> (MyType mt) { thing1 = mt.getFirst(); thing2 = mt.getSecond(); };

Or

int thing1, thing2;
Stream.of(methodICurrentlyCall(input)).forEach((mt) -> { thing1 = mt.getFirst(); thing2 = mt.getSecond();} 
patrickjp93
  • 399
  • 4
  • 20
  • Your approach is discouraged in Java. Local variables used in lambda must be final or effectively final. That means the variables thing1 and thing2 can't be changed in lambda. It's better to use map feature of streams. – Ansar Ozden Mar 25 '21 at 19:01
  • @AnsarOzden stream().map() doesn't do this either. All it does is output a single value. I could collect the values in a List, but that's worse than useless. I understand "Local variables used in lambda must be final or effectively final," but I have a use case where that constraint is detrimental to producing more succinct, readable code. Is there a bridge, or is this just an unfortunate gap in the Java language? – patrickjp93 Mar 25 '21 at 20:35
  • 4
    If I get your question right, it’s not really about streams, but about [deconstruction](https://cr.openjdk.java.net/~briangoetz/amber/datum.html#records-and-pattern-matching), a language construct currently not supported by Java but planned for the future. – Holger Mar 26 '21 at 07:33
  • @Holger I added some clarification. I believe you're correct that is ONE solution, but I don't necessarily limit the form of the solution to that. Why can't I just give the Complex/Object result of a method call to an in-line function to unpack the Primitives? – patrickjp93 Mar 26 '21 at 11:22
  • In short - NO, Java doesn't support destructuring yet. But Kotlin (JVM language fully interoperable with Java) does have this feature, so you can rewrite some of your classes in Kotlin and import them in your Java code. But if you plan to use deconstructing across whole application, there is no convenient way of doing it with Java. – Nikolai Shevchenko Mar 26 '21 at 11:54
  • 2
    There’s the fundamental principle that Java does not allow to create references to local variables that could persist longer than the variable itself. You can reference (effectively) final variables because that can be implemented by copying the value, without sacrificing consistency. But you can’t use a lambda expression to assign to local variables of the outer scope. The closest thing you can do, is to use the variables right in the same lambda expression, e.g. `map.for((key,value) -> /*use key and value*/);`. While this example is `void`, similar methods could have a (single) result. – Holger Mar 26 '21 at 12:35

1 Answers1

2

There's a really awkward hack: arrays.

final int[] thing1 = {0}, thing2 = {0};
Stream.of(methodICurrentlyCall(input))
  .forEach(mt -> {
    thing1[0] = mt.getFirst();
    thing2[0] = mt.getSecond();
  });

The array reference is final, therefore you can use it in lambdas.

I don't recommend this! It is neither shorter nor easier to understand

(Another option is using AtomicReference<T> or AtomicInteger/AtomicLong instead of the array, but there's a minimal overhead for guaranteeing atomic updates)


If you are looking for pattern matching or destructuring support for Java, this could be implemented in future versions of Java, see this related question on SO: Java object destructuring (even more details at https://cr.openjdk.java.net/~briangoetz/amber/serialization.html)

knittl
  • 246,190
  • 53
  • 318
  • 364
  • While the AtomicInteger answer ANNOYS me, it's tolerable. I can write in a comment to the tune of "Deficiency in Java requiring functional programming quality to lambdas, normal primitives not allowed (as of J8)." If anyone from Oracle, or another influencer in the Java world sees this, fix your language and let people write reasonable code, for God's sake. – patrickjp93 Mar 27 '21 at 10:45
  • @patrickjp93 then don't use Java for functional programming. I don't go around complaining that Haskell makes side-effectful programming so difficult and "should clean up its act". Different languages were built with different paradigms in mind. – knittl Mar 27 '21 at 11:22
  • 2
    I still don't see why `int a,b; do(call()).extract(result -> { a=result.a; b=result.b; })` would be more desirable than `var result=call(); int a=result.a, b=result.b;` – the latter being shorter to write and easier to understand; the former being convoluted and inherently side-effectful with temporary dependencies (you cannot know when the lambda body will be executed, maybe it is only evaluated lazily?) – knittl Mar 27 '21 at 11:24
  • 2
    I just wanted to write a similar comment, `var mt = call(); int a = mt.a, b = mt.b;` is the baseline we have the compare with. Is the alternative simpler? Nah. Does it save us from dealing with the object, we're not interested in? Nah, we're now dealing with even more objects, we're actually not interested in. – Holger Mar 27 '21 at 11:38
  • "with a _temporal_ dependency" (not temporary) is what I meant in my previous comment :) – knittl Mar 27 '21 at 11:49