9
Stream st = Stream.of(1,2,3,4);

Does this create a stream of Integers (By inferring at runtime) or a Stream of Objects?

st.collect(Collectors.averagingInt((Integer x)->x));

This line compiles fine.

st.forEach((Integer x)-> System.out.println(x));

This line does not compile. It gives a compilation error at (Integer x), saying expected object but found Integer. Why?

Why does it not complain the same for the averagingInt method above?

averagingInt takes the ToIntFunction interface with raw reference type as <? super T> and allowing Integer value. While forEach is taking the Consumer interface with the same raw reference type as <? super T> and the compiler is complaining for the Integer argument value.

Boann
  • 48,794
  • 16
  • 117
  • 146
Khuzi
  • 2,792
  • 3
  • 15
  • 26
  • 1
    The signatures are incomparable - [`Collectors.averagingInt`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors.html#averagingInt-java.util.function.ToIntFunction-) doesn't accept the same argument as [`Stream.forEach`](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#forEach-java.util.function.Consumer-). Thus, `forEach` is raw and would expectedly fail if you tried to convert an `Object` to an `Integer`. – Makoto Feb 03 '19 at 00:00
  • averagingInt also takes ToIntFunction interface with raw reference type as super T>. how is it allowing the same thing? Similarly forEach is taking Consumer interface with raw reference type as super T> and the compiler is complaining for the same. – Khuzi Feb 03 '19 at 00:05
  • return types are different that is why it is giving a compile time error. – Vishwa Ratna Feb 03 '19 at 02:16
  • **`IntStream.of`** - the special stream for the primitive (non-Object) type `int` should be used. – Joop Eggen Feb 04 '19 at 15:11

3 Answers3

4

Does this create stream of Integers (By inferring at runtime) or Stream of Objects ?

Generics do not exist at runtime, there is nothing to "infer" at runtime. Since you have used a raw type for the Stream st - it is a Stream<Object> if you want.

Well, even what you wrote for (and does compile):

st.collect(Collectors.averagingInt((Integer x)->x));

would not compile when you try to add the expected result:

Double i = st.collect(Collectors.averagingInt((Integer x) -> x));

I wonder if you re-write like below would make more sense:

Collector<Integer, ?, Double> collector = Collectors.averagingInt((Integer x) -> x);
Double result = st.collect(collector); // does not compile

Inference would work for Collectors.averagingInt, but since you have used Stream as raw - everything else is raw too.

Eugene
  • 117,005
  • 15
  • 201
  • 306
2

As was told above you are trying to use raw Stream. If you want to iterate across Integer then you need to specify generic for Stream.

For instance

Stream<Integer> st = Stream.of(1,2,3,4);

Then it will compile.

This probalem is equuvalent to problem with iterating across generic colection in old style

Collection values = Arrays.asList(1,2,3,4);
for(Integer v: values){
    System.out.println(v);
}

The code above won't compile because values is list of objects not integers.

As for averagingInt it will compile even if you will provide a list of String. For instance

    Stream st = Stream.of("1","2","3","4");
    st.collect(Collectors.averagingInt((Integer x)->x));

However it will failed during runtime with ClassCastException. The difference here is that averagingInt tryes to cast all incoming types to specific and forEach just uses types as is what may cause compilation fail.

It is equivalent

    ToIntFunction<? super Integer> mapper = new ToIntFunction<Integer>(){
        @Override
        public int applyAsInt(Integer value) {
            return value;
        }
    };

    st.collect(Collectors.averagingInt(mapper));

The anonymous class will be used inside of averagingInt and before passing args to applyAsInt method it will be cast to appropriate type (in my sample to Integer )

Another interesting thing

Stream st = Stream.of(1,2,3,4);

Consumer<String> stringConsumer = new Consumer<String>() {
    @Override
    public void accept(String o) {

    }
};

st.forEach(stringConsumer); // will compile disregarding to raw type of Stream and specific Consumer

st.forEach((Integer  a) -> System.out.println(a)); // will fail because os raw Stream 
mger1979
  • 109
  • 6
  • Even forEach method is based on Consumer functional interface which is taking raw type too. And can you please point out where averaginInt tries to cast all incoming types to specific types? – Khuzi Feb 03 '19 at 04:48
  • Before calling method applyAsInt. This issue is easier to understand without lymbda, for instance if you will create TreeSet with Comparator for integer and then will try to add String into it then compiler will allow you to compile, but it will fail in runtime because JVM wil try to cast all incoming to Comparator args to Integer. – mger1979 Feb 03 '19 at 05:08
  • Why are you considering ToIntFunction with ref type as super Integer> when stream elements are of type Object? – Khuzi Feb 03 '19 at 17:13
  • Because in structure '(Integer x)->x' you are using 'integer'. – mger1979 Feb 03 '19 at 20:31
  • BTW as Eugene told all generic templates (like ) will be removed after compilation, however some classes may have code that casts to that templates, in that case it will be replaced by real type. For instance Object a = (T) myString; in case if T was String then it will became Object a = (String) myString; after compilation. – mger1979 Feb 03 '19 at 20:43
  • I am also using (Integer x) for Consumer interface in forEach. Why Compiler is complaining in that case? And this is what troubling me that why is it working for IntFunction and not for Consumer? and I am not worried about after compilation phase when all of the generics code is removed. I understand that concept. – Khuzi Feb 04 '19 at 05:10
  • You are right i've just added some more specific to the case sample. – mger1979 Feb 04 '19 at 18:25
1

The signatures are different.

Collectors.averagingInt accepts a ToIntFunction, of which the return type of the operation collect is bound to the type of Stream, which is raw.

Stream.forEach accepts a Consumer, which is directly bound to the type of the Stream.

The raw type is interfering with your ability to specify the type in forEach so you result in a compilation error, because of type safety.

Makoto
  • 104,088
  • 27
  • 192
  • 230
  • 3
    Your answer sounds unclear, not helping in clarifying my doubts. Please explain concisely with an example in hand. PS: return type of the operation collect is bound to the return type of Collector method which is averagingInt method here. My question is more about the argument type which is going in the lambdas. – Khuzi Feb 03 '19 at 01:50