转换Set为List而不创建新的List

603

我正在使用这段代码将一个 Set 转换为一个 List:

Map<String, List<String>> mainMap = new HashMap<>();

for (int i=0; i < something.size(); i++) {
  Set<String> set = getSet(...); //returns different result each time
  List<String> listOfNames = new ArrayList<>(set);
  mainMap.put(differentKeyName, listOfNames);
}

我想避免在每次循环迭代时创建一个新列表。这是否可能?


1
我知道一种将集合转换为列表的方法,就像在Q中一样。我想避免在循环中每次创建新列表。 - Muhammad Imran Tariq
4
为什么你不能直接将Set添加到主List中?为什么需要将Set转换为List? - DagR
1
你的意图是创建一个 List<List<?>> 吗? - Hiery Nomus
6
无法翻译。你的问题包含一个自相矛盾的悖论。 - user207421
15个回答

902
你可以使用List.addAll()方法。它接受一个集合作为参数,而你的集合就是一个集合。
List<String> mainList = new ArrayList<String>();
mainList.addAll(set);

编辑:针对问题的编辑作出回应。
很容易看出,如果您想要一个将List作为值的Map,为了拥有k个不同的值,您需要创建k个不同的列表。
因此:您无法完全避免创建这些列表,必须创建这些列表。

可能的解决方法:
将您的Map声明为Map<String,Set>Map<String,Collection>,然后插入您的集合。


1
抱歉,应该是mainMap而不是list。请查看问题。 - Muhammad Imran Tariq
@imrantariq:我猜你想要为每个键创建一个不同的列表? - amit
@imrantariq:你所请求的是不可能实现的。请阅读我的编辑以了解更多详细信息。 - amit
你可以想象一种方法,将一个集合包装成一个列表,类似于使用Collections.unmodifiableList将列表包装成不可变列表。拥有这样的API可能并不是一个好主意,因为插入操作可能会改变顺序,但仍然可以考虑实现这个方法。 - Kire Haglin
@basheer 使用 ArrayList 构造函数的一行解决方案更简单。我不明白为什么这个答案得到了这么多赞。 - user1075613
显示剩余2条评论

489

使用构造函数进行转换:

List<?> list = new ArrayList<>(set);

26
他明确表示他想避免这种情况。 - mapeters
9
不相关,因为他的要求无法实现。 - user207421
19
那么他的回答需要这样说,而不是仅仅陈述问题的答案并没有任何解释。 - mapeters
4
他在避免它,那个构造器使用System.arrayCopy,它会进行浅拷贝,也就是说,它只会将对象的引用复制到用于创建列表的数组中。如果你比较这两个集合,你会发现它们都包含指向相同对象的引用。 - Gubatron
这在安卓上实际上不起作用。有什么原因吗? - kbluue

93

同样来自Guava Collect库,您可以使用newArrayList(Collection)

Lists.newArrayList([your_set])

这与之前的答案非常相似,只是您不需要声明(或实例化)任何list对象。


2
如果您正在使用guava库,这会很方便。 - vsingh
9
虽然您没有直接调用构造函数,但该方法仍会调用ArrayList的构造函数。 - glen3b
如果我没有声明一个List,我怎么能使用已创建的List呢? - Koray Tugay
2
你有什么猜测为什么会使用这种方法吗?它似乎并没有比new ArrayList<>([your_set])更好。 - DavidS
1
@DavidS:工厂(查找概念和优势)风格的方法通常更灵活、更通用,更易于编写和处理。例如,假设您在变量中提供了一个null集,并希望在这种情况下获得一个null列表。 - Andreas Covidiot
显示剩余3条评论

57

我们可以在Java 8中使用以下一行代码:

List<String> list = set.stream().collect(Collectors.toList());

这里有一个小例子:

public static void main(String[] args) {
        Set<String> set = new TreeSet<>();
        set.add("A");
        set.add("B");
        set.add("C");
        List<String> list = set.stream().collect(Collectors.toList());
}

