210

Is it possible to cast a stream in Java 8? Say I have a list of objects, I can do something like this to filter out all the additional objects:

Stream.of(objects).filter(c -> c instanceof Client)

After this though, if I want to do something with the clients I would need to cast each of them:

Stream.of(objects).filter(c -> c instanceof Client)
    .map(c -> ((Client) c).getID()).forEach(System.out::println);

This looks a little ugly. Is it possible to cast an entire stream to a different type? Like cast Stream<Object> to a Stream<Client>?

Please ignore the fact that doing things like this would probably mean bad design. We do stuff like this in my computer science class, so I was looking into the new features of java 8 and was curious if this was possible.

Stuart Marks
  • 127,867
  • 37
  • 205
  • 259
Phiction
  • 2,387
  • 2
  • 12
  • 7
  • 3
    From the standpoint of the Java runtime the two Stream types are the same already, so no cast is required. The trick is to sneak it past the compiler. (That is, assuming it makes any sense to do so.) – Hot Licks Mar 19 '14 at 16:23

5 Answers5

376

I don't think there is a way to do that out-of-the-box. A possibly cleaner solution would be:

Stream.of(objects)
    .filter(c -> c instanceof Client)
    .map(c -> (Client) c)
    .map(Client::getID)
    .forEach(System.out::println);

or, as suggested in the comments, you could use the cast method - the former may be easier to read though:

Stream.of(objects)
    .filter(Client.class::isInstance)
    .map(Client.class::cast)
    .map(Client::getID)
    .forEach(System.out::println);
Scolytus
  • 16,338
  • 6
  • 46
  • 69
assylias
  • 321,522
  • 82
  • 660
  • 783
  • This is pretty much what I was looking for. I guess I overlooked that casting it to Client in `map` would return a `Stream`. Thanks! – Phiction Mar 19 '14 at 16:35
  • +1 interesting new ways, although they risk to get in spaghetti-code of a new-generation type (horizontal, not vertical) – robermann Mar 20 '14 at 08:44
  • @LordOfThePigs Yes it works although I am not sure if the code gets clearer. I have added the idea to my answer. – assylias Jun 22 '14 at 22:38
  • 51
    You could "simplify" the instanceOf filter with: `Stream.of(objects).filter(Client.class::isInstance).[...]` – Nicolas Labrot Nov 26 '14 at 21:43
  • This does not work for untyped Streams. For example `entityManager.createNativeQuery("SQL", Tuple.class).getResultStream().map(Tuple.class::cast).filter(t -> t.get(0) != null)`. It won't compile because it cannot find `.get()`. Even if you expand the parameter type in the lambda it still does not work. – T3rm1 Feb 02 '21 at 14:01
  • 1
    @T3rm1 when you work with raw types, many things become more difficult... – assylias Feb 02 '21 at 17:51
18

Along the lines of ggovan's answer, I do this as follows:

/**
 * Provides various high-order functions.
 */
public final class F {
    /**
     * When the returned {@code Function} is passed as an argument to
     * {@link Stream#flatMap}, the result is a stream of instances of
     * {@code cls}.
     */
    public static <E> Function<Object, Stream<E>> instancesOf(Class<E> cls) {
        return o -> cls.isInstance(o)
                ? Stream.of(cls.cast(o))
                : Stream.empty();
    }
}

Using this helper function:

Stream.of(objects).flatMap(F.instancesOf(Client.class))
        .map(Client::getId)
        .forEach(System.out::println);
Community
  • 1
  • 1
Brandon
  • 2,367
  • 26
  • 32
13

Late to the party, but I think it is a useful answer.

flatMap would be the shortest way to do it.

Stream.of(objects).flatMap(o->(o instanceof Client)?Stream.of((Client)o):Stream.empty())

If o is a Client then create a Stream with a single element, otherwise use the empty stream. These streams will then be flattened into a Stream<Client>.

