2015-06-02 33 views
13

Giả sử tôi có một bản đồ cho tên, họ và cặp tôi muốn tìm tên đã cho của mục nhập đầu tiên trong bản đồ có họ khớp với một giá trị nhất định. Làm thế nào chúng ta sẽ làm điều này trong một thời trang java 8.Java 8 trích xuất khóa đầu tiên từ khớp với giá trị trong một Bản đồ

Trong ví dụ trường hợp thử nghiệm của tôi bên dưới, tôi đặt hai cách để thực hiện.

Tuy nhiên, tên đầu tiên (tìm tên đã cho của người đầu tiên có họ "Donkey") sẽ ném java.util.NoSuchElementException: Không có giá trị hiện tại để không an toàn.

Tác phẩm thứ hai hoạt động nhưng không chỉ khó đọc hơn mà còn hơi khó thực hiện chức năng.

Chỉ cần tự hỏi liệu có ai đó ở đây sẽ gợi ý cho tôi cách dễ dàng hơn để đạt được điều này bằng cách sử dụng stream() hoặc forEach() hoặc cả hai.

@Test 
public void shouldBeAbleToReturnTheKeyOfTheFirstMatchingValue() throws Exception { 
    Map<String, String> names = new LinkedHashMap<>(); 
    names.put("John", "Doe"); 
    names.put("Fred", "Flintstone"); 
    names.put("Jane", "Doe"); 
    String keyOfTheFirst = names.entrySet().stream().filter(e -> e.getValue().equals("Doe")).findFirst().get().getKey(); 
    assertEquals("John", keyOfTheFirst); 

    try { 
     names.entrySet().stream().filter(e -> e.getValue().equals("Donkey")).findFirst().get(); 
    } catch (NoSuchElementException e){ 
     // Expected 
    } 

    Optional<Map.Entry<String, String>> optionalEntry = names.entrySet().stream().filter(e -> e.getValue().equals("Donkey")).findFirst(); 
    keyOfTheFirst = optionalEntry.isPresent() ? optionalEntry.get().getKey() : null; 

    assertNull(keyOfTheFirst); 
} 

Cảm ơn bạn trước.

+1

Rõ ràng sử dụng bản đồ hai chiều sẽ hiệu quả hơn. –

+0

Mã này [mùi] (http://en.wikipedia.org/wiki/Code_smell). Thiết kế kém. –

+0

Xin chào patryk. Tôi hoàn toàn đồng ý với bạn. Đây là lý do tại sao tôi đăng câu hỏi. Bạn có thể cung cấp giải pháp không? – Julian

Trả lời

41

Để trở về giá trị mặc định nếu không có trận đấu, sử dụng Optional#orElse

names.entrySet().stream() 
    .filter(e -> e.getValue().equals("Donkey")) 
    .map(Map.Entry::getKey) 
    .findFirst() 
    .orElse(null); 
+1

Cảm ơn Misha và Doon. Cả hai câu trả lời của bạn đã giúp tôi tìm ra giải pháp mà tôi muốn. Tôi đang chọn Misha như là người gần gũi nhất với nó. Để trả về 'java.lang.String' nhưng không phải là' java.util.Optional' tất cả những gì tôi phải làm là gọi số 'orElseGet (Nhà cung cấp mới () { @Override chuỗi công khai get() { trở lại null; } }) ' – Julian

+3

Nếu bạn thực sự muốn sử dụng' orElseGet' thay vì 'orElse', bạn có thể sử dụng một biểu thức lambda thay vì lớp nặc danh:' .orElseGet (() -> null) '. – Misha

+0

Cảm ơn Misha. Điều này thậm chí còn tốt hơn và chính xác là những gì tôi đã làm sau: một giải pháp chức năng tinh khiết cho vấn đề này. ** Java 8 rocks! ** – Julian

0

Từ một tương tự question:

public static <T, E> Set<T> getKeysByValue(Map<T, E> map, E value) { 
    return map.entrySet() 
       .stream() 
       .filter(entry -> Objects.equals(entry.getValue(), value)) 
       .map(Map.Entry::getKey) 
       .collect(Collectors.toSet()); 
} 

Sau đó, bạn có thể chọn đầu tiên, nếu bạn muốn. Hãy nhớ rằng key là duy nhất, value thì không.

Edit: Toàn bộ mã (nhờ @ Peter Lawrey)

package test; 

import java.util.LinkedHashMap; 
import java.util.Map; 
import java.util.Objects; 
import java.util.Optional; 

public class Main { 

    public static void main(String[] args) { 
     Map<String, String> names = new LinkedHashMap<>(); 
     names.put("John", "Doe"); 
     names.put("Fred", "Flintstone"); 
     names.put("Jane", "Doe"); 

     Optional<String> firstKey = names.entrySet().stream() 
       .filter(entry -> Objects.equals(entry.getValue(), "Doe")) 
       .map(Map.Entry::getKey).findFirst(); 

     if (firstKey.isPresent()) { 
      System.out.println(firstKey.get()); 
     } 
    } 
} 
+2

Thay vì 'thu thập', bạn có thể sử dụng' findAny' để tìm kiếm đầu tiên. –

+0

@PeterLawrey Vâng nhắc nhở! Nhưng nó sẽ là 'findFirst', phải không? – Doon

+1

Nó sẽ là đầu tiên, trừ khi bạn sử dụng parallelStream. –

0

Tôi thích phong cách cũ:

static <K, V> K findFirstKeyByValue(Map<K, V> map, String value) { 
    for (Entry<K, V> e : map.entrySet()) 
     if (e.getValue().equals(value)) 
      return e.getKey(); 
    return null; 
} 
1

Giải pháp được cung cấp bởi @Misha là giải pháp tốt nhất nếu bạn làm không muốn sử dụng mã của bên thứ ba. My library có phương pháp phím tắt đặc biệt ofKeys đối với trường hợp như tôi phát hiện ra rằng đó là nhiệm vụ khá phổ biến:

StreamEx.ofKeys(names, "Donkey"::equals).findFirst().orElse(null); 
+0

Sẽ không phải bộ lọc đó cho khóa có tên là lừa, thay vì khóa tương ứng với một giá trị có tên là lừa không? – flup

+0

@flup, no, đó là bộ lọc cho khóa tương ứng với một giá trị có tên '" Donkey "'. Để lọc một khóa, bạn có thể sử dụng phương pháp lọc thông thường như 'StreamEx.ofKeys (tên) .filter (" Donkey ":: equals)'. –

+0

aha! Tính năng tuyệt vời! – flup

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