Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extending List<T> in Java 8

I often want to map one list into another list. For example if I had a list of people, and I wanted a list of their names, I would like to do:

GOAL

List<Person> people = ... ;

List<String> names = people.map(x -> x.getName());

Something like this is possible with Java 8:

JAVA 8 VERSION

List<String> names = people.stream()
                           .map(x -> x.getName())
                           .collect(Collectors.toList());

But this is clearly not as nice. In fact, I think using Guava is cleaner:

GUAVA VERSION

List<String> names = Lists.transform(people, x -> x.getName());

However, I do like chaining. So, is my goal possible?

I have heard people say that Java 8 default methods are similar to C# extension methods. With a C# extension method, I could easily add a helper method to IEnumerable<T>:

public static IEnumerable<TRet> Map<T, TRet>(this IEnumerable<T> list, Func<T, TRet> selector)
{
    return list.Select(selector);
}

However I can't figure out how to use default methods to extend an existing interface.

Also, this is obviously a trivial example. In general, I would like to be able to extend the List<T> and Iterable<T> interfaces to make interacting with the streams api easier.

like image 603
Alden Avatar asked Mar 10 '14 18:03

Alden


People also ask

How do you return a long List in Java?

get(i); return longs; ... Turns the Long list into a long array. if (isEmpty(members)) { return null; List<Long> result = new ArrayList<Long>(); Iterator<String> iterator = members. iterator(); while (iterator.

How do I add a List to an existing List in Java?

You insert elements (objects) into a Java List using its add() method. Here is an example of adding elements to a Java List using the add() method: List<String> listA = new ArrayList<>(); listA. add("element 1"); listA.


2 Answers

No; you can't do that.

Default methods are not the same as extension methods; they can only be defined within the original interface.

like image 99
SLaks Avatar answered Nov 13 '22 03:11

SLaks


If you want to have a lightweight view to a List applying a Function and supporting chaining you can do it like this:

import java.util.*;
import java.util.function.Function;
import java.util.stream.Stream;

public class MappingList<E> extends AbstractList<E> {
  // using this helper class we avoid carrying <S> with the public API
  static final class Source<E,S> {
      final List<S> list;
      final Function<? super S, ? extends E> mapper;
      Source(List<S> l, Function<? super S, ? extends E> m) {
          list=l;
          mapper=m;
      }
      E get(int index) { return mapper.apply(list.get(index)); }
      <T> Source map(Function<? super E, ? extends T> f) {
          Objects.requireNonNull(f);
          return new Source<>(list, mapper.andThen(f));
      }
      Stream<E> stream() { return list.stream().map(mapper); }
      Stream<E> parallelStream() { return list.parallelStream().map(mapper); }
    }
    final Source<E,?> source;

    private MappingList(Source<E,?> s) {
        Objects.requireNonNull(s);
        source=s;
    }
    @Override
    public E get(int index) {
        return source.get(index);
    }
    @Override
    public int size() {
        return source.list.size();
    }
    @Override
    public Stream<E> stream() {
        return source.stream();
    }
    @Override
    public Stream<E> parallelStream() {
        return source.parallelStream();
    }
    public <T> MappingList<T> map(Function<? super E, ? extends T> f) {
        return new MappingList<>(source.map(f));
    }
    public static <S,T> MappingList<T> map(
      List<S> l, Function<? super S, ? extends T> f) {
        Objects.requireNonNull(l);
        if(l instanceof MappingList)
            return ((MappingList<S>)l).map(f);
        return new MappingList<>(new Source<>(l, f));
    }
}

It supports a GUAVA style creation of a mapped list while still allowing to use the Stream API with the mapped list evaluating all values lazily:

public static void main(String[] arg) {
    List<String> strings=Arrays.asList("a", "simple", "list");
    List<Integer> ints=MappingList.map(strings, s->compute(s));
    List<Integer> results=MappingList.map(ints, i->compute(i));
    for(int result:results) {
        System.out.println("first result: "+result);
        System.out.println("Not computing any more values");
        break;
    }
    System.out.println();
    System.out.println("  interacting with stream API:");
    System.out.println(results.stream().filter(i-> i>500).findFirst());
}
public static int compute(String s) {
    System.out.println("doing computation for "+s);
    return Integer.parseInt(s, 36);
}
public static int compute(int i) {
    System.out.println("doing computation for "+i);
    return i*i;
}
doing computation for a
doing computation for 10
first result: 100
Not computing any more values

  interacting with stream API:
doing computation for a
doing computation for 10
doing computation for simple
doing computation for 1724345618
Optional[410277188]

If you want to create a List with pre-calculated values out of it you can simply use new ArrayList<>(mappedList).

like image 31
Holger Avatar answered Nov 13 '22 02:11

Holger