【问题标题】:Extending List<T> in Java 8在 Java 8 中扩展 List<T>
【发布时间】:2014-04-14 01:36:45
【问题描述】:

我经常想将一个列表映射到另一个列表。例如,如果我有一个人员列表,并且我想要他们的姓名列表,我想这样做:

目标

List<Person> people = ... ;

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

Java 8 可以实现这样的事情:

JAVA 8 版本

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

但这显然不是那么好。其实我觉得用番石榴更干净:

番石榴版本

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

但是,我确实喜欢链接。那么,我的目标可行吗?

我听说有人说 Java 8 默认方法类似于 C# 扩展方法。使用 C# 扩展方法,我可以轻松地将辅助方法添加到 IEnumerable&lt;T&gt;

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

但是我不知道如何使用默认方法来扩展现有接口

另外,这显然是一个微不足道的例子。一般来说,我希望能够扩展List&lt;T&gt;Iterable&lt;T&gt; 接口,以使与流API 的交互更容易。

【问题讨论】:

  • 请注意collect(Collectors.toList()) 将返回一个副本,而Lists.transform 返回一个视图。
  • 您可以随时创建自己的接口并创建类似于transform(list).using(x -&gt; x.getName())的代码
  • 仅供参考:使用Foo::getName 代替x -&gt; x.getName() 会更简单。
  • @LouisWasserman 是的,我可以做到。不过,我仍在决定是否更喜欢 lambda 语法……截至目前,我认为开发人员发现 lambdas 比方法引用更容易阅读,尤其是来自 coffeescript 或 C# 或其他什么的时候。
  • @Alden,Java 类型系统不如 Scala 那样富有表现力,而且 Java 不是一种动态语言,因此(语言和库的作者)不可能改进API 做你想做的事,而不会失去灵活性(例如简单的并行性和惰性)或向现有接口添加 huge 膨胀(例如,他们要直接将 map 方法添加到 List 类)。而且由于 Java 没有提供任何扩展现有类型的方法,因此您实际上陷入了困境。对不起:(

标签: java lambda guava java-8 default-method


【解决方案1】:

没有;你不能那样做。

默认方法与扩展方法不同;它们只能在原始界面中定义。

【讨论】:

  • 那很不幸......我一直在试图弄清楚这是否可能。我以您的巨大声誉假设您是正确的:)。有关使流 api 更清洁的任何建议?
  • @Alden,不幸的是,Java 没有针对expression problem 的解决方案,这与 Scala 和其他语言不同。因此,您必须创建自己的类似 DSL 的包装器(或者只是静态函数,如在 Guava 中)或使用已经存在的东西。
  • 或者您可以随时创建一个新的集合对象,在构造函数中使用 list 作为支持类型,并在新的集合对象中创建所有功能。
【解决方案2】:

如果您想为List 应用Function 并支持链接,您可以这样做:

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));
    }
}

它支持 GUAVA 风格的映射列表创建,同时仍然允许使用 Stream API 和映射列表懒惰地评估所有值:

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;
}
为 a 做计算 计算 10 第一个结果:100 不再计算任何值 与流 API 交互: 为 a 做计算 计算 10 做简单的计算 为 1724345618 做计算 可选[410277188]

如果您想创建一个带有预先计算值的List,您可以简单地使用new ArrayList&lt;&gt;(mappedList)

【讨论】:

    【解决方案3】:

    吃我自己的狗粮并实施我在评论中的建议(未经测试,但它应该有效 - 请注意,您应该在适当的情况下使用super,这不是我的强项):

    public final class ListTransformer<T>
    {
        private final List<T> inputList;
    
        public static <X> ListTransformer<X> transform(final List<X> inputList)
        {
            return new ListTransformer<X>(inputList);
        }
    
        private ListTransformer(final List<T> inputList)
        {
            this.inputList = inputList;
        }
    
        public <U> List<U> using(final Function<T, U> f)
        {
            return inputList.stream().map(f).collect(Collectors.toList());
        }
    }
    

    用法:

    import static wherever.is.ListTransformer.transform;
    
    //
    final List<String> names = transform(personList).using(x -> x.getName());
    

    【讨论】:

    • 我确定您在示例中指的是import static wherever.is.ListTransformer.transform :)
    • @Holger 为什么不呢。但另一方面,为什么不呢?有些人更喜欢流畅的界面...
    • @Holger,在我看来,它是 your 解决方案,没有任何好处;归根结底,这是一个品味问题。 OP喜欢链式,我的方案提供链式,你的解决方案提供链式,仅此而已
    【解决方案4】:

    C# 扩展方法很棒,并且提供了简单的机制来使用自己的方法扩展 C# 集合。

    但是现在java中有流。 基于@fge 的响应,我用这个 sn-p 在流上编写自己的扩展方法:

    public final class StreamExtender<T>
    {
        private final Stream<T> _inputStream;
    
        public static <T> StreamExtender<T> extend(final Stream<T> inputStream)
        {
            return new StreamExtender<>(inputStream);
        }
    
        private StreamExtender(final Stream<T> inputStream)
        {
            this._inputStream = inputStream;
        }
    
        public <U> List<U> extensionMethod(final Function<T, U> f)
        {
            // your own code.
            return _inputStream.map(f).collect(Collectors.toList());
        }
    }
    

    看看它的实际效果:

    Integer[] array = { 1, 2, 3};
    List<String> result = StreamExtender.extend(stream(array)).extensionMethod(Object::toString);
    

    【讨论】:

      猜你喜欢
      • 2012-12-01
      • 1970-01-01
      • 1970-01-01
      • 2014-05-06
      • 1970-01-01
      • 1970-01-01
      • 2011-02-02
      • 1970-01-01
      • 2011-10-01
      相关资源
      最近更新 更多