2017-08-02 20 views
11

Java8 giới thiệu các phương pháp đó đẹp getOrDefault()putIfAbsent(), cho phép viết mã như:Tôi có nên sử dụng put() hoặc putIfAbsent() sau khi sử dụng getOrDefault() không?

Map<Foo, List<Bar>> itemsByFoo = ... 
List<Bar> bars = itemsByFoo.getOrDefault(key, new ArrayList<>()); 
bars.add(someNewBar); 

Bây giờ tôi tự hỏi nếu có lý do chính đáng thực tế để một trong hai làm:

itemsByFoo.put(key, bars); 

hoặc

itemsByFoo.putIfAbsent(key, bars); 

Cả hai đều hoạt động:

  • tùy chọn 1 có thể làm được rất nhiều không cần thiết "đặt" các cuộc gọi khi thêm các yếu tố vào danh sách xảy ra thường
  • option2 có thể làm được rất nhiều "containsKey" không cần thiết gọi khi thêm mục mới cho các phím mới chiếm ưu thế

SO: là những lý do chính đáng để chọn tùy chọn 1 hoặc tùy chọn 2 "luôn"?

+13

Ahem, * không *. Sử dụng 'itemsByFoo.computeIfAbsent (key, x -> new ArrayList <>()) .add (someNewBar);' cho toàn bộ hoạt động. – Holger

+0

@Holger yes :) điểm tuyệt vời. Bởi vì 'putIfAbsent' có thể trả về một' null' vì nó trả về giá trị * trước * ... Ngoài ra 'computeifAbsent' hiện diện trong java-8, không phải 7. Tôi đã đối mặt với điều này trước đây ... – Eugene

+2

@Eugene: 'putIfAbsent' được thêm vào giao diện' Bản đồ' trong Java 8, vì nó bây giờ có thể với các phương thức 'default', nhưng nó phải giữ lại hợp đồng' ConcurrentMap.putIfAbsent', tồn tại từ Java 5, vì vậy nó không thuận tiện như 'computeIfAbsent'… – Holger

Trả lời

19

getOrDefault phù hợp nếu bạn muốn sử dụng giá trị trống cho giá trị vắng mặt mà không sửa đổi bản đồ. Nếu bạn muốn thêm một giá trị mới cho các phím vắng mặt, bạn có thể làm điều đó ngay trong một thao tác.

List<Bar> bars = itemsByFoo.computeIfAbsent(key, x -> new ArrayList<>()); 
bars.add(someNewBar); 

hoặc thậm chí

itemsByFoo.computeIfAbsent(key, x -> new ArrayList<>()).add(someNewBar); 

Trong trường hợp tốt nhất, khi bị ghi đè bởi việc thực hiện Map, giống như với HashMap, điều này sẽ chịu một tra cứu băm duy nhất mà thôi.

Không phải putIfAbsent chỉ mang hai tra cứu khi sử dụng triển khai default, nhưng, tất nhiên, hầu hết triển khai Map sẽ cung cấp một triển khai tra cứu duy nhất cho nó. Tuy nhiên, sự kết hợp của getOrDefaultputIfAbsent vẫn sẽ chịu hai tra cứu trong trường hợp tốt nhất, trong khi chỉ một tối ưu hóa computeIfAbsent.

+0

không phải là 'itemsFoo.compute ...; bars.add (someNewBar) 'một điều kiện chủng tộc bây giờ? Vì nó không phải là một hoạt động nguyên tử duy nhất; ai đó có thể loại bỏ mục đó vào lúc 'add' được thực hiện? Tôi tự hỏi liệu tôi có đạt đến giới hạn kiên nhẫn của bạn ngày hôm nay với rất nhiều câu hỏi sau đây không ... – Eugene

+5

@Eugene: đây là câu hỏi chung về 'Bản đồ'. Là nguyên tử không phải là một yêu cầu. Nếu không, bạn có nhiều việc phải làm hơn. Trong khi bạn có thể làm cho thread chèn an toàn làm mọi thứ bên trong 'compute', nó không có liên quan đến mã mà cuối cùng đọc' List' và phải có mã đọc nó, nếu bộ nhớ không phải là kết thúc trong chính nó, đối với bất kỳ trường hợp thực tế nào, nỗ lực bổ sung cũng sẽ cần thiết. – Holger

+0

cảm ơn bạn. nơi mà trên thế giới tôi thấy 'CHM' ở đây? lỗi của tôi. Đó là một điểm rất hay về 'putIfAbsent' mà có 2 look-up trong việc thực hiện mặc định ... Tôi nhận thấy nó bây giờ là nó có một' get' rồi một 'put'. Điều này sẽ làm cho một ví dụ tuyệt vời về lý do tại sao các phương pháp mặc định được ghi đè lên. – Eugene

5

Điểm quan trọng về computeIfAbsent là phải mất Function sẽ chỉ được thực thi nếu không có Key và chúng tôi cần mặc định Value.

Trong khi đó getOrDefault yêu cầu mặc định Value chính nó, đã được tính toán. Trong trường hợp này, mặc định Value chúng tôi sẽ cần là một new ArrayList<Bar>(), trong đó có tác dụng phụ của phân bổ một đối tượng mới trên heap.

Chúng tôi muốn trì hoãn việc đó cho đến khi chúng tôi chắc chắn rằng key chưa có trong số itemsByFoo. Nếu không, chúng tôi sẽ tạo ra rác không cần thiết cho gc để thu thập.

+0

Thông tin bổ sung tuyệt vời ;-) – GhostCat

Các vấn đề liên quan