• Post Reply Bookmark Topic Watch Topic
  • New Topic
programming forums Java Mobile Certification Databases Caching Books Engineering Micro Controllers OS Languages Paradigms IDEs Build Tools Frameworks Application Servers Open Source This Site Careers Other Pie Elite all forums
this forum made possible by our volunteer staff, including ...
Marshals:
  • Campbell Ritchie
  • Ron McLeod
  • Rob Spoor
  • Tim Cooke
  • Junilu Lacar
Sheriffs:
  • Henry Wong
  • Liutauras Vilda
  • Jeanne Boyarsky
Saloon Keepers:
  • Jesse Silverman
  • Tim Holloway
  • Stephan van Hulst
  • Tim Moores
  • Carey Brown
Bartenders:
  • Al Hobbs
  • Mikalai Zaikin
  • Piet Souris

Should Streams be Cloneable?

 
Saloon Keeper
Posts: 13248
291
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Rob Spoor wrote:Guess Stephan and I had the same idea, although I think Stephan's implementation has a few small bugs in both the accumulate and combine methods:

  • accumulate also adds shorted strings
  • the combiner should return a combined object


  • Wow! I love how similar our solutions are! Yes, I missed the two bugs you spotted
     
    Bartender
    Posts: 4624
    182
    • Likes 1
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator
    It goes to show that the old fashioned way of doing is sometimes way easier.
    I think no one would have any trouble writing:


    I reckon progress comes with a price...

    But we can give this method a small java 8 look by making generic: String -> T, String.length -> mapper<T, U> and a Comparator<U>
     
    Bartender
    Posts: 10780
    71
    Hibernate Eclipse IDE Ubuntu
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator

    Stephan van Hulst wrote:This won't work, because it will return a stream of strings that were longest at the time.


    Ah. I did wonder about that. Personally, I think the API docs are consistently misleading about this, since they say that things like peek() and filter() return Streams when, in fact, they effectively return elements of a Stream (except for flatMap()). Perhaps it's clarified somewhere else, but I've gone through the Stream docs and some of the "general" documentation more than once, and it certainly didn't leap out at me; although the idea is familiar from Unix pipelines.

    However, thanks for the info. Guess I'm back to my initial idea, except have the method take a List...

    Winston
     
    Stephan van Hulst
    Saloon Keeper
    Posts: 13248
    291
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator

    Winston Gutkowski wrote:Ah. I did wonder about that. Personally, I think the API docs are consistently misleading about this, since they say that things like peek() and filter() return Streams when, in fact, they effectively return elements of a Stream (except for flatMap()).


    I don't understand. peek() and filter() do return Streams.
     
    Stephan van Hulst
    Saloon Keeper
    Posts: 13248
    291
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator
    Piet, good old procedural code would be the first thing I would write in this case, but it's worth mentioning that the version using a Collector is trivially parallelizable, which can be worth it if the data set is large enough.
     
    Master Rancher
    Posts: 4023
    53
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator

    Winston Gutkowski wrote:@Mike: It seems that what I want is indeed a form of "reduction" operation; but I think that your suggestion will only return one "longest" String (correct me if I'm wrong), where the original requirement was to return all Strings that are the same length as the longest.



    Looking here, I see no such requirement.

    Jude Niroshan wrote:How can I retrieve the longest name out of my name list?



    Note the singular form of the word "name".

    My solution compiles, runs, fulfills the description given, and passes the one test case given. It's also short, readable, fast, and significantly, has a minimal memory footprint.

    It's entirely possible that what is really needed here is a list (or Set) of all the longest names. It's also entirely possible that that is *not* needed. None of us asked, and Jude never said. Maybe my solution is too simple, sure. Or maybe the others are over-engineered. Hard to tell without more input from the original poster. The simplicity and low memory footprint make me think it's worth considering my solution, though.

    I would note that the groupingBy() solutions seem highly undesirable to me, as they force everything into main memory, in one big Map. Remember that one of the big advantages of a Stream is that the data doesn't need to be in memory all at once. Maybe it's being streamed from a file or database somewhere. Forcing everything into memory at once is a bad idea if it's not actually necessary. We should retain only as much data as we need to actually solve the problem. Custom Collector solutions can do this (as shown already) if the problem is too complex for a one-argument reduce().

     
    Mike Simmons
    Master Rancher
    Posts: 4023
    53
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator

    Stephan van Hulst wrote:Piet, good old procedural code would be the first thing I would write in this case, but it's worth mentioning that the version using a Collector is trivially parallelizable, which can be worth it if the data set is large enough.



    Also worth mentioning that a Stream version doesn't require everything to be in memory, as in an array, Collection or Map. If your procedural code was written to use only Iterable and not assume everything's in memory, great - but the Stream techniques make this easier.

    Here's another solution, returning all the longest Strings, but as a Set rather than List to remove possible memory-consuming duplicates, and using the 3-arg reduce() method:



    ...aaand, after posting, I see it's nearly identical to those from Rob and Stephan, except using a slightly shorter reduce() syntax, and using a Set.
     
    Stephan van Hulst
    Saloon Keeper
    Posts: 13248
    291
    • Likes 2
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator
    I'm afraid that you shouldn't use reduce() like that, Mike. The API explicitly mentions that the accumulator and combiner functions need to be stateless. For a mutable reduction, you need to use collect() instead of reduce().
     
    Mike Simmons
    Master Rancher
    Posts: 4023
    53
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator
    Excellent point; thanks for the reminder. It works fine in most tests, but loses thread safety, which is a very bad idea. OK, for completeness here’s the corrected 3-arg reduce version, now stateless:


     
    Winston Gutkowski
    Bartender
    Posts: 10780
    71
    Hibernate Eclipse IDE Ubuntu
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator

    Mike Simmons wrote:Looking here, I see no such requirement.

    Jude Niroshan wrote:How can I retrieve the longest name out of my name list?


    Dang! You're quite right; that was my interpretation (I didn't see the point in returning only one "longest" String if there were 5 of them). However, it seems to have sparked a fun thread...

    I would note that the groupingBy() solutions seem highly undesirable to me, as they force everything into main memory, in one big Map. Remember that one of the big advantages of a Stream is that the data doesn't need to be in memory all at once.


    Yes I do see that. Since I have no other point of reference, I look at Streams as being a bit like Unix pipelines, which are simple buffers or LIFO queues with commands (or "functions") in the middle. It may not be exact, but it helps me to visualise them....even if I'm still having a few problems with the semantics.

    WInston
     
    Piet Souris
    Bartender
    Posts: 4624
    182
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator
    I was thinking about Stephans "bringing in state" remartk.

    At the Scala course I learnt that an object is stateless if its current situation (state) depends in no way on its history. Now, the determination of the list/set of longest strings has certainly a deterministic outcome, that does not depend om the route that lead to that outcome, whether that route was a sequential, random or parallel.

    So, maybe Mike's accumulator and/or combiner may be stateful, but whar relevance could that have? Stephan (or others), can you elaborate a little?
     
    Winston Gutkowski
    Bartender
    Posts: 10780
    71
    Hibernate Eclipse IDE Ubuntu
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator

    Mike Simmons wrote:It's entirely possible that what is really needed here is a list (or Set) of all the longest names.


    Which isn't a particularly easy task either. Adding is dirt simple, but removal isn't - particularly if you want it to run faster than O(n) - and even harder if you want the structure as a whole to act like a List.

    Winston
     
    Piet Souris
    Bartender
    Posts: 4624
    182
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator
    But why not create a Map<Integer, List<String>> then? That should take O(n) time, and getting the list of largest length is then very fast indeed.
    Besides, it enables you to get other fuctions than max length with ease (like shortest length, average length, whatever).

    It may have the disadvantage that Mike mentioned, about having the whole collection in memory. but if that is a problem, you could overload the method by supplying a BufferedStream, or so.

    But what was the reason for having a Stream as parameter again?
     
    Stephan van Hulst
    Saloon Keeper
    Posts: 13248
    291
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator
    If you call reduce() on a parallel stream using stateful functions, the result may be indeterministic because reduce() does not perform synchronization.

    There's a second, subtler issue. If a function's outcome is determined by the function arguments, and only the function arguments, and calling the function has no side-effects, we call such a function "referentially transparent". Mapping and reducing operations can be greatly optimized by caching the outcome of the mapper and reduction functions for given input values, but only if these functions are referentially transparent.

    Let's consider a function that generates the Fibonacci sequence:

    This function is referentially transparent: It doesn't matter how often you call it, passing 7 will always return 13. Now, let's say we have a list of integers that we want to map to Fibonacci numbers:

    The first time the map() operation encounters 7, it will call fib() which will calculate that the 7th Fibonacci number is 13. It's entirely possible though that the second time map() encounters 7, it won't call fib(), but just return the cached result because it assumes that the mapper function is referentially transparent. The reduce() operation can achieve similar performance improvements because it too assumes that the functions passed are referentially transparent. collect() was specifically added to work with functions that have side-effects.
     
    Winston Gutkowski
    Bartender
    Posts: 10780
    71
    Hibernate Eclipse IDE Ubuntu
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator

    Stephan van Hulst wrote:[I don't understand. peek() and filter() do return Streams.


    OK, yes they return a Stream, but their "effect" is only on one element of that Stream; otherwise my AtomicInteger method would work just fine.

    The way I see it (and correct me if I'm wrong), when processing a pipeline, each intermediate function is called, in series, once for each element of the "stream" output by the previous function, so any side-effects it has (like setting a variable) have to be viewed from the standpoint of the element being passed through at the time.

    Oddly enough, I was thinking how I'd do this with a Unix pipeline; and it wouldn't be particularly easy there either - although I think it can be done with tee (I'll have to test it).

    Winston
     
    Winston Gutkowski
    Bartender
    Posts: 10780
    71
    Hibernate Eclipse IDE Ubuntu
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator

    Rob Spoor wrote:Perhaps the best solution is to use a custom Collector. We would need an extra utility class to hold not just the list of longest strings, but also the length of these:


    OK, it took me a while to work out what that combine() method was all about, but now I get it. Very nifty.

    However, I still don't understand one thing: The first parameter to of() seems to specify a Supplier, and your example uses
      LongestValues::new
    yet I don't see any get() method in LongestValues.

    Winston
     
    Piet Souris
    Bartender
    Posts: 4624
    182
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator

    Stephan van Hulst wrote:If you call reduce() on a parallel stream using stateful functions, the result may be indeterministic because reduce() does not perform synchronization.

    (...)


    Perfectly clear. Thanks!
     
    Marshal
    Posts: 73974
    332
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator

    Rob Spoor wrote:. . . That should be a purely hypothetical situation, . . . You can use an AtomicInteger or even an int[1] instead to overcome the non-final value. But of course, the fact that you'd have to use such workaround should be enough of a warning to tell you that this is a bad idea . .

    Agree; I spent half the weekend thinking this is a situation they didn't envisage when they created the Streams API.
     
    Stephan van Hulst
    Saloon Keeper
    Posts: 13248
    291
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator

    Winston Gutkowski wrote:The way I see it (and correct me if I'm wrong), when processing a pipeline, each intermediate function is called, in series, once for each element of the "stream" output by the previous function, so any side-effects it has (like setting a variable) have to be viewed from the standpoint of the element being passed through at the time.


    This is correct. The reduction or mapping function that is passed as an argument operates on one element at a time. However, the stream operation itself (map(), filter(), reduce(), whatever()) operates on the stream as a whole, and returns a new stream. There's nothing inconsistent or confusing about this in the API description, IMO.

    Winston Gutkowski wrote:However, I still don't understand one thing: The first parameter to of() seems to specify a Supplier, and your example uses
      LongestValues::new
    yet I don't see any get() method in LongestValues.


    Wouldn't you say that LongestValues' default constructor counts as a Supplier<LongestValues>?
     
    Winston Gutkowski
    Bartender
    Posts: 10780
    71
    Hibernate Eclipse IDE Ubuntu
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator

    Piet Souris wrote:But why not create a Map<Integer, List<String>> then? That should take O(n) time, and getting the list of largest length is then very fast indeed.
    Besides, it enables you to get other fuctions than max length with ease (like shortest length, average length, whatever).


    Well, my answer would be that you're taking up unnecessary space for the specified task. You're creating a structure containing information that "might be useful for something", rather than sticking to the task at hand.

    I also see no reason why Rob's collector couldn't be refined to something like a LengthCollector which you supply with an enum value like SHORTEST or AVERAGE or MEDIAN - which could possibly include some useful logic as well.

    Winston
     
    Winston Gutkowski
    Bartender
    Posts: 10780
    71
    Hibernate Eclipse IDE Ubuntu
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator

    Stephan van Hulst wrote:Wouldn't you say that LongestValues' default constructor counts as a Supplier<LongestValues>?


    No, because a Supplier is defined as having a get() method. But maybe (again) I'm missing something here...

    Winston
     
    Winston Gutkowski
    Bartender
    Posts: 10780
    71
    Hibernate Eclipse IDE Ubuntu
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator
    Another question (actually two):

    1. It seems to me that it would be nice, if we're going to the business of writing a custom Collector, to write so that it works with the collect(Collector) method, rather than having to write that 'orrible of() monstrosity every time.

    2. Am I right in thinking that there's nothing to stop a Collector like Rob's (or yours) from producing a Stream, because then I think I (or we) will have achieved precisely what I'm looking for: a StreamStream "reduction" that depends on a property of the contents of the input. Or is that still a problem?

    Winston
     
    Stephan van Hulst
    Saloon Keeper
    Posts: 13248
    291
    • Likes 1
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator
    Method names are unimportant when supplying a method/constructor handle or lambda to a higher order function, as long as the method/constructor/lambda has compatible parameter and return types.

    For instance, one of ExecutorService.submit()'s overloads takes a Runnable. Runnable is a functional interface, so you can provide submit() with any method handle, as long as it take no arguments:
     
    Stephan van Hulst
    Saloon Keeper
    Posts: 13248
    291
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator

    Winston Gutkowski wrote:Another question (actually two):1. It seems to me that it would be nice, if we're going to the business of writing a custom Collector, to write so that it works with the collect(Collector) method, rather than having to write that 'orrible of() monstrosity every time.


    But we do call the collect(Collector) method. The of() method is just a static factory method of the Collector interface. There's nothing stopping you from passing a custom implementation of Collector, such as an anonymous class.

    2. Am I right in thinking that there's nothing to stop a Collector like Rob's from producing a Stream, because then I think I (or we) will have achieved precisely what I'm looking for: a StreamStream "reduction" that depends on a property of the contents of the input. Or is that still a problem?


    Sure, you could write a Collector to do that, but it doesn't make conceptual sense. collect() strictly evaluates a functional expression. Stream doesn't represent a value. It represents a functional expression.

    It's also not useful, because the Collector would almost always have to maintain some sort of state, and the only way to make a Stream out of it would be to return state.stream(). Why not just return state, and let the user call stream() if they happen to want a Stream?
     
    Winston Gutkowski
    Bartender
    Posts: 10780
    71
    Hibernate Eclipse IDE Ubuntu
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator

    Stephan van Hulst wrote:Method names are unimportant when supplying a method/constructor handle or lambda to a higher order function, as long as the method/constructor/lambda has compatible parameter and return types.
    For instance, one of ExecutorService.submit()'s overloads takes a Runnable. Runnable is a functional interface, so you can provide submit() with any method handle, as long as it take no arguments:


    But that's not my point (again, unless I'm missing something fundamental). The first parameter of Collector's of() method is a Supplier, which is an interface defined as requiring a get() method (and it doesn't have a default implementation); but Rob's LongestValues class doesn't have one.
    Therefore: How can it "be" a Supplier?

    Winston
     
    Winston Gutkowski
    Bartender
    Posts: 10780
    71
    Hibernate Eclipse IDE Ubuntu
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator

    Stephan van Hulst wrote:It's also not useful, because the Collector would almost always have to maintain some sort of state, and the only way to make a Stream out of it would be to return state.stream(). Why not just return state, and let the user call stream() if they happen to want a Stream?


    Well, my first thought is that I could then put it in a pipeline, viz:
    Winston
     
    Stephan van Hulst
    Saloon Keeper
    Posts: 13248
    291
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator
    Because method names don't matter to higher order functions. If your method accepts a functional interface (which Supplier is), then you can pass it any lambda, constructor or method handle, as long as it's compatible with the exceptions, parameter list and return type of the method declared in the functional interface. That's just the way it is.

    When a method declares that it needs a Supplier<Foo>, it's saying: "Give me anything at all, I don't care what it's named, as long as I can get a Foo out of it without supplying it with arguments".

    Another example: Say you have a list of Strings representing addresses and you want to convert it to a list of Address. Address has a constructor that parses a String. We can write the following:

    In this context map() requires you to pass it a Function<String, Address>, and even though that means you need to implement apply(String) in Function, we can pass a handle to Address(String).

    map() doesn't care that you're not passing it a function named apply(). All it cares is that it can pass it a String, and get an Address out of it. The constructor matches that requirement.
     
    Winston Gutkowski
    Bartender
    Posts: 10780
    71
    Hibernate Eclipse IDE Ubuntu
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator

    Winston Gutkowski wrote:Well, my first thought is that I could then put it in a pipeline...


    It also occurs to me that this kind of thing is not unique. sorted() is described as a "stateful intermediate function" which presumably needs to consume the entire contents of a Stream in order to be able to spew out the "sorted" elements. There are also others one can imagine, like top(int, Predicate).

    I'm starting to come to the conclusion that I'm not entirely out to lunch on this, even if my original question was a bit off base. I want to write a Collector (or a stateful intermediate function) that consumes a Stream and outputs another one based on the contents of the input. Explain to me why that's "non-functional".

    Winston
     
    Stephan van Hulst
    Saloon Keeper
    Posts: 13248
    291
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator

    Winston Gutkowski wrote:Well, my first thought is that I could then put it in a pipeline, viz:


    This is not a pipeline. These are actually two pipelines. collect() closes the first pipeline and then returns a second pipeline. Everything between the two pipelines is eagerly evaluated and buffered.
     
    Winston Gutkowski
    Bartender
    Posts: 10780
    71
    Hibernate Eclipse IDE Ubuntu
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator

    Stephan van Hulst wrote:map() doesn't care that you're not passing it a function named apply(). All it cares is that it can pass it a String, and get an Address out of it. The constructor matches that requirement.


    Aaaah, OK. Now ze mist beginz to klear.

    Blimey, that's quite a departure for us "static" bods.

    Winston
     
    Piet Souris
    Bartender
    Posts: 4624
    182
    • Likes 1
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator
    Well, if it makes one thing clear, it is that it is the highest time to get this Mint of yours to run java 8, so that you can try out first hand all of your siblings.
    Any help needed?
     
    Winston Gutkowski
    Bartender
    Posts: 10780
    71
    Hibernate Eclipse IDE Ubuntu
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator

    Stephan van Hulst wrote:This is not a pipeline. These are actually two pipelines. collect() closes the first pipeline and then returns a second pipeline. Everything between the two pipelines is eagerly evaluated and buffered.


    Do I care? Presumably the same is (or is probably) true of sorted(). I simply want some way of sticking a function that relies on the contents of a Stream in the middle of a pipeline because it makes sense to me based on what I want to do.

    I suppose it doesn't really matter if I add a stream() call to Rob's class...but to me that's premature implementation(*).

    Winston

    (*) That's not quite correct. What it is is less "objective".
     
    Stephan van Hulst
    Saloon Keeper
    Posts: 13248
    291
    • Likes 1
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator

    Winston Gutkowski wrote:I want to write a Collector (or a stateful intermediate function) that consumes a Stream and outputs another one based on the contents of the input. Explain to me why that's "non-functional".


    Stateful 'functions' are not functional by definition. Purely functional programs don't have state at all.

    Winston Gutkowski wrote:Do I care? Presumably the same is (or is probably) true of sorted(). I simply want some way of sticking a function that relies on the contents of a Stream in the middle of a pipeline because it makes sense to me based on what I want to do.


    After some deliberation, I have to agree. I guess you'd have to carefully document that the function you wrote is stateful and potentially buffers the entire input stream.
     
    Winston Gutkowski
    Bartender
    Posts: 10780
    71
    Hibernate Eclipse IDE Ubuntu
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator

    Piet Souris wrote:Well, if it makes one thing clear, it is that it is the highest time to get this Mint of yours to run java 8, so that you can try out first hand all of your siblings.


    Absolutely.

    Any help needed?


    Don't think so, but thanks for the offer. I think I'm just going to bite the bullet and upgrade properly - ie, install a new version of the OS, which will probably have it as standard.

    Long overdue anyway. This one's about two years old, even if it is a "long-term" release.

    Winston
     
    Winston Gutkowski
    Bartender
    Posts: 10780
    71
    Hibernate Eclipse IDE Ubuntu
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator
    BTW, this is my try at a truly "insertable" Longest function, plagiarised entirely from Rob's, that doesn't rely on of():and my assumption is that it might be used as follows:
      Longest longest = new Longest();
      Stream<String> longestStrings = listOfStrings.stream().collect(longest);

    or as part of a larger "pipeline".

    Does it look reasonable, or have I made yet more mistakes?

    Winston
     
    Stephan van Hulst
    Saloon Keeper
    Posts: 13248
    291
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator
    I just figured out what my objection with the 'two part pipeline' is. As opposed to sorted(), your method will close the first part of the pipeline, even if the second part is not evaluated.
     
    Winston Gutkowski
    Bartender
    Posts: 10780
    71
    Hibernate Eclipse IDE Ubuntu
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator

    Stephan van Hulst wrote:I just figured out what my objection with the 'two part pipeline' is. As opposed to sorted(), your method will close the first part of the pipeline, even if the second part is not evaluated.


    Erm, you'll have to explain that one to me. I see nothing empirically different between what we're doing and what a sort does, so if there is a difference, it suggests to me that it's been built in to sorted(). And assuming that it is a problem, is there any way to turn our "longest" function into a true "stateful intermediate" one.

    Winston
     
    Stephan van Hulst
    Saloon Keeper
    Posts: 13248
    291
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator

    The solution to this problem is to have getLongest() return a new Stream that does 'late binding'. I'm afraid the code is not pretty:
     
    Mike Simmons
    Master Rancher
    Posts: 4023
    53
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator

    Winston Gutkowski wrote:BTW, this is my try at a truly "insertable" Longest function, plagiarised entirely from Rob's, that doesn't rely on of():and my assumption is that it might be used as follows:
      Longest longest = new Longest();
      Stream<String> longestStrings = listOfStrings.stream().collect(longest);

    or as part of a larger "pipeline".

    Does it look reasonable, or have I made yet more mistakes?


    The return types of the methods are all off - they need to be functions, most easily achieved with lambdas or method references. So for example

    needs to be

    or

    Similar transformations are needed for all the other methods.

    This may seem annoyingly convoluted, as they could have defined the Collector interface pretty much the way you imagine... but they didn't. The syntax they chose looks more convoluted, when you try to implement it with a single class. But it's fairly compact when you use method references and Collector.of(). Once you learn to accept it.
     
    Stephan van Hulst
    Saloon Keeper
    Posts: 13248
    291
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator

    Winston Gutkowski wrote:BTW, this is my try at a truly "insertable" Longest function, plagiarised entirely from Rob's, that doesn't rely on of():

    ...

    Does it look reasonable, or have I made yet more mistakes?


    I missed this post!

    Aside from the concerns I voiced earlier, there are a few mistakes. Your Collector doubles as an accumulator. That's a bit like having a class implement Comparator<ItSelf>.

    To implement Collector correctly, you need to implement
  • accumulator(),
  • characteristics(),
  • combiner(),
  • finisher(),
  • supplier().

  • That's why there is a Collector.of() factory method.
     
    You showed up just in time for the waffles! And this tiny ad:
    Building a Better World in your Backyard by Paul Wheaton and Shawn Klassen-Koop
    https://coderanch.com/wiki/718759/books/Building-World-Backyard-Paul-Wheaton
    reply
      Bookmark Topic Watch Topic
    • New Topic