ggovan
  • 1,907
  • 18
  • 21
  • I tried to implement this, but got a warning saying that my class "uses unchecked or unsafe operations" – is that to be expected? – aweibell Dec 10 '14 at 12:57
  • unfortunately, yes. Were you to use an `if/else` rather than the `?:` operator then there would be no warning. Rest assured you can safely supress the warning. – ggovan Dec 10 '14 at 17:46
  • 5
    Actually this is longer than `Stream.of(objects).filter(o->o instanceof Client).map(o -> (Client)o)` or even `Stream.of(objects).filter(Client.class::isInstance).map(Client.class::cast)`. – Didier L Jan 19 '16 at 16:17
8

This looks a little ugly. Is it possible to cast an entire stream to a different type? Like cast Stream<Object> to a Stream<Client>?

No that wouldn't be possible. This is not new in Java 8. This is specific to generics. A List<Object> is not a super type of List<String>, so you can't just cast a List<Object> to a List<String>.

Similar is the issue here. You can't cast Stream<Object> to Stream<Client>. Of course you can cast it indirectly like this:

Stream<Client> intStream = (Stream<Client>) (Stream<?>)stream;

but that is not safe, and might fail at runtime. The underlying reason for this is, generics in Java are implemented using erasure. So, there is no type information available about which type of Stream it is at runtime. Everything is just Stream.

BTW, what's wrong with your approach? Looks fine to me.

Rohit Jain
  • 209,639
  • 45
  • 409
  • 525
  • It is wrong (read: "ugly") because C# supports this in a very nice fahsion: `enumerable.Cast().Where(x => x.SomeMethodOnMyType())` – D.R. Mar 19 '14 at 16:17
  • 2
    @D.R. Generics in `C#` is implemented using reification, while in Java, it is implemented using erasure. Both are implemented in different fashion underlying. So you can't expect it to work same way in both the languages. – Rohit Jain Mar 19 '14 at 16:18
  • Of course, still, the Java code remains ugly in this case ;-) – D.R. Mar 19 '14 at 16:19
  • 1
    @D.R. I understand that erasure poses many issues for the beginners to understand the concept of generics in Java. And since I don't use C#, I can't go into much detail about comparison. But the whole motivation behind implementing it this way IMO was to avoid major changes in JVM implementation. – Rohit Jain Mar 19 '14 at 16:22
  • 1
    Why would it "certainly fail at runtime"? As you say there's no (generic) type information, so nothing for the runtime to check. It might *possibly* fail at runtime, if the wrong types are fed through, but there is no "certainty" about that whatsoever. – Hot Licks Mar 19 '14 at 16:26
  • 2
    @RohitJain: I'm not criticizing Java's generic concept, but this single consequence still creates ugly code ;-) – D.R. Mar 19 '14 at 16:28
  • 1
    @D.R. - Java generics are ugly from the git-go. Mostly just C++ feature lust. – Hot Licks Mar 19 '14 at 16:28
  • 1
    @D.R. @Rohit - the C# `enumerable.Cast` isn't actually "casting the stream". It is a standard iterator block doing a runtime cast of each element of the stream. See [here at the .NET reference sources](http://referencesource.microsoft.com/#System.Core/System/Linq/Enumerable.cs,152b93d25e224365). Java 8 streams could have done this too had they thought to provide it. (The difference is you'd get the exception for a failed runtime cast at a different point in the code.) You can't provide it nicely yourself because of the lack of C# extension methods, unfortunately. – davidbak Dec 16 '15 at 18:21
0

I found when dealing with an untyped collection of one class you can create a typed collection with

UntypedObjCollection bag = some preGenerics method;
List<Foo> foolist = new ArrayList<>();
bag.stream().forEach(o ->fooList.add((Foo)o));

There doesn't seem to be a way to cast Object into something and filter, map, etc... in one shot. The only way to get Object into a typed collection is a terminal operation. This is running JDK 17 and dealing with module.base exceptions when trying less granular methods.

Sherum
  • 63
  • 7