11
为了易读性,不建议这样做。例如,IntelliJ建议使用“new ArrayList<>(set)”并列出了超过20个类似的代码示例,可以用相同的方式替换。 - rrhrg
确切地说!@rrhrg 如果我们使用 set.parallelStream(),哪个更好性能? - Gaurav
Collectors.toList() 在内存中创建一个新的列表。 - cdalxndr
2
直到今天,我仍然不知道为什么这个评论会得到那么多赞,尤其是当它变得更加复杂时,正如其他评论所指出的那样。此外,它正在创建一个新的列表,而作者试图避免这种情况。 - Catalin Pirvu
更不用说首先转向流的性能惩罚,对于这样一个简单的事情。 - GhostCat

43

最简单的解决方案

我想要一种非常快速的方法来将我的集合转换为列表并返回它,所以我只用了一行代码:

 return new ArrayList<Long>(mySetVariable);

1
这也是IntelliJ IDEA建议使用而不是流API的内容。 - Ben

17

自Java 10以来,你可以使用新的copyOf工厂方法,这一点到目前为止还没有提到:

List.copyOf(set);

来自 Javadoc 的说明:

返回一个不可修改的列表(unmodifiable List),其中包含给定集合的元素,按其迭代顺序排列。

请注意,这将在幕后创建一个新列表(准确地说是ImmutableCollections$ListN),方法是通过:

  1. 在给定集合上调用Collection#toArray()
  2. 将这些对象放入新数组中。

3
很遗憾,“List.copyOf”仍会分配新的内存空间来存储项目。 - cdalxndr
@cdalxndr 是的,就像其他答案中的 95% 一样,这就是我为了完整性而添加的原因。不过,如果你想要的话,我很乐意添加免责声明? - beatngu13
我因为这个问题已经给95%的其他答案投了反对票。当然,这个答案应该包含信息,说明它并没有回答OP的问题要求。 - cdalxndr
1
基于提供的代码片段,你无法确定通过 getSet(...) 检索到什么类型的集合实现,它是否实际上已排序。并且在创建映射之后发生了什么也不清楚。标题说将集合转换为列表而不创建新列表。我认为,如果创建新列表违反了要求,那么转换为不同类型也是如此。无论如何,我认为我们可以各自坚持己见。 - beatngu13
1
请注意,当有一个单独的大集合需要转换为列表时,内存复杂度为O(n)(复制所需的内存加倍)。因此,与noop多态性或“视图包装器”相反,它并不是“可以忽略不计”的。 - cdalxndr
显示剩余2条评论

5
您可以使用这一行代码进行更改:Arrays.asList(set.toArray(new Object[set.size()]))
Map<String, List> mainMap = new HashMap<String, List>();

for(int i=0; i<something.size(); i++){
  Set set = getSet(...); 
  mainMap.put(differentKeyName, Arrays.asList(set.toArray(new Object[set.size()])));
}  

修正大小,因为新的Object[0]只能容纳一个元素,但是新的Object[set.size()]可以容纳所有值。 - rajadilipkolli

5
为了完整起见...
假设你确实想把Map的值作为List来处理,但是又想避免每次将Set复制到List中。
例如,您可能正在调用一个创建Set的库函数,但是您将Map<String,List<String>>结果传递给一个(设计不良但不在您手中)仅接受Map<String,List<String>>的库函数,尽管您“不知何故地知道”它对List所做的操作同样适用于任何Collection(因此任何Set)。而且,由于某种原因,您需要避免将每个Set复制到List的速度/内存开销。
在这种超级小众的情况下,根据库函数需要从您的List中获得的行为(可能无法知道),您可以在每个Set上创建一个List视图。请注意,这本质上是不安全的(因为库函数对每个List的要求可能会发生变化而不为您所知),因此应优先选择另一种解决方案。但是以下是如何执行此操作的。
您将创建一个实现List接口的类,在构造函数中使用Set并将该Set分配给一个字段,然后使用该内部Set来实现List API(在可能和期望的范围内)。
请注意,有些List行为您将无法模拟而不将元素存储为List,并且有些行为您只能部分模拟。同样,此类不是一般List的安全替代品。特别是,如果您知道使用情况需要与索引相关的操作或更改List,则此方法将非常快速地失效。
public class ListViewOfSet<U> implements List<U> {
    private final Set<U> wrappedSet;
    public ListViewOfSet(Set<U> setToWrap) { this.wrappedSet = setToWrap; }

