Trong Java 8, làm cách nào để chuyển đổi Bản đồ <K, V> sang Bản đồ khác <K, V> bằng lambda?


140

Tôi mới bắt đầu xem Java 8 và để dùng thử lambdas tôi nghĩ tôi đã thử viết lại một điều rất đơn giản mà tôi đã viết gần đây. Tôi cần biến Bản đồ chuỗi thành Cột thành Bản đồ chuỗi khác thành Cột trong đó Cột trong Bản đồ mới là bản sao phòng thủ của Cột trong Bản đồ đầu tiên. Cột có một constructor sao chép. Gần nhất tôi đã có cho đến nay là:

    Map<String, Column> newColumnMap= new HashMap<>();
    originalColumnMap.entrySet().stream().forEach(x -> newColumnMap.put(x.getKey(), new Column(x.getValue())));

nhưng tôi chắc chắn phải có một cách tốt hơn để làm điều đó và tôi rất biết ơn về một số lời khuyên.

Câu trả lời:


222

Bạn có thể sử dụng Collector :

import java.util.*;
import java.util.stream.Collectors;

public class Defensive {

  public static void main(String[] args) {
    Map<String, Column> original = new HashMap<>();
    original.put("foo", new Column());
    original.put("bar", new Column());

    Map<String, Column> copy = original.entrySet()
        .stream()
        .collect(Collectors.toMap(Map.Entry::getKey,
                                  e -> new Column(e.getValue())));

    System.out.println(original);
    System.out.println(copy);
  }

  static class Column {
    public Column() {}
    public Column(Column c) {}
  }
}

6
Tôi nghĩ điều quan trọng (và hơi phản trực giác) cần lưu ý ở trên là việc chuyển đổi diễn ra trong bộ sưu tập, thay vì trong hoạt động luồng bản đồ ()
Brian Agnew

24
Map<String, Integer> map = new HashMap<>();
map.put("test1", 1);
map.put("test2", 2);

Map<String, Integer> map2 = new HashMap<>();
map.forEach(map2::put);

System.out.println("map: " + map);
System.out.println("map2: " + map2);
// Output:
// map:  {test2=2, test1=1}
// map2: {test2=2, test1=1}

Bạn có thể sử dụng forEachphương pháp để làm những gì bạn muốn.

Những gì bạn đang làm là:

map.forEach(new BiConsumer<String, Integer>() {
    @Override
    public void accept(String s, Integer integer) {
        map2.put(s, integer);     
    }
});

Mà chúng ta có thể đơn giản hóa thành lambda:

map.forEach((s, integer) ->  map2.put(s, integer));

Và bởi vì chúng ta chỉ gọi một phương thức hiện có, chúng ta có thể sử dụng một tham chiếu phương thức , cung cấp cho chúng ta:

map.forEach(map2::put);

8
Tôi thích cách bạn giải thích chính xác những gì đang diễn ra đằng sau hậu trường ở đây, nó làm cho nó rõ ràng hơn nhiều. Nhưng tôi nghĩ rằng chỉ cần cho tôi một bản sao thẳng của bản đồ gốc của tôi chứ không phải bản đồ mới nơi các giá trị đã được chuyển thành bản sao phòng thủ. Tôi có hiểu chính xác cho bạn rằng lý do chúng ta có thể sử dụng (map2 :: put) là các đối số tương tự đang đi vào lambda, ví dụ (s, số nguyên) như đang đi vào phương thức put không? Vì vậy, để làm một bản sao phòng thủ, nó cần phải originalColumnMap.forEach((string, column) -> newColumnMap.put(string, new Column(column)));hoặc tôi có thể rút ngắn điều đó?
annesadleir

Vâng, đúng vậy Nếu bạn chỉ truyền vào cùng một đối số, bạn có thể sử dụng một tham chiếu phương thức, tuy nhiên trong trường hợp này, vì bạn có new Column(column)tư cách là một đối tượng, bạn sẽ phải đi với điều đó.
Arrem

Tôi thích câu trả lời này tốt hơn vì nó hoạt động nếu cả hai bản đồ đã được cung cấp cho bạn, chẳng hạn như trong gia hạn phiên.
David Stanley

Không chắc chắn để hiểu điểm của một câu trả lời như vậy. Nó đang viết lại hàm tạo của hầu hết tất cả các triển khai Bản đồ từ một Bản đồ hiện có - như thế này .
Kineolyan

13

Cách mà không cần chèn lại tất cả các mục vào bản đồ mới sẽ là cách nhanh nhất vì nó cũng sẽ HashMap.clonethực hiện việc thử lại.

