Hello.

My question is for the sake of curiosity, not being related to a real problem - or, better, the problem - which is tiny - can be fixed with a simple work around. But I'd like to blog a short post about it and I'd like to check I have all the context. It stemmed from a class about Java 8 that I recently taught and one of the participants asked about that.

I think that there's something that possibly involves the compiler - I'll eventually post the relevant part of the questions to the proper mailing list.


1. Everything starts from this code chunk that doesnt' compile:

Stream<String> s = IntStream.rangeClosed(1, 10) // just as an example to quickly create a Stream<String>
                .mapToObj(n -> "String #" + n);

        Files.write(Paths.get("/tmp/pippo.txt"), s);

error: no suitable method found for write(Path,Stream<String>)
        Files.write(Paths.get("/tmp/pippo.txt"), s);
    method Files.write(Path,byte[],OpenOption...) is not applicable
      (argument mismatch; Stream<String> cannot be converted to byte[])
method Files.write(Path,Iterable<? extends CharSequence>,Charset,OpenOption...) is not applicable (argument mismatch; Stream<String> cannot be converted to Iterable<? extends CharSequence>) method Files.write(Path,Iterable<? extends CharSequence>,OpenOption...) is not applicable (argument mismatch; Stream<String> cannot be converted to Iterable<? extends CharSequence>)


2. Variation.

        Files.write(Paths.get("/tmp/pippo.txt"), (Iterable<String>)s);

This gives:

Exception in thread "main" java.lang.ClassCastException: java.util.stream.IntPipeline$4 cannot be cast to java.lang.Iterable
        at StreamIteratorExample.main(StreamIteratorExample.java:13)

Ok, so far it's the fact described here

        
http://stackoverflow.com/questions/20129762/why-does-streamt-not-implement-iterablet

on why Stream doesn't implement Iterable.

Question A: Is the SO answer "because iterator() is usually supposed to be callable multiple times, while in a Stream it can't" correct?


3. This is the known trick around the problem:

        final Iterable<String> i = s::iterator;
        Files.write(Paths.get("/tmp/pippo.txt"), i);

It works and I think I understand why (Iterable has the same functional descriptor of Supplier<Iterator>, which is s::iterator, so they are compatible in assignment - right?).


4. But at this point putting it into the same line gives compilation error:

        Files.write(Paths.get("/tmp/pippo.txt"), s::iterator);

error: no suitable method found for write(Path,s::iterator)
        Files.write(Paths.get("/tmp/pippo.txt"), s::iterator);
    method Files.write(Path,byte[],OpenOption...) is not applicable
      (argument mismatch; Array is not a functional interface)
method Files.write(Path,Iterable<? extends CharSequence>,Charset,OpenOption...) is not applicable
      (argument mismatch; bad return type in method reference
          Iterator<String> cannot be converted to Iterator<CharSequence>)
method Files.write(Path,Iterable<? extends CharSequence>,OpenOption...) is not applicable
      (argument mismatch; bad return type in method reference
          Iterator<String> cannot be converted to Iterator<CharSequence>)


5. This at last works:

Files.write(Paths.get("/tmp/pippo.txt"), (Iterable<String>)s::iterator);

I assume that the compiler can't autonomously infer that s::iterator is compatible with Iterable<String> so the cast is needed. I have a question about that (feature or bug?), but this is related to the compiler, so I think is OT here.

At last, question B: Given all those premises, is there a specific reason for which Files.write() hasn't been overloaded with a version capable of accepting a Stream<String>? It would have been the perfect complement of Files.lines()


Thanks.

--
Fabrizio Giudici - Java Architect @ Tidalwave s.a.s.
"We make Java work. Everywhere."
http://tidalwave.it/fabrizio/blog - fabrizio.giud...@tidalwave.it

Reply via email to