    @Override public int size() { return this.wrappedSet.size(); }
    @Override public boolean isEmpty() { return this.wrappedSet.isEmpty(); }
    @Override public boolean contains(Object o) { return this.wrappedSet.contains(o); }
    @Override public java.util.Iterator<U> iterator() { return this.wrappedSet.iterator(); }
    @Override public Object[] toArray() { return this.wrappedSet.toArray(); }
    @Override public <T> T[] toArray(T[] ts) { return this.wrappedSet.toArray(ts); }
    @Override public boolean add(U e) { return this.wrappedSet.add(e); }
    @Override public boolean remove(Object o) { return this.wrappedSet.remove(o); }
    @Override public boolean containsAll(Collection<?> clctn) { return this.wrappedSet.containsAll(clctn); }
    @Override public boolean addAll(Collection<? extends U> clctn) { return this.wrappedSet.addAll(clctn); }
    @Override public boolean addAll(int i, Collection<? extends U> clctn) { throw new UnsupportedOperationException(); }
    @Override public boolean removeAll(Collection<?> clctn) { return this.wrappedSet.removeAll(clctn); }
    @Override public boolean retainAll(Collection<?> clctn) { return this.wrappedSet.retainAll(clctn); }
    @Override public void clear() { this.wrappedSet.clear(); }
    @Override public U get(int i) { throw new UnsupportedOperationException(); }
    @Override public U set(int i, U e) { throw new UnsupportedOperationException(); }
    @Override public void add(int i, U e) { throw new UnsupportedOperationException(); }
    @Override public U remove(int i) { throw new UnsupportedOperationException(); }
    @Override public int indexOf(Object o) { throw new UnsupportedOperationException(); }
    @Override public int lastIndexOf(Object o) { throw new UnsupportedOperationException(); }
    @Override public ListIterator<U> listIterator() { throw new UnsupportedOperationException(); }
    @Override public ListIterator<U> listIterator(int i) { throw new UnsupportedOperationException(); }
    @Override public List<U> subList(int i, int i1) { throw new UnsupportedOperationException(); }
}

...
Set<String> set = getSet(...);
ListViewOfSet<String> listOfNames = new ListViewOfSet<>(set);
...

1
这实际上是唯一一个真正解决了问题的答案! - Lii
你可以通过扩展AbstractList来轻松实现这个功能。 - Lii
1
过于复杂了。不要使用一个不完整List实现来防止有序元素的违规,最简单的答案是使用Collection接口,它是ListSet的基础接口。 - cdalxndr

5

我将要做的是:

Map<String, Collection> mainMap = new HashMap<String, Collection>();

for(int i=0; i<something.size(); i++){
  Set set = getSet(...); //return different result each time
  mainMap.put(differentKeyName,set);
}

好的答案,因为它不需要新的内存分配,正如 OP 所请求的那样。 - cdalxndr
1
这并没有解决OP所要求的从SetList的转换问题。不幸的是,目前还不清楚是否只需要Collection或者确实需要List的特性。 - beatngu13

4

Java 8 提供了使用流的选项,您可以从 Set<String> setString 中获取列表,如下所示:

List<String> stringList = setString.stream().collect(Collectors.toList());

尽管当前的内部实现提供了一个 ArrayList 实例:

public static <T>
    Collector<T, ?, List<T>> toList() {
        return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add,
                                   (left, right) -> { left.addAll(right); return left; },
                                   CH_ID);
    }

但 JDK 不能保证它。正如在这里提到的那样:here

返回的 List 的类型、可变性、序列化和线程安全性均无法保证;如果需要更多控制返回的 List,请使用 toCollection(Supplier)。

如果您想始终确保,则可以按以下方式明确请求实例:

List<String> stringArrayList = setString.stream()
                     .collect(Collectors.toCollection(ArrayList::new));

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接