Map<String, Column> newColumnMap = originalColumnMap.clone();
newColumnMap.replaceAll((s, c) -> new Column(c));

Đó là một cách rất dễ đọc để làm điều đó và khá súc tích. Có vẻ như HashMap.clone () có Map<String,Column> newColumnMap = new HashMap<>(originalColumnMap);. Tôi không biết nếu nó khác nhau dưới vỏ bọc.
annesadleir

2
Cách tiếp cận này có lợi thế là giữ Maphành vi của việc thực hiện, tức là nếu Maplà một EnumMaphoặc a SortedMap, kết quả Mapcũng sẽ như vậy. Trong trường hợp SortedMapđặc biệt, Comparatornó có thể tạo ra một sự khác biệt lớn về ngữ nghĩa. Oh well, cùng áp dụng cho một IdentityHashMap...
Holger

8

Giữ cho nó đơn giản và sử dụng Java 8: -

 Map<String, AccountGroupMappingModel> mapAccountGroup=CustomerDAO.getAccountGroupMapping();
 Map<String, AccountGroupMappingModel> mapH2ToBydAccountGroups = 
              mapAccountGroup.entrySet().stream()
                         .collect(Collectors.toMap(e->e.getValue().getH2AccountGroup(),
                                                   e ->e.getValue())
                                  );

trong trường hợp này: map.forEach (map2 :: put); thật đơn giản :)
Ses

5

Nếu bạn sử dụng Guava (tối thiểu v11) trong dự án của mình, bạn có thể sử dụng Maps :: TransformValues .

Map<String, Column> newColumnMap = Maps.transformValues(
  originalColumnMap,
  Column::new // equivalent to: x -> new Column(x) 
)

Lưu ý: Các giá trị của bản đồ này được đánh giá một cách lười biếng. Nếu việc chuyển đổi tốn kém, bạn có thể sao chép kết quả vào một bản đồ mới như được đề xuất trong tài liệu của Guava.

To avoid lazy evaluation when the returned map doesn't need to be a view, copy the returned map into a new map of your choosing.

Lưu ý: Theo tài liệu, điều này trả về chế độ xem được đánh giá lười biếng trên bản gốcColumnMap, do đó, hàm Cột :: mới được đánh giá lại mỗi khi mục nhập được truy cập (có thể không mong muốn khi chức năng ánh xạ đắt tiền)
Erric

Chính xác. Nếu việc chuyển đổi tốn kém, có lẽ bạn vẫn ổn với chi phí sao chép nó sang một bản đồ mới như được đề xuất trong các tài liệu. To avoid lazy evaluation when the returned map doesn't need to be a view, copy the returned map into a new map of your choosing.
Andrea Bergonzo

Đúng, nhưng nó có thể đáng để thêm vào như một chú thích trong câu trả lời cho những người có xu hướng bỏ qua việc đọc các tài liệu.
Erric

@Erric Làm cho ý nghĩa, chỉ cần thêm.
Andrea Bergonzo

3

Đây là một cách khác cho phép bạn truy cập vào khóa và giá trị cùng một lúc, trong trường hợp bạn phải thực hiện một số loại chuyển đổi.

Map<String, Integer> pointsByName = new HashMap<>();
Map<String, Integer> maxPointsByName = new HashMap<>();

Map<String, Double> gradesByName = pointsByName.entrySet().stream()
        .map(entry -> new AbstractMap.SimpleImmutableEntry<>(
                entry.getKey(), ((double) entry.getValue() /
                        maxPointsByName.get(entry.getKey())) * 100d))
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

1

Nếu bạn không phiền khi sử dụng các thư viện của bên thứ 3, lib phản ứng cyclops của tôi có các phần mở rộng cho tất cả các loại Bộ sưu tập JDK , bao gồm cả Bản đồ . Bạn có thể trực tiếp sử dụng các phương pháp bản đồ hoặc bimap để chuyển đổi Bản đồ của mình. Một MapX có thể được xây dựng từ một Bản đồ hiện có, vd.

  MapX<String, Column> y = MapX.fromMap(orgColumnMap)
                               .map(c->new Column(c.getValue());

Nếu bạn cũng muốn thay đổi chìa khóa, bạn có thể viết

  MapX<String, Column> y = MapX.fromMap(orgColumnMap)
                               .bimap(this::newKey,c->new Column(c.getValue());

bimap có thể được sử dụng để biến đổi các khóa và giá trị cùng một lúc.

Khi MapX mở rộng Bản đồ, bản đồ được tạo cũng có thể được định nghĩa là

  Map<String, Column> y
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.