对Java8中distinct()的思考

探讨Java8中Stream API的distinct方法原理及应用,包括解决重复数据问题、自定义去重逻辑,以及如何处理不可修改类的去重。

list的转map的另一种猜想

Java8使用lambda表达式进行函数式编程可以对集合进行非常方便的操作。一个比较常见的操作是将list转换成map,一般使用Collectors的toMap()方法进行转换。一个比较常见的问题是当list中含有相同元素的时候,如果不指定取哪一个,则会抛出异常。因此,这个指定是必须的。当然,使用toMap()的另一个重载方法,可以直接指定。这里,我们想讨论的是另一种方法:在进行转map的操作之前,能不能使用distinct()先把list的重复元素过滤掉,然后转map的时候就不用考虑重复元素的问题了。

  • 对lambda不太了解的可以戳这里
  • 对lambda操作集合不太了解的可以戳这里
  • 对list转map的toMap()方法不太了解的可以戳这里
  • 想看list转map的toMap()的具体示例的可以戳这里

使用distinct()给list去重

直接使用distinct(),失败

package example.mystream;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author liuhaibo on 2017/11/16
 */
public class ListToMap {

    @AllArgsConstructor
    @NoArgsConstructor
    @ToString
    private static class VideoInfo {
        @Getter
        String id;
        int width;
        int height;
    }

    public static void main(String [] args) {
        List<VideoInfo> list = Arrays.asList(new VideoInfo("123", 1, 2),
                new VideoInfo("456", 4, 5), new VideoInfo("123", 1, 2));

        // preferred: handle duplicated data when toMap()
        Map<String, VideoInfo> id2VideoInfo = list.stream().collect(
                Collectors.toMap(VideoInfo::getId, x -> x,
                        (oldValue, newValue) -> newValue)
        );

        System.out.println("No Duplicated1: ");
        id2VideoInfo.forEach((x, y) -> System.out.println("<" + x + ", " + y + ">"));

        // handle duplicated data using distinct(), before toMap()
        Map<String, VideoInfo> id2VideoInfo2 = list.stream().distinct().collect(
                Collectors.toMap(VideoInfo::getId, x -> x)
        );

        System.out.println("No Duplicated2: ");
        id2VideoInfo2.forEach((x, y) -> System.out.println("<" + x + ", " + y + ">"));
    }
}

list里总共有三个元素,其中有两个我们认为是重复的。第一种转换是使用toMap()直接指定了对重复key的处理情况,因此可以正常转换成map。而第二种转换是想先对list进行去重,然后再转换成map,结果还是失败了,抛出了IllegalStateException,所以distinct()应该是失败了。

No Duplicated1: 
<123, ListToMap.VideoInfo(id=123, width=1, height=2)>
<456, ListToMap.VideoInfo(id=456, width=4, height=5)>
Exception in thread "main" java.lang.IllegalStateException: Duplicate key ListToMap.VideoInfo(id=123, width=1, height=2)
    at java.util.stream.Collectors.lambda$throwingMerger$0(Collectors.java:133)
    at java.util.HashMap.merge(HashMap.java:1253)
    at java.util.stream.Collectors.lambda$toMap$58(Collectors.java:1320)
    at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
    at java.util.stream.DistinctOps$1$2.accept(DistinctOps.java:175)
    at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
    at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
    at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
    at example.mystream.ListToMap.main(ListToMap.java:79)

原因:distinct()依赖于equals()

查看distinct()的API,可以看到如下介绍:

