I have an unexpected situation in a project in which all types extending one class are packed into a Java collection; but only a specific extension of that class contains an additional method. Let’s call it “also()”; and let me be clear that no other extension has it. Right before performing one task on every item in that collection, I need to call also() on every item that implements it.
The easiest way forward is this:
stuff.stream().filter(item -> item instanceof SpecificItem)
.forEach(item -> ((SpecificItem)item).also()));
stuff.stream().forEach(item -> item.method());
It works fine, but I’m not comfortable with the “instanceof” in there. That’s generally a marker of bad code smell. It is very possible that I will refactor this class just to get rid of it. Before I do something that dramatic, though, I thought I would check with the community and see if someone with more experience with either Streams or Collections had a simpler solution.
As an example (certainly nonexclusive), is it possible to get a view of a collection that filters entries by class?
11
I’d suggest tossing in a .map
call to do the cast for you. Then your later code can use the ‘real thing’.
Before:
stuff.stream().filter(item -> item instanceof SpecificItem)
.forEach(item -> ((SpecificItem)item).also()));
After:
stuff.stream().filter(item -> item instanceof SpecificItem)
.map(item -> (SpecificItem)item)
.forEach(specificItem -> specificItem.also()));
This isn’t perfect, but seems to clean things up a little.
2
I have an unexpected situation in a project in which all types extending one class are packed into a Java collection; but only an extension of that class implements a method. Let’s call it “also()”. Right before performing one task on every item in that collection, I need to call also() on every item that implements it.
That is obviously a defective design. From what you wrote it is not clear, what it means, that one class does not implement the method. If it simply does nothing, it wouldn’t matter, so I assume there is an unwanted side effect.
It works fine, but I’m not comfortable with the “instanceof” in there.
Your guts are right. Good object oriented design would work without further knowledge what exactly an object, resp whether it is an instance of a special kind.
Before I do something that dramatic, though, I thought I would check with the community and see if someone with more experience with either Streams or Collections had a simpler solution.
Refactoring is not dramatic. It improves code quality.
With your given codebase, the simplest solution would be, to make sure, you have two separate collections, one with the parent and one with the child class. But that’s not quite clean.
4
It’s hard to give specific recommendations without knowing what SpecificItem
and also()
actually are, but:
Define also()
on the superclass of SpecificItem
. Give it a default implementation which does nothing (give it an empty method body). Individual subclasses can override it if desired. Depending on what also
actually is, you may need to rename it to something which makes sense for all the classes involved.
1
I hope I’m not missing anything obvious here (also as suggested by @Tristan Burnside’s comment), but why can’t SpecificItem.method()
call also()
first?
public class SpecificItem extends Item {
...
public void method() {
also();
super.method();
}
}
As an example (certainly nonexclusive), is it possible to get a view of a collection that filters entries by class?
A stream-y way I can think of, at the expense of maybe some performance impact( YMMV), is to collect()
via Collectors.groupingBy()
on the class as the key, then pick what you want from the resulting Map
. The values are stored as a List
, so if you were expecting to do such a filtering on a Set
and you hope to get a filtered Set
out of it, then you will need an additional step to further put the values into a resulting Set
as well.
an alternative:
- make SpecificItem implement an interface (say “Filterable”)
- make a new class that extends Stream
- create a new ‘filter’ method which accept object that implement your interface
- override the original stream method and redirect it to your implementation (by casting the predicate parameter to your interface)
that way only objects which implement your interface would be able to pass themeselves to your method… no need to use instanceof
public class MyStream extends Stream
{
//...
Stream<Filterable> filter(Predicate<? implements Filterable> predicate)
{
return super.filter(predicate);
}
}
3
Here is my approach:
stuff.stream().filter(item -> SpecificItem.class.isInstance(item)).map(item -> SpecificItem.class.cast(item)).forEach(item -> item.also());
stuff.stream().forEach(item -> item.method());
No instanceof, no explicit casts (actually it is an explicit cast, but masked by a method call). I think this is as good as it can get.
2