Làm cách nào để sử dụng hàm computeIfAbsent mới?


115

Tôi rất muốn sử dụng Map.computeIfAbsent nhưng đã quá lâu kể từ khi lambdas ở đại học.

Gần như trực tiếp từ tài liệu: nó đưa ra một ví dụ về cách cũ để thực hiện mọi việc:

Map<String, Boolean> whoLetDogsOut = new ConcurrentHashMap<>();
String key = "snoop";
if (whoLetDogsOut.get(key) == null) {
  Boolean isLetOut = tryToLetOut(key);
  if (isLetOut != null)
    map.putIfAbsent(key, isLetOut);
}

Và cách mới:

map.computeIfAbsent(key, k -> new Value(f(k)));

Nhưng trong ví dụ của họ, tôi nghĩ rằng tôi không hoàn toàn "hiểu được nó." Làm cách nào để tôi chuyển đổi mã để sử dụng cách lambda mới để diễn đạt điều này?


Tôi không chắc bạn không hiểu những gì từ ví dụ ở đó?
Louis Wasserman

2
"K" là gì? Nó có phải là một biến đang được định nghĩa không? Còn về "Giá trị mới" - đó là thứ từ java 8 hay đại diện cho một đối tượng mà tôi cần xác định hoặc ghi đè? whoLetDogsOut.computeIfAbsent (key, k -> new Boolean (tryToLetOut (k))) không biên dịch, vì vậy tôi thiếu cái gì ...
Benjamin H

Những gì chính xác không biên dịch? Nó tạo ra lỗi gì?
axtavt

Temp.java:26: error: bắt đầu bất hợp pháp của biểu thức whoLetDogsOut.computeIfAbsent (key, k -> new Boolean (tryToLetOut (k))); (trỏ vào dấu ">")
Benjamin H

Biên dịch tốt cho tôi. Đảm bảo rằng bạn thực sự sử dụng trình biên dịch Java 8. Các tính năng khác của Java 8 có hoạt động không?
axtavt

Câu trả lời:


97

Giả sử bạn có mã sau:

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class Test {
    public static void main(String[] s) {
        Map<String, Boolean> whoLetDogsOut = new ConcurrentHashMap<>();
        whoLetDogsOut.computeIfAbsent("snoop", k -> f(k));
        whoLetDogsOut.computeIfAbsent("snoop", k -> f(k));
    }
    static boolean f(String s) {
        System.out.println("creating a value for \""+s+'"');
        return s.isEmpty();
    }
}

Sau đó, bạn sẽ thấy thông báo creating a value for "snoop"chính xác một lần như trong lần gọi thứ hai computeIfAbsentlà đã có một giá trị cho khóa đó. Trong kbiểu thức lambda k -> f(k)chỉ là một bộ định vị (tham số) cho khóa mà bản đồ sẽ chuyển đến lambda của bạn để tính toán giá trị. Vì vậy, trong ví dụ, khóa được chuyển cho lệnh gọi hàm.

Ngoài ra, bạn có thể viết: whoLetDogsOut.computeIfAbsent("snoop", k -> k.isEmpty());để đạt được kết quả tương tự mà không cần phương thức trợ giúp (nhưng bạn sẽ không thấy đầu ra gỡ lỗi sau đó). Và đơn giản hơn nữa, vì nó là một ủy quyền đơn giản cho một phương thức hiện có mà bạn có thể viết: whoLetDogsOut.computeIfAbsent("snoop", String::isEmpty);Sự ủy quyền này không cần bất kỳ tham số nào được viết.

Để gần với ví dụ trong câu hỏi của bạn hơn, bạn có thể viết nó là whoLetDogsOut.computeIfAbsent("snoop", key -> tryToLetOut(key));(không quan trọng bạn đặt tên cho tham số khay key). Hoặc viết nó như whoLetDogsOut.computeIfAbsent("snoop", MyClass::tryToLetOut);thể tryToLetOutstatichoặc whoLetDogsOut.computeIfAbsent("snoop", this::tryToLetOut);nếu tryToLetOutlà một phương thức thể hiện.


114

Gần đây tôi cũng đang chơi với phương pháp này. Tôi đã viết một thuật toán được ghi nhớ cho các số Fibonacci calcualte có thể dùng như một minh họa khác về cách sử dụng phương pháp.