Returns a stream consisting of the distinct elements (according to {@link Object#equals(Object)}) of this stream.

显然,distinct()对对象进行去重时,是根据对象的equals()方法去处理的。如果我们的VideoInfo类不overrride超类Object的equals()方法,就会使用Object的。但是Object的equals()方法只有在两个对象完全相同时才返回true。而我们想要的效果是只要VideoInfo的id/width/height均相同,就认为两个videoInfo对象是同一个。所以我们比如重写属于videoInfo的equals()方法。

重写equals()的注意事项

我们设计VideoInfo的equals()如下:

        @Override
        public boolean equals(Object obj) {
            if (!(obj instanceof VideoInfo)) {
                return false;
            }
            VideoInfo vi = (VideoInfo) obj;
            return this.id.equals(vi.id)
                    && this.width == vi.width
                    && this.height == vi.height;
        }

这样一来,只要两个videoInfo对象的三个属性都相同,这两个对象就相同了。欢天喜地去运行程序,依旧失败!why?

《Effective Java》是本好书,连Java之父James Gosling都说,这是一本连他都需要的Java教程。在这本书中,作者指出,如果重写了一个类的equals()方法,那么就必须一起重写它的hashCode()方法!必须!没有商量的余地!

必须使得重写后的equals()满足如下条件:
1. 根据equals()进行比较,相等的两个对象,hashCode()的值也必须相同;
2. 根据equals()进行比较,不相等的两个对象,hashCode()的值可以相同,也可以不同;
因为这是Java的规定,违背这些规定将导致Java程序运行不再正常。

具体更多的细节,建议大家读读原书,必定获益匪浅。强烈推荐!当然,如果手边没有此书,可以先看看这篇对原书的相关章节进行了摘录总结的博客

最终,我按照神书的指导设计VideoInfo的hashCode()方法如下:

        @Override
        public int hashCode() {
            int n = 31;
            n = n * 31 + this.id.hashCode();
            n = n * 31 + this.height;
            n = n * 31 + this.width;
            return n;
        }

终于,distinct()成功过滤了list中的重复元素,此时使用两种toMap()将list转换成map都是没问题的:

No Duplicated1: 
<123, ListToMap.VideoInfo(id=123, width=1, height=2)>
<456, ListToMap.VideoInfo(id=456, width=4, height=5)>
No Duplicated2: 
<123, ListToMap.VideoInfo(id=123, width=1, height=2)>
<456, ListToMap.VideoInfo(id=456, width=4, height=5)>

引申

既然说distinct()是调用equals()进行比较的,那按照我的理解,list的3个元素至少需要比较3次吧。那是不是就调用了3次equals()呢?

在equals()中加入一句打印,这样就可以知道了。加后的equals()如下:

        @Override
        public boolean equals(Object obj) {
            if (!(obj instanceof VideoInfo)) {
                return false;
            }
            VideoInfo vi = (VideoInfo) obj;

            System.out.println("<===> Invoke equals() ==> "
                    + this.toString() + " vs. " + vi.toString());

            return this.id.equals(vi.id)
                    && this.width == vi.width
                    && this.height == vi.height;
        }

结果:

No Duplicated1: 
<123, ListToMap.VideoInfo(id=123, width=1, height=2)>
<456, ListToMap.VideoInfo(id=456, width=4, height=5)>
<===> Invoke equals() ==> ListToMap.VideoInfo(id=123, width=1, height=2) vs. ListToMap.VideoInfo(id=123, width=1, height=2)
No Duplicated2: 
<123, ListToMap.VideoInfo(id=123, width=1, height=2)>
<456, ListToMap.VideoInfo(id=456, width=4, height=5)>

结果发现才调用了一次equals()。为什么不是3次呢?仔细想想,根据hashCode()进行比较,hashCode()相同的情况就一次,就是list的第一个元素和第三个元素(都是VideoInfo(id=123, width=1, height=2))会出现hashCode()相同的情况。

所以我们是不是可以这么猜想:只有当hashCode()返回的hashCode相同的时候,才会调用equals()进行更进一步的判断。如果连hashCode()返回的hashCode都不同,那么可以认为这两个对象一定就是不同的了!

验证猜想:

更改hashCode()如下:

        @Override
        public int hashCode() {
            return 1;
        }

这样一来,所有的对象的hashCode()返回值都是相同的。当然,这样搞是符合Java规范的,因为Java只规定equals()相同的对象的hashCode必须相同,但是不同的对象的hashCode未必会不同。

结果:

No Duplicated1: 
<123, ListToMap.VideoInfo(id=123, width=1, height=2)>
<456, ListToMap.VideoInfo(id=456, width=4, height=5)>
<===> Invoke equals() ==> ListToMap.VideoInfo(id=456, width=4, height=5) vs. ListToMap.VideoInfo(id=123, width=1, height=2)
<===> Invoke equals() ==> ListToMap.VideoInfo(id=456, width=4, height=5) vs. ListToMap.VideoInfo(id=123, width=1, height=2)
<===> Invoke equals() ==> ListToMap.VideoInfo(id=123, width=1, height=2) vs. ListToMap.VideoInfo(id=123, width=1, height=2)
No Duplicated2: 
<123, ListToMap.VideoInfo(id=123, width=1, height=2)>
<456, ListToMap.VideoInfo(id=456, width=4, height=5)>

果然,equals()调用了三次!看来的确只有hashCode相同的时候才会调用equal()进一步判断两个对象究竟是否相同;如果hashCode不相同,两个对象显然不相同。猜想是正确的。

结论

  1. list转map推荐使用toMap(),并且无论是否会出现重复的问题,都要指定重复后的取舍规则,不费功夫但受益无穷;
  2. 对一个自定义的class使用distinct(),切记覆写equals()方法;
  3. 覆写equals(),一定要覆写hashCode();
  4. 虽然设计出一个hashCode()可以简单地让其return 1,这样并不会违反Java规定,但是这样做会导致很多恶果。比如将这样的对象存入hashMap的时候,所有的对象的hashCode都相同,最终所有对象都存储在hashMap的同一个桶中,直接将hashMap恶化成了一个链表。从而O(1)的复杂度被整成了O(n)的,性能自然大大下降。
  5. 好书是程序猿进步的阶梯。——高尔基。比如《Effecctive Java》。

最终参考程序:

package example.mystream;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author liuhaibo on 2017/11/16
 */
public class ListToMap {

    @AllArgsConstructor
    @NoArgsConstructor
    @ToString
    private static class VideoInfo {
        @Getter
        String id;
        int width;
        int height;

        public static void main(String [] args) {
            System.out.println(new VideoInfo("123", 1, 2).equals(new VideoInfo("123", 1, 2)));
        }

        @Override
        public boolean equals(Object obj) {
            if (!(obj instanceof VideoInfo)) {
                return false;
            }
            VideoInfo vi = (VideoInfo) obj;
            return this.id.equals(vi.id)
                    && this.width == vi.width
                    && this.height == vi.height;
        }

        /**
         * If equals() is override, hashCode() must be override, too.
         * 1. if a equals b, they must have the same hashCode;
         * 2. if a doesn't equals b, they may have the same hashCode;
         * 3. hashCode written in this way can be affected by sequence of the fields;
         * 3. 2^5 - 1 = 31. So 31 will be faster when do the multiplication,
         *      because it can be replaced by bit-shifting: 31 * i = (i << 5) - i.
         * @return
         */
        @Override
        public int hashCode() {
            int n = 31;
            n = n * 31 + this.id.hashCode();
            n = n * 31 + this.height;
            n = n * 31 + this.width;
            return n;
        }
    }

    public static void main(String [] args) {
        List<VideoInfo> list = Arrays.asList(new VideoInfo("123", 1, 2),
                new VideoInfo("456", 4, 5), new VideoInfo("123", 1, 2));

        // preferred: handle duplicated data when toMap()
        Map<String, VideoInfo> id2VideoInfo = list.stream().collect(
                Collectors.toMap(VideoInfo::getId, x -> x,
                        (oldValue, newValue) -> newValue)
        );

        System.out.println("No Duplicated1: ");
        id2VideoInfo.forEach((x, y) -> System.out.println("<" + x + ", " + y + ">"));

        // handle duplicated data using distinct(), before toMap()
        // Note that distinct() relies on equals() in the object
        // if you override equals(), hashCode() must be override together
        Map<String, VideoInfo> id2VideoInfo2 = list.stream().distinct().collect(
                Collectors.toMap(VideoInfo::getId, x -> x)
        );

        System.out.println("No Duplicated2: ");
        id2VideoInfo2.forEach((x, y) -> System.out.println("<" + x + ", " + y + ">"));
    }
}

再拓展

假设类是别人的,不能修改

以上,VideoInfo使我们自己写的类,我们可以往里添加equals()和hashCode()方法。如果VideoInfo是我们引用的依赖中的一个类,我们无权对其进行修改,那么是不是就没办法使用distinct()按照某些元素是否相同,对对象进行自定义的过滤了呢?

使用wrapper

stackoverflow的一个回答上,我们可以找到一个可行的方法:使用wrapper。

假设在一个依赖中(我们无权修改该类),VideoInfo定义如下:

@AllArgsConstructor
@NoArgsConstructor
@ToString
public class VideoInfo {
    @Getter
    String id;
    int width;
    int height;
}

使用刚刚的wrapper思路,写程序如下(当然,为了程序的可运行性,还是把VideoInfo放进来了,假设它就是不能修改的,不能为其添加任何方法):

package example.mystream;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author liuhaibo on 2017/11/17
 */
public class DistinctByWrapper {

    private static class VideoInfoWrapper {

        private final VideoInfo videoInfo;

        public VideoInfoWrapper(VideoInfo videoInfo) {
            this.videoInfo = videoInfo;
        }

        public VideoInfo unwrap() {
            return videoInfo;
        }

        @Override
        public boolean equals(Object obj) {
            if (!(obj instanceof VideoInfo)) {
                return false;
            }
            VideoInfo vi = (VideoInfo) obj;
            return videoInfo.id.equals(vi.id)
                    && videoInfo.width == vi.width
                    && videoInfo.height == vi.height;
        }

        @Override
        public int hashCode() {
            int n = 31;
            n = n * 31 + videoInfo.id.hashCode();
            n = n * 31 + videoInfo.height;
            n = n * 31 + videoInfo.width;
            return n;
        }
    }

    public static void main(String [] args) {
        List<VideoInfo> list = Arrays.asList(new VideoInfo("123", 1, 2),
                new VideoInfo("456", 4, 5), new VideoInfo("123", 1, 2));

        // VideoInfo --map()--> VideoInfoWrapper ----> distinct(): VideoInfoWrapper --map()--> VideoInfo
        Map<String, VideoInfo> id2VideoInfo = list.stream()
                .map(VideoInfoWrapper::new).distinct().map(VideoInfoWrapper::unwrap)
                .collect(
                Collectors.toMap(VideoInfo::getId, x -> x,
                        (oldValue, newValue) -> newValue)
        );

        id2VideoInfo.forEach((x, y) -> System.out.println("<" + x + ", " + y + ">"));
    }

}

/**
 * Assume that VideoInfo is a class that we can't modify
 */
@AllArgsConstructor
@NoArgsConstructor
@ToString
class VideoInfo {
    @Getter
    String id;
    int width;
    int height;
}

整个wrapper的思路无非就是构造另一个类VideoInfoWrapper,把hashCode()和equals()添加到wrapper中,这样便可以按照自定义规则对wrapper对象进行自定义的过滤。

我们没法自定义过滤VideoInfo,但是我们可以自定义过滤VideoInfoWrapper啊!

之后要做的,就是将VideoInfo全部转化为VideoInfoWrapper,然后过滤掉某些VideoInfoWrapper,再将剩下的VideoInfoWrapper转回VideoInfo,以此达到过滤VideoInfo的目的。很巧妙!

使用“filter() + 自定义函数”取代distinct()

另一种更精妙的实现方式是自定义一个函数

    private static <T> Predicate<T> distinctByKey(Function<? super T, Object> keyExtractor) {
        Map<Object, Boolean> map = new ConcurrentHashMap<>();
        return t -> map.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
    }

(输入元素的类型是T及其父类,keyExtracctor是映射函数,返回Object,整个传入的函数的功能应该是提取key的。distinctByKey函数返回的是Predicate函数,类型为T。)

这个函数传入一个函数(lambda),对传入的对象提取key,然后尝试将key放入concurrentHashMap,如果能放进去,说明此key之前没出现过,函数返回false;如果不能放进去,说明这个key和之前的某个key重复了,函数返回true。

这个函数最终作为filter()函数的入参。根据Java API可知filter(func)过滤的规则为:如果func为true,则过滤,否则不过滤。因此,通过“filter() + 自定义的函数”,凡是重复的key都返回true,并被filter()过滤掉,最终留下的都是不重复的。

最终实现的程序如下

package example.mystream;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * @author liuhaibo on 2017/11/17
 */
public class DistinctByFilterAndLambda {

    public static void main(String[] args) {
        List<VideoInfo> list = Arrays.asList(new VideoInfo("123", 1, 2),
                new VideoInfo("456", 4, 5), new VideoInfo("123", 1, 2));

        // Get distinct only
        Map<String, VideoInfo> id2VideoInfo = list.stream().filter(distinctByKey(vi -> vi.getId())).collect(
                Collectors.toMap(VideoInfo::getId, x -> x,
                        (oldValue, newValue) -> newValue)
        );

        id2VideoInfo.forEach((x, y) -> System.out.println("<" + x + ", " + y + ">"));
    }

    /**
     * If a key could not be put into ConcurrentHashMap, that means the key is duplicated
     * @param keyExtractor a mapping function to produce keys
     * @param <T> the type of the input elements
     * @return true if key is duplicated; else return false
     */
    private static <T> Predicate<T> distinctByKey(Function<? super T, Object> keyExtractor) {
        Map<Object, Boolean> map = new ConcurrentHashMap<>();
        return t -> map.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
    }
}

/**
 * Assume that VideoInfo is a class that we can't modify
 */
@AllArgsConstructor
@NoArgsConstructor
@ToString
class VideoInfo {
    @Getter
    String id;
    int width;
    int height;
}
<think>好的,用户再次提问“java map中的value 装到list中”,看起来他们可能之前已经问过类似的问题,或者需要更详细的解释。我需要先回顾之前的对话历史,确保这次回答不重复,同时补充更多有用的信息。 首先,用户之前的问题已经得到了几种不同的解决方案,包括使用Stream API、ArrayList构造函数、Collections.addAll和for-each循环。现在用户再次提问,可能希望得到更多的方法或者更深入的细节,比如性能比较、不同场景下的选择,或者处理复杂数据类型的情况。 接下来,我需要确认用户的需求是否已经被完全覆盖。之前的回答已经涵盖了基本方法,但可能还可以补充一些高级用法,例如处理并发Map的情况,或者在转换过程中进行过滤、排序等操作。此外,可能用户对某些方法的性能有疑问,需要更详细的比较。 另外,用户可能对某些代码示例有疑问,比如如何处理特定类型的Map,或者是否需要保持顺序。例如,使用LinkedHashMap时,List的顺序是否会保留,或者在多线程环境下如何安全地转换。 还需要考虑是否遗漏了某些方法,例如使用Java 16+的Stream.toList()方法,或者使用其他集合工具库如Guava来实现。同时,可能需要提醒用户注意内存使用或优化,比如预分配ArrayList的大小以提高性能。 最后,确保回答的结构清晰,分点明确,涵盖不同情况,并给出最佳实践建议。同时,相关问题部分需要引导用户进一步思考,比如处理嵌套Map、转换为不可变List、处理大容量数据等。 </think>将 Java 中 `Map` 的 `value` 装入 `List` 是常见操作,以下是多种实现方式及适用场景分析,包含性能优化和特殊场景处理。 --- ### 1. **基础实现方式** #### (1) **Java 8+ 使用 Stream API** ```java Map<String, Integer> map = new HashMap<>(); List<Integer> list = map.values().stream().toList(); // Java 16+ 可用 Stream.toList() ``` - **特点**:代码简洁,适合链式调用,可结合 `filter()`、`map()` 等操作。 - **注意**:`Stream.toList()` 在 Java 16+ 中为终端操作,无需 `Collectors.toList()`。 #### (2) **直接使用 `ArrayList` 构造函数** ```java List<Integer> list = new ArrayList<>(map.values()); ``` - **特点**:性能最优,适用于所有 Java 版本。 - **优化**:若已知 Map 大小,可预分配容量提升性能: ```java List<Integer> list = new ArrayList<>(map.size()); list.addAll(map.values()); ``` --- ### 2. **特殊场景处理** #### (1) **保持插入顺序** 使用 `LinkedHashMap` 时,`List` 会保留插入顺序: ```java Map<String, Integer> map = new LinkedHashMap<>(); map.put("a", 1); map.put("b", 2); List<Integer> list = new ArrayList<>(map.values()); // 顺序为 [1, 2] ``` #### (2) **去重处理** 若 `value` 有重复且需唯一性: ```java List<Integer> uniqueList = map.values().stream() .distinct() .collect(Collectors.toList()); ``` #### (3) **过滤或转换** 转换过程中可添加条件筛选或类型转换: ```java List<String> filteredList = map.entrySet().stream() .filter(e -> e.getValue() > 10) .map(e -> "Key: " + e.getKey() + ", Value: " + e.getValue()) .collect(Collectors.toList()); ``` --- ### 3. **高性能场景优化** #### (1) **避免频繁扩容** 对于大容量 Map,预分配 `ArrayList` 容量可减少扩容开销: ```java int size = map.size(); List<Integer> list = new ArrayList<>(size); list.addAll(map.values()); ``` #### (2) **并发 Map 的线程安全处理** 若使用 `ConcurrentHashMap`,需确保遍历过程中一致性: ```java Map<String, Integer> map = new ConcurrentHashMap<>(); List<Integer> list = new ArrayList<>(); map.values().forEach(list::add); // 或使用 synchronized 块 ``` --- ### 4. **兼容性与历史版本** #### (1) **Java 1.5+ 使用 `Collections.addAll()`** ```java List<Integer> list = new ArrayList<>(); Collections.addAll(list, map.values().toArray(new Integer[0])); ``` - **注意**:需手动指定数组类型,避免 `toArray()` 返回 `Object[]`。 #### (2) **传统 `for-each` 循环** 适用于需额外逻辑处理的场景: ```java List<Integer> list = new ArrayList<>(); for (Integer value : map.values()) { if (value != null) { // 可添加过滤条件 list.add(value); } } ``` --- ### 5. **高级用法** #### (1) **转换为不可变 List(Java 9+)** ```java List<Integer> immutableList = List.copyOf(map.values()); // Java 9+ 支持 ``` - **注意**:若 `value` 包含 `null`,`List.copyOf()` 会抛出异常。 #### (2) **处理嵌套 Map** 例如 `Map<K, Map<K2, V2>>` 提取所有内层 `value`: ```java Map<String, Map<String, Integer>> nestedMap = ...; List<Integer> flatList = nestedMap.values().stream() .flatMap(innerMap -> innerMap.values().stream()) .collect(Collectors.toList()); ``` --- ### 6. **性能对比(基准测试参考)** | 方法 | 时间复杂度 | 内存开销 | 适用场景 | |------|------------|----------|----------| | `new ArrayList<>(map.values())` | O(n) | 低 | 常规转换 | | `Stream API` | O(n) | 中 | 需过滤/转换 | | `Collections.addAll()` | O(n) | 中 | 兼容旧版本 | | `List.copyOf()` | O(n) | 低 | 不可变集合 | --- ### 7. **完整示例** ```java import java.util.*; import java.util.stream.Collectors; public class MapToListExamples { public static void main(String[] args) { Map<String, Integer> map = new HashMap<>(); map.put("a", 10); map.put("b", 20); map.put("c", 30); // 方法1: Stream API (Java 8+) List<Integer> list1 = map.values().stream().collect(Collectors.toList()); // 方法2: ArrayList 构造函数 List<Integer> list2 = new ArrayList<>(map.values()); // 方法3: 预分配容量优化 List<Integer> list3 = new ArrayList<>(map.size()); list3.addAll(map.values()); // 方法4: 过滤后转换 List<Integer> filteredList = map.values().stream() .filter(v -> v > 15) .collect(Collectors.toList()); // 方法5: 不可变 List (Java 9+) List<Integer> immutableList = List.copyOf(map.values()); System.out.println("List1: " + list1); System.out.println("List2: " + list2); System.out.println("List3: " + list3); System.out.println("FilteredList: " + filteredList); System.out.println("ImmutableList: " + immutableList); } } ``` --- ### 总结 - **推荐方式**: - Java 8+ 使用 `new ArrayList<>(map.values())` 或 `Stream API`。 - 需去重或过滤时结合 `distinct()`、`filter()`。 - **性能优化**:预分配容量、避免冗余操作。 - **特殊需求**:保持顺序用 `LinkedHashMap`,并发用 `ConcurrentHashMap`,不可变集合用 `List.copyOf()`。 ---
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值