2015-01-06 48 views
19

Giả sử có một enum đơn giản gọi là Kiểu định nghĩa như thế này:Finding với Java 8 Suối API

enum Type{ 
    X("S1"), 
    Y("S2"); 

    private String s; 

    private Type(String s) { 
     this.s = s; 
    } 
} 

Tìm enum chính xác cho trao s được trivially thực hiện với phương pháp tĩnh với cho vòng lặp (giả định phương pháp được định nghĩa bên trong enum), ví dụ:

private static Type find(String val) { 
     for (Type e : Type.values()) { 
      if (e.s.equals(val)) 
       return e; 
     } 
     throw new IllegalStateException(String.format("Unsupported type %s.", val)); 
} 

tôi nghĩ rằng tương đương với chức năng này bày tỏ với Suối API sẽ là một cái gì đó như thế này:

private static Type find(String val) { 
    return Arrays.stream(Type.values()) 
      .filter(e -> e.s.equals(val)) 
      .reduce((t1, t2) -> t1) 
      .orElseThrow(() -> {throw new IllegalStateException(String.format("Unsupported type %s.", val));}); 
} 

Làm thế nào chúng ta có thể viết điều này tốt hơn và đơn giản hơn? Mã này cảm thấy bị ép buộc và không rõ ràng lắm. Các reduce() đặc biệt có vẻ clunky và lạm dụng vì nó không tích lũy bất cứ điều gì, thực hiện không có tính toán và luôn luôn đơn giản trả về t1 (miễn là bộ lọc trả về một giá trị - nếu nó không rõ ràng là một thảm họa), chưa kể t2 là có thừa và gây nhầm lẫn. Tuy nhiên, tôi không thể tìm thấy bất kỳ thứ gì trong API luồng mà chỉ đơn giản bằng cách nào đó trả về trực tiếp một số T từ số Stream<T>.

Có cách nào tốt hơn không?

+3

Tôi biết rằng nhận xét này sẽ không được bỏ phiếu tán bởi bất cứ ai, nhưng là tuyệt vời như Java 8 là bạn không cần phải sử dụng 'Stream's cho mọi vấn đề đơn . Cách tiếp cận vòng lặp của bạn là rõ ràng hơn (và nhanh hơn) so với bất kỳ cách tiếp cận nào sử dụng 'Stream'. –

+3

@pbabcdefp Vâng, tôi nghĩ đó là một lời bình luận tốt, nhưng nếu tôi upvoted nó thì cụm từ đầu tiên trong bình luận của bạn sẽ sai, có nghĩa là tôi phải downvote nó một lần nữa, sau đó tôi nghĩ rằng đó là một bình luận tốt một lần nữa, vì vậy tôi phải upvote nó, nhưng sau đó cụm từ đầu tiên sẽ sai ...Tôi nghĩ rằng tôi sắp ném 'StackOverflowException' ... – ajb

+0

@pbabcdefp - đó có lẽ là vấn đề về ý kiến ​​nhưng tôi thấy lambdas ngày càng thích hợp hơn cho lặp lại và rõ ràng hơn là hiệu quả hầu như luôn luôn. Tôi đã tích cực tôi đã thử 'findFirst()' và đã nhận được một số lỗi biên dịch lạ trong IDEA và đã viết biến thể 'reduce()'. Ở mức nào, tôi đã upvoted tất cả các câu trả lời của bạn nhưng cảm thấy 'first' là rõ ràng hơn so với 'bất kỳ' vì vậy tôi đã đi với nó. Cảm ơn bạn đã giúp đỡ! – quantum

Trả lời

46

Tôi sẽ sử dụng findFirst thay vì:

return Arrays.stream(Type.values()) 
      .filter(e -> e.s.equals(val)) 
      .findFirst() 
      .orElseThrow(() -> new IllegalStateException(String.format("Unsupported type %s.", val))); 


Mặc dù một Map có thể tốt hơn trong trường hợp này:

enum Type{ 
    X("S1"), 
    Y("S2"); 

    private static class Holder { 
     static Map<String, Type> MAP = new HashMap<>(); 
    } 

    private Type(String s) { 
     Holder.MAP.put(s, this); 
    } 

    public static Type find(String val) { 
     Type t = Holder.MAP.get(val); 
     if(t == null) { 
      throw new IllegalStateException(String.format("Unsupported type %s.", val)); 
     } 
     return t; 
    } 
} 

