Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I add map and filter when I extend ArrayList in Java?

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.

like image 420
ToMakPo Avatar asked Mar 17 '18 18:03

ToMakPo


2 Answers

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?

like image 50
Alexey Romanov Avatar answered Oct 30 '22 12:10

Alexey Romanov


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.

like image 3
Vinay Prajapati Avatar answered Oct 30 '22 13:10

Vinay Prajapati