I am trying to create a table
class that extends ArrayList
. In it, I would like to be able to create a map
method that takes a lambda expression and returns a new table
with the mapped values. I would also like to do this with filter
. I use the map and filter a lot and I don't like typing out the whole thing over and over.
public abstract class Table<E extends Element> extends ArrayList<E> {
// a lot of other stuff.
public Table<E> map(/*WHAT DO I PUT HERE?*/ mapper) {
return this.stream().map(mapper).collect(/*WHAT DO I PUT HERE?*/);
}
public Table<E> filter(/*WHAT DO I PUT HERE?*/ predicate) {
return this.stream().filter(predicate).collect(/*WHAT DO I PUT HERE?*/);
}
}
I am still trying to figure out generics. Maybe there is a better way. I don't know. I have tried duplicating what is in the original code for the ArrayList
, but everything I try seems to create new problems.
On one hand, it's entirely possible:
public abstract class Table<E extends Element> extends ArrayList<E> {
// implement in concrete subclasses
public abstract <E1 extends Element> Collector<E1, ?, Table<E1>> collector();
// Collector<E, ?, Table<E>> collector() is enough if map doesn't have E1 type parameter
public <E1 extends Element> Table<E1> map(Function<E, E1> mapper) {
return this.stream().map(mapper).collect(collector());
}
public Table<E> filter(Predicate<E> predicate) {
return this.stream().filter(predicate).collect(collector());
}
}
public class ChildTable<E extends Element> extends Table<E> {
@Override
public <E1 extends Element> Collector<E1, ?, Table<E1>> collector() {
return Collectors.toCollection(() -> new ChildTable<E1>());
// or simpler Collectors.toCollection(ChildTable::new);
}
}
collector()
could be implemented, but it would have to return a specific subtype of Table
.
On the other, it may be better to have the list as a field of Table
instead of extending it: prefer composition over inheritance. Do you really want all ArrayList
methods available?
Simply use Function<E, E>
for mapper as your return type is same object and Predicate<E>
for predicate as E
is to be predicated.
For Table whatever implementation class you have that you be used inside collect. See the way collect works.
It basically takes an initializer (first argument
), BiFunction to specify how to add the element to the collection (second argument
) and finally BiFunction to specify how it works in case of the parallel stream (third argument
). See the code below:-
public abstract class Table<E extends Element> extends ArrayList<E> {
public Table<E> map(Function<E, E> mapper) {
return this.stream().map(mapper).collect(TableImpl::new, TableImpl::add, TableImpl::addAll);
}
public Table<E> filter(Predicate<E> predicate) {
return this.stream().filter(predicate).collect(TableImpl::new, TableImpl::add, TableImpl::addAll);
}
}
class TableImpl extends Table {
//whatever you impl is
}
Please feel free to extract the collect implementation to a Collector
to avoid duplication of code.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With