2

I have the following piece of code (the code for TrResponse and TrNode is irrelevant):

public TrResponse from(final TrNode b1, final TrNode b2, final TrNode b3) {
  final TrResponse resp = new TrResponse();
  Stream.of(b1, b2, b3)
    .filter(Objects::nonNull)
    .findFirst()
    .ifPresent(it -> {
      resp.m1 = it.m1;
      resp.m2 = it.m2;
      resp.m3 = it.m3;
    });
  // b1, b2 or b3 can be null
  // normalize() returns a mutated TrNode
  resp.b1 = (null != b1) ? b1.normalize() : null;
  resp.b2 = (null != b2) ? b2.normalize() : null;
  resp.b3 = (null != b3) ? b3.normalize() : null;
  return resp;
}

I'm trying to figure it out how to execute the normalize function for all of b1, b2, and b3 (that are eventually non-null) within the same stream operation so I don't have to have those null checks later on (as I would have now). I've tried .map(TrNode::normalize) right before .findFirst(), but it only applies it, eventually, to the first found instance (if any).

Any clues?

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
x80486
  • 6,627
  • 5
  • 52
  • 111
  • 1
    I don't think there is a need to do this within the stream as the result of `normalize` are being assigned to different fields and there is no reason to treat them as a stream from a collection as such. – Thiyagu Feb 27 '18 at 16:33
  • 2
    Just an aside this comment is, but is your use of [Yoda conditions](https://stackoverflow.com/questions/2369226/object-null-or-null-object) related to your name being upside-down? – DodgyCodeException Feb 27 '18 at 17:24
  • 2
    @DodgyCodeException ¿ʇnoqɐ ƃuᴉʞlɐʇ noʎ ǝɹɐ ʇɐɥʍ – Michael Feb 27 '18 at 17:30
  • It's like Mandarin...but worst :p – x80486 Feb 27 '18 at 17:38
  • Is the initial value of `resp.b[123]` different from `null`? Or, in other words, is there a reason to use `resp.b1 = (null != b1) ? b1.normalize() : null;` instead of just `if(null != b1) resp.b1 = b1.normalize();`? – Holger Feb 28 '18 at 10:36

2 Answers2

2

findFirst is doing exactly what it's supposed to here. It's a short-circuiting terminal operation.

You could write your own collector which just accepts the first element and ignores everything else. collect is not short-circuiting so would process every element. You could then use map as you were trying to do.

class FirstNode
{
    private TrNode node;

    public void setNode(final TrNode node)
    {
        if (this.node == null)
        {
            this.node = node;
        }
    }

    public Optional<TrNode> first()
    {
        return Optional.ofNullable(node);
    }
}

Stream.of(b1, b2, b3)
    .map(TrNode::normalize)
    .filter(Objects::nonNull)
    .collect(
        Collector.of(
            FirstNode::new,
            FirstNode::setNode,
            (node, node2) -> node
        )
    )
    .first()
    .ifPresent(it -> {
        resp.m1 = it.m1;
        resp.m2 = it.m2;
        resp.m3 = it.m3;
    });

But is this better or more readable than what you have at the moment? Not really. It's also worth noting that you'll be normalizing everything - is that required or desirable?

Michael
  • 41,989
  • 11
  • 82
  • 128
1

b1, b2 and b3 are not a collection but individual fields of an object.
How do you want to apply stream operations on?

Just replace this duplicate code :

resp.b1 = (null != b1) ? b1.normalize() : null;
resp.b2 = (null != b2) ? b2.normalize() : null;
resp.b3 = (null != b3) ? b3.normalize() : null;

by an helper method.

Personally I would provide specific methods in TrResponse to be able to apply the normalize methods in this way :

resp.setNormalizedOrNull(b1, b2, b3);

Where setNormalizedOrNull is defined as :

public void setNormalizedOrNull(TrNode b1, TrNode b2, TrNode b3){
    this.b1 = getNormalizedOrNull(b1);
    this.b2 = getNormalizedOrNull(b2);
    this.b3 = getNormalizedOrNull(b3);
}

// helper method :
private TrNode getNormalizedOrNull(TrNode node){
   return null != node ? node.normalize() : null;
}
davidxxx
  • 125,838
  • 23
  • 214
  • 215
  • Yeah, I get it. I have indeed a helper/utility function to wrap and check for `null` values, but I'm looking for something different. Basically, I'm trying to figure it out (if possible because so far I can't find a way to do it like that) if I can chain two (sort of) terminal operations on the same stream...then I just need to assign the members/fields back to the `TrResponse` instance. – x80486 Feb 27 '18 at 17:20
  • 2
    @ɐuıɥɔɐɯ It should be obvious from the definition of the word 'terminal' that you cannot perform anything after it. – Michael Feb 27 '18 at 17:29