On 07/22/2015 10:10 AM, Paul Sandoz wrote:
On 20 Jul 2015, at 19:08, Remi Forax <[email protected]> wrote:

Hi all, hi Paul,
I've found that the signature of Stream.generate doesn't to use a wildcard 
hence some program are rejected even if there are valid, by example:
  public static void main(String[] args) {
      Supplier<String> supplier = () -> "hello";
      Stream<CharSequence> s = Stream.generate(supplier);
  }

The fix is simple, in interface Stream, generate should be declared like this :
  public static<T> Stream<T> generate(Supplier<? extends T> s)
and
the field 's' of the InfiniteSupplyingSpliterator.OfRef should be also declared as a 
Supplier<? extends T>.

I believe such changes to Stream.generate are backwards compatible because 
overriding is not possible. (In addition static method on an interface are 
scoped to that interface, but i am not sure if that matters in this case.)

The fact that a static method on a class is accessible from a subclass doesn't help my students to understand how overriding works and i'm glad that Kevin Bourillion suggests that for static methods in interface we can fix that.

I believe that if Stream was a class, it might cause an issue because i remember that the JLS defines name-clash even on static methods i.e. a code like this is illegal:

class A {
  static void m(Supplier<Object> s) { ... }
}
class B extends A {
  static void m(Supplier<? extends Object> s) { ... }  // name clash
}


We could also adjust Stream.iterate as well:

   public static<T, S extends T> Stream<T> iterate(final S seed, final 
UnaryOperator<S> f)

Seems appropriate for consistency with Stream.generate. I presume that is 
backwards compatible as well?

yes !
as you said, these changes are source backward compatible, the static methods now accept more potential types and binary backward compatible because the erasures are not changed.

BTW, in the body of iterate(), the cast of Stream.NONE to T is plainly wrong from the type system point of view, it only works because of erasure. A slightly less wrong code is to store the current element in an Object and do the cast from Object to T (or S with the new signature) when calling f because the parameter is always a T at that point.

         final Iterator<T> iterator = new Iterator<T>() {
            private Object element = Streams.NONE;

            @Override
            public boolean hasNext() {
                return true;
            }

            @Override
            @SuppressWarnings("unchecked")
            public T next() {
                Object e = element;
                S value = (e == Streams.NONE) ? seed : f.apply((S)e);
                element = value;
                return value;
            }
        };

The code of next() is less readable because in Java when you write a = b = c, the type of b = c is the type of b instead of being the type of c (like in Kotlin or Groovy). A fun hacky solution to simplify the code, and make it obviously not readable, is to use a try/finally here :)

            public T next() {
                T value;
                try {
return value = (element == Streams.NONE) ? seed : f.apply((S)element);
                } finally {
                    element = value;
                }
            }


Paul.

Rémi

Reply via email to