Chúng ta có thể bắt đầu bằng cách xác định một bản đồ và đặt các giá trị vào đó cho các trường hợp cơ sở, cụ thể là fibonnaci(0)fibonacci(1):

private static Map<Integer,Long> memo = new HashMap<>();
static {
   memo.put(0,0L); //fibonacci(0)
   memo.put(1,1L); //fibonacci(1)
}

Và đối với bước quy nạp, tất cả những gì chúng ta phải làm là xác định lại hàm Fibonacci của chúng ta như sau:

public static long fibonacci(int x) {
   return memo.computeIfAbsent(x, n -> fibonacci(n-2) + fibonacci(n-1));
}

Như bạn có thể thấy, phương pháp computeIfAbsentnày sẽ sử dụng biểu thức lambda được cung cấp để tính số Fibonacci khi số không có trong bản đồ. Điều này thể hiện một sự cải tiến đáng kể so với thuật toán đệ quy cây truyền thống.


18
Tốt, chuyển đổi một dòng sang lập trình động. Rất bóng bẩy.
Benjamin H

3
Bạn có thể nhận được ít cuộc gọi đệ quy hơn nếu bạn có cuộc gọi (n-2) trước?
Thorbjørn Ravn Andersen

9
Bạn nên thận trọng hơn khi sử dụng đệ quy computeIfAbsent. Để biết thêm chi tiết, vui lòng kiểm tra stackoverflow.com/questions/28840047/…
Ajit Kumar

11
Mã này dẫn HashMapđến nội bộ của bị hỏng, giống như trong bug.openjdk.java.net/browse/JDK-8172951 và sẽ bị lỗi ConcurrentModificationExceptiontrong Java 9 ( bug.openjdk.java.net/browse/JDK-8071667 )
Piotr Findeisen

22
Các tài liệu nói theo nghĩa đen rằng chức năng ánh xạ không được sửa đổi bản đồ này trong quá trình tính toán , vì vậy câu trả lời này rõ ràng là sai.
khung hình / giây

41

Một vi dụ khac. Khi xây dựng một bản đồ phức tạp, phương thức computeIfAbsent () thay thế cho phương thức get () của bản đồ. Thông qua việc xâu chuỗi các lệnh gọi computeIfAbsent () lại với nhau, các vùng chứa bị thiếu được xây dựng nhanh chóng bằng các biểu thức lambda được cung cấp:

  // Stores regional movie ratings
  Map<String, Map<Integer, Set<String>>> regionalMovieRatings = new TreeMap<>();

  // This will throw NullPointerException!
  regionalMovieRatings.get("New York").get(5).add("Boyhood");

  // This will work
  regionalMovieRatings
    .computeIfAbsent("New York", region -> new TreeMap<>())
    .computeIfAbsent(5, rating -> new TreeSet<>())
    .add("Boyhood");

31

nhiều bản đồ

Điều này thực sự hữu ích nếu bạn muốn tạo một multimap mà không cần dùng đến thư viện Google Guava để thực hiện MultiMap.

Ví dụ: giả sử bạn muốn lưu trữ danh sách các sinh viên đã đăng ký một môn học cụ thể.

Giải pháp thông thường cho việc này bằng cách sử dụng thư viện JDK là:

Map<String,List<String>> studentListSubjectWise = new TreeMap<>();
List<String>lis = studentListSubjectWise.get("a");
if(lis == null) {
    lis = new ArrayList<>();
}
lis.add("John");

//continue....

Vì nó có một số mã soạn sẵn, mọi người có xu hướng sử dụng Ổi Mutltimap.

Sử dụng Map.computeIfAbsent, chúng ta có thể viết trong một dòng đơn lẻ không ổi Multimap như sau.

studentListSubjectWise.computeIfAbsent("a", (x -> new ArrayList<>())).add("John");

Stuart Marks và Brian Goetz đã nói rất hay về điều này https://www.youtube.com/watch?v=9uTVXxJjuco


Một cách khác để tạo studentListSubjectWise.stream().collect(Collectors.GroupingBy(subj::getSubjName, Collectors.toList());đa bản đồ trong Java 8 (và ngắn gọn hơn) là chỉ làm Điều này tạo ra một kiểu đa bản đồ Map<T,List<T>trong JDK chỉ ngắn gọn hơn.
Zombies
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.