การจัดการข้อยกเว้นด้วยสตรีม


10

ฉันมีMap<String,List<String>>และต้องการที่จะกลายเป็นMap<String,List<Long>>เพราะแต่ละคนStringในรายการแสดงถึงLong:

Map<String,List<String>> input = ...;
Map<String,List<Long>> output= 
input.entrySet()
       .stream()
       .collect(toMap(Entry::getKey, e -> e.getValue().stream()
                                                      .map(Long::valueOf)
                                                      .collect(toList()))
               );

ปัญหาหลักของฉันคือแต่ละคนStringอาจไม่ได้แสดงอย่างถูกต้อง a Long; อาจมีปัญหาบางอย่าง Long::valueOfอาจเพิ่มข้อยกเว้น หากเป็นกรณีนี้ฉันต้องการคืนค่าว่างหรือไม่Map<String,List<Long>>

เพราะฉันต้องการทำซ้ำหลังจากผ่านoutputแผนที่นี้ แต่ฉันไม่สามารถยอมรับการแปลงผิดพลาดได้ ไม่แม้แต่คนเดียว มีความคิดเกี่ยวกับวิธีที่ฉันสามารถคืนเอาต์พุตว่างในกรณีที่ String ไม่ถูกต้อง -> การแปลงยาว?


ฉันเห็นด้วยกับวิธีแก้ปัญหา Naman แต่น่าเสียดายที่ใน catch catch ฉันไม่สามารถดึงคีย์ (Entry :: getKey) ที่ String -> การแปลงยาวไม่ถูกต้อง
AntonBoarf

การสนทนาที่คล้ายกันที่นี่: สตริงไปยังข้อมูลที่ไม่ดีน่าจะต้องหลีกเลี่ยงข้อยกเว้นที่ในที่สุดฉันก็ตัดสินใจตรวจสอบกับ regex ล่วงหน้า (เอกสาร parseLong ใช้กฎการแยกวิเคราะห์แบบเดียวกัน & คุณอาจต้องการส่งคืนLongStreamถ้าคุณวางแผนที่จะลบemptyผลลัพธ์)
AjahnCharles

ขอโทษฉันเข้าใจผิด ฉันคิดว่าคุณตั้งใจจะส่งคืนรายการเดียวเป็นค่าว่าง / null แต่ตอนนี้ฉันคิดว่าคุณหมายถึงแผนที่ทั้งหมด!
AjahnCharles

1
ยังไม่ชัดเจนว่าประเด็นหลักคืออะไร - คุณต้องการส่งคืนแผนที่เปล่าในกรณีที่เกิดข้อผิดพลาด แต่ยังคงพิมพ์ "คีย์" ซึ่งมีข้อผิดพลาดปรากฏขึ้นที่คอนโซลหรือไม่ ฉันหมายถึงข้อมูลเกี่ยวกับบริบทที่ข้อยกเว้นที่ปรากฏมักจะส่งผ่านสแตกการเรียกในข้อยกเว้น ไม่ว่า: คุณถามถึงกระแสข้อมูลโดยเฉพาะ แต่ฉันขอแนะนำอย่างยิ่งให้หลีกเลี่ยงการโทร "รวบรวม" ที่ซ้อนกัน คนที่ต้องรักษาไว้ในภายหลัง (และนี่อาจเป็นอนาคตของคุณ !) จะสงสัยในสิ่งที่คุณ ... มี อย่างน้อยแนะนำวิธีการช่วยเหลือบางอย่างที่มีชื่ออย่างถูกต้อง
Marco13

คำตอบ:


4

วิธีการเกี่ยวกับcatchข้อยกเว้นอย่างชัดเจน:

private Map<String, List<Long>> transformInput(Map<String, List<String>> input) {
    try {
        return input.entrySet()
                .stream()
                .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().stream()
                        .map(Long::valueOf)
                        .collect(Collectors.toList())));
    } catch (NumberFormatException nfe) {
        // log the cause
        return Collections.emptyMap();
    }
}

ตกลงฟังดูดี ... แต่ในการดักจับ (nfe) ฉันต้องการดึงค่าคีย์เฉพาะ (รายการ :: getKey) และสตริงที่ไม่ถูกต้องซึ่งมันล้มเหลวดังนั้นฉันจึงสามารถบันทึกได้อย่างแม่นยำว่าเกิดอะไรขึ้น เป็นไปได้ไหม ?
AntonBoarf

@AntonBoarf หากคุณเพียงต้องการบันทึกกุญแจซึ่งมันล้มเหลวในการแยกสตริงใช้nfe.getMessage()
Naman