Tôi đã học được thủ thuật này từ answer này. Về cơ bản, trình nạp lớp khởi tạo các lớp tĩnh trước lớp enum, cho phép bạn điền vào số Map trong chính hàm tạo enum. Rất tiện dụng !

Hy vọng điều đó sẽ hữu ích! :)

+3

Đó là một thủ thuật rất tiện lợi, tôi đặc biệt thích cách JVM đảm bảo dân số bản đồ nối tiếp - tuyệt vời. Chỉ là một gợi ý nhỏ - chúng ta có thể làm cho mã trở nên nhỏ gọn hơn bằng cách loại bỏ trường 's' vì nó không được sử dụng ở nơi khác. – quantum

+0

'findAny()' thay vì 'findFirst()'? - nếu bạn được đảm bảo một (hoặc 0), thì có khả năng 'findAny()' sẽ nhận được câu trả lời sớm hơn (mặc dù tôi đoán nó có vấn đề liệu một enum sẽ đủ lớn để tìm kiếm song song) – slim

+0

@slim Khi luồng được sắp xếp, 'findFirst()' thể hiện hành vi tương tự như mã pre-java 8 gốc trong trường hợp có nhiều kết quả khớp. Mặc dù tôi nghi ngờ một ánh xạ sinh học giữa các giá trị và tên của enum, tôi không mong đợi nhiều sự khác biệt về hiệu suất giữa 'findFirst()' và 'findAny()' (và như bạn đã nói, nó có vấn đề). Điều đó nói rằng, tôi sẽ sử dụng phương pháp thứ hai. Nó sử dụng nhiều bộ nhớ hơn nhưng nó có giá trị khi thời gian tra cứu tốt hơn :) –

3
Arrays.stream(Type.values()).filter(v -> v.s.equals(val)).findAny().orElseThrow(...); 
4

Cách sử dụng findAny() thay vì reduce?

private static Type find(String val) { 
    return Arrays.stream(Type.values()) 
     .filter(e -> e.s.equals(val)) 
     .findAny() 
     .orElseThrow(() -> new IllegalStateException(String.format("Unsupported type %s.", val))); 
} 
+1

'orElseThrow' mong đợi một' Nhà cung cấp', như tên cho thấy, * cung cấp * ngoại lệ, không * ném * ngoại lệ, vì vậy thay vì ' .orElseThrow (() -> {throw new IllegalStateException ...}) 'bạn nên sử dụng' .orElseThrow (() -> new IllegalStateException ...) '. Bạn sẽ nhận thấy sự khác biệt khi sử dụng một ngoại lệ đã kiểm tra. – Holger

+0

@Holger - Bắt tốt, cố định. Cảm ơn! – Todd

0

tôi không thể thêm một bình luận nào, vì vậy tôi đăng câu trả lời để bổ sung trên answer, chỉ sau ý tưởng tương tự nhưng sử dụng java 8 cách tiếp cận:

public static Type find(String val) { 
    return Optional 
      .ofNullable(Holder.MAP.get(val)) 
      .orElseThrow(() -> new IllegalStateException(String.format("Unsupported type %s.", val))); 
} 
12

Các công trình trả lời chấp nhận tốt, nhưng nếu bạn muốn tránh tạo luồng mới bằng một mảng tạm thời, bạn có thể sử dụng EnumSet.allOf().

EnumSet.allOf(Type.class) 
     .stream() 
     .filter(e -> e.s.equals(val)) 
     .findFirst() 
     .orElseThrow(String.format("Unsupported type %s.", val)); 
+4

Nhìn vào nguồn JDK, 'Arrays.stream (Type.values ​​())' nội dòng nhân bản một mảng và sau đó tạo ra một luồng mới - trong khi 'EnumSet.allOf (Type.class) .stream()' nội bộ tạo ra một EnumSet mới , thêm tất cả các giá trị enum vào nó và sau đó tạo một luồng mới. Giải pháp này trông đẹp hơn với tôi nhưng quyết định sử dụng điều này không chỉ dựa trên các giả định về số lượng đối tượng được tạo ra. – Kapep

0

Bạn cần getter cho chuỗi s. Trong ví dụ dưới đây phương pháp này là getDesc():

public static StatusManifestoType getFromValue(String value) { 
    return Arrays.asList(values()).stream().filter(t -> t.getDesc().equals(value)).findAny().orElse(null); 
} 
Các vấn đề liên quan