1
@AntonBoarf ข้อความของข้อยกเว้นจะมีสตริงอินพุตที่มีรูปแบบไม่ถูกต้อง เพื่อให้ได้รหัสที่รับผิดชอบฉันจะทำการค้นหาอย่างชัดเจนเฉพาะเมื่อมีข้อยกเว้นเกิดขึ้นเช่นinput.entrySet().stream() .filter(e -> e.getValue().stream().anyMatch(s -> !new Scanner(s).hasNextLong())) .map(Map.Entry::getKey) .findAny()
Holger

@ที่ยึด. ขอบคุณ ... ดูเหมือนว่าจะซับซ้อน ... ฉันสงสัยว่าถ้าใช้รูปแบบสำหรับลูป Java5 นั้นไม่ดีกว่าในกรณีของฉัน
AntonBoarf

@AntonBoarf เพียงใช้ทั้งสองอย่างและเปรียบเทียบ ...
โฮล

3

ฉันชอบที่จะให้Optionalข้อมูลเกี่ยวกับการแยกตัวเลข:

public static Optional<Long> parseLong(String input) {
    try {
        return Optional.of(Long.parseLong(input));
    } catch (NumberFormatException ex) {
        return Optional.empty();
    }
}

จากนั้นใช้รหัสของคุณเอง (และไม่สนใจอินพุตที่ไม่ดี):

Map<String,List<String>> input = ...;
Map<String,List<Long>> output= 
input.entrySet()
       .stream()
       .collect(toMap(Entry::getKey, e -> e.getValue().stream()
                                                      .map(MyClass::parseLong)
                                                      .filter(Optional::isPresent)
                                                      .map(Optional::get)
                                                      .collect(toList()))
               );

นอกจากนี้ให้พิจารณาวิธีการของผู้ช่วยเพื่อทำให้รวบรัดยิ่งขึ้น:

public static List<Long> convertList(List<String> input) {
    return input.stream()
        .map(MyClass::parseLong).filter(Optional::isPresent).map(Optional::get)
        .collect(Collectors.toList());
}

public static List<Long> convertEntry(Map.Entry<String, List<String>> entry) {
    return MyClass.convertList(entry.getValue());
}

จากนั้นคุณสามารถกรองผลลัพธ์ในตัวรวบรวมกระแสของคุณ:

Map<String, List<Long>> converted = input.entrySet().stream()
    .collect(Collectors.toMap(Entry::getKey, MyClass::convertEntry));

คุณยังสามารถเก็บOptionalวัตถุว่างไว้ในรายการของคุณได้และจากการเปรียบเทียบดัชนีของพวกเขาในรายการใหม่List<Optional<Long>>(แทนที่จะเป็นList<Long>) กับต้นฉบับList<String>คุณสามารถค้นหาสตริงที่ทำให้เกิดการป้อนข้อมูลที่ผิดพลาดได้ คุณสามารถบันทึกความล้มเหลวเหล่านี้ได้ด้วยMyClass#parseLong

แต่ถ้าความต้องการของคุณคือการไม่ดำเนินการใด ๆการป้อนข้อมูลที่ไม่ดีที่ทั้งหมดแล้วโดยรอบกระแสทั้งในสิ่งที่คุณกำลังพยายามที่จะจับ (ต่อคำตอบของ Naman) เป็นเส้นทางที่ฉันจะใช้เวลา


2

คุณสามารถสร้างStringBuilderคีย์ด้วยข้อยกเว้นและตรวจสอบว่าeleเป็นตัวเลขดังต่อไปนี้

 public static Map<String, List<Long>> transformInput(Map<String, List<String>> input) {
    StringBuilder sb = new StringBuilder();
    try {
    return input.entrySet()
            .stream()
            .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().stream()
                    .map(ele->{
                        if (!StringUtils.isNumeric(ele)) {
                            sb.append(e.getKey()); //add exception key
                            throw new NumberFormatException();
                        }
                        return Long.valueOf(ele);
                    })
                    .collect(Collectors.toList())));
} catch (NumberFormatException nfe) {
    System.out.println("Exception key "+sb);
    return Collections.emptyMap();
}
}

หวังว่ามันจะช่วย


0

อาจเป็นได้ว่าคุณสามารถเขียนวิธีผู้ช่วยที่สามารถตรวจสอบตัวเลขในสตริงและกรองออกจากสตรีมและค่า null จากนั้นก็รวบรวมลงในแผนที่ในที่สุด

// StringUtils.java
public static boolean isNumeric(String string) {
    try {
        Long.parseLong(string);
        return true;
    } catch(NumberFormatException e) {
        return false;
    }
}

นี้จะดูแลทุกอย่าง

และใช้สิ่งนี้ในสตรีมของคุณ

Map<String, List<Long>> newMap = map.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> mapToLongValues(entry.getValue())));

public List<Long> mapToLongValues(List<String> strs) {
    return strs.stream()
        .filter(Objects::nonNull)
        .filter(StringUtils::isNumeric)
        .map(Long::valueOf)
        .collect(Collectors.toList());
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.