2015-08-03 36 views
7

Nếu tôi muốn tổng một danh sách các số dư tài khoản hiện tại, tôi có thể làm:Java lambda để trả về null nếu danh sách trống nếu không tổng các giá trị?

accountOverview.setCurrentBalance(account.stream(). 
       filter(a -> a.getCurrentBalance() != null). 
       mapToLong(a -> a.getCurrentBalance()). 
       sum()); 

Nhưng biểu hiện này sẽ trở về 0, ngay cả khi tất cả các số dư là null. Tôi muốn nó trả về null nếu tất cả số dư là null, 0 nếu có 0 số dư không bằng 0 và tổng số dư khác.

Tôi có thể làm điều này bằng biểu thức lambda như thế nào?

Rất cám ơn

+2

Tốt nhất là chỉ cần chia nhỏ thành 2 dòng, một để lọc ra 'null' và một để trả về' null' nếu kích thước là '0', tổng khác. Có thể thực hiện nó trong một dòng bằng cách sử dụng 'reduce' thay vì' filter' nhưng khả năng đọc sẽ bị ảnh hưởng. –

+4

Thực ra đó là thực tế tồi để trả lại giá trị rỗng (xem mẫu NullObject) ... – Maksym

+0

@pbabcdefp - ah, cảm ơn. Đúng, tôi đã làm lại nó thành hai dòng như bạn đề nghị, và nó chắc chắn là dễ đọc hơn. – user384842

Trả lời

3

Hiện tại, tôi sẽ thực hiện việc này. Suy nghĩ?

 accountOverview.setCurrentBalance(account.stream(). 
       filter(a -> a.getCurrentBalance() != null). 
       map(a -> a.getCurrentBalance()). 
       reduce(null, (i,j) -> { if (i == null) { return j; } else { return i+j; } })); 

Vì tôi đã lọc rỗng, tôi được đảm bảo không được nhấn bất kỳ. Bằng cách làm cho tham số ban đầu để giảm 'null', tôi có thể đảm bảo rằng tôi nhận được null trở lại trên một danh sách trống.

Cảm thấy hơi khó hiểu/khó hiểu khi đọc. Muốn có một giải pháp đẹp hơn ..

EDIT Nhờ pbabcdefp, tôi đã đi với giải pháp khá đáng kính hơn này:

 List<Account> filtered = account.stream(). 
       filter(a -> a.getCurrentBalance() != null). 
       collect(Collectors.toList()); 

     accountOverview.setCurrentBalance(filtered.size() == 0?null: 
      filtered.stream().mapToLong(a -> a.getCurrentBalance()). 
      sum()); 
2

Bạn đang cố gắng thực hiện hai cách cơ bản mâu thuẫn với điều: lọc ra các yếu tố null (đó là một hoạt động cục bộ, dựa trên một phần tử duy nhất) và phát hiện khi tất cả các phần tử là null (là một hoạt động toàn cục, dựa trên toàn bộ danh sách). Thông thường, bạn nên làm như hai hoạt động riêng biệt, mà làm cho mọi thứ dễ đọc hơn nhiều.

Ngoài các reduce() lừa bạn đã tìm thấy, bạn cũng có thể dùng thủ đoạn ám muội, nếu bạn biết cân bằng mà không bao giờ có thể là tiêu cực ví dụ, bạn có thể làm một cái gì đó giống như

long sum = account.stream(). 
       mapToLong(a -> a.getCurrentBalance() == null ? 0 : a.getCurrentBalance()+1). 
       sum() - account.size(); 
Long nullableSum = sum < 0 ? null : sum; 

Nhưng bạn' đã phải tự hỏi mình: là những gì bạn đạt được bằng cách chỉ lặp qua bộ sưu tập của bạn một lần đáng giá của việc viết một đoạn mã không đọc được và khá dễ vỡ? Trong hầu hết các trường hợp, câu trả lời sẽ là: không.

5

Khi bạn lọc chúng khỏi luồng, không có cách nào để biết liệu tất cả số dư có là null hay không.

Làm hai đi ngang qua các dữ liệu có lẽ là giải pháp thẳng về phía trước, và tôi có lẽ sẽ đi với điều đó đầu tiên:

boolean allNulls = account.stream().map(Account::getBalance).allMatch(Objects::isNull); 

Long sum = allNulls ? null : account.stream().map(Account::getBalance).filter(Objects::nonNull).mapToLong(l -> l).sum(); 

Bạn có thể thoát khỏi bước lọc với giải pháp của bạn với reduce, mặc dù khả năng đọc có thể không phải là tốt nhất:

Long sum = account.stream() 
        .reduce(null, (l1, l2) -> l1 == null ? l2 : 
                 l2 == null ? l1 : Long.valueOf(l1 + l2)); 

Thông báo cuộc gọi Long.valueOf. Đó là để tránh rằng các loại biểu thức có điều kiện là long, và do đó một NPE trên một số trường hợp cạnh.


Một giải pháp khác là sử dụng API Optional.Đầu tiên, tạo một Stream<Optional<Long>> từ các giá trị của các số dư và giảm họ:

Optional<Long> opt = account.stream() 
          .map(Account::getBalance) 
          .flatMap(l -> Stream.of(Optional.ofNullable(l))) 
          .reduce(Optional.empty(), 
            (o1, o2) -> o1.isPresent() ? o1.map(l -> l + o2.orElse(0L)) : o2); 

này sẽ cung cấp cho bạn một Optional<Long> rằng sẽ trống nếu tất cả các giá trị là null, nếu không nó sẽ cung cấp cho bạn tổng các phi giá trị null.

Hoặc bạn có thể muốn tạo ra một nhà sưu tập tùy chỉnh cho việc này:

class SumIntoOptional { 

    private boolean allNull = true; 
    private long sum = 0L; 

    public SumIntoOptional() {} 

    public void add(Long value) { 
     if(value != null) { 
      allNull = false; 
      sum += value; 
     } 
    } 

    public void merge(SumIntoOptional other) { 
     if(!other.allNull) { 
      allNull = false; 
      sum += other.sum; 
     } 
    } 

    public OptionalLong getSum() { 
     return allNull ? OptionalLong.empty() : OptionalLong.of(sum); 
    } 
} 

và sau đó:

OptionalLong opt = account.stream().map(Account::getBalance).collect(SumIntoOptional::new, SumIntoOptional::add, SumIntoOptional::merge).getSum(); 


Như bạn có thể thấy, có rất nhiều cách để đạt được điều này, vì vậy tôi lời khuyên sẽ là: chọn đầu tiên dễ đọc nhất. Nếu các vấn đề về hiệu suất phát sinh với giải pháp của bạn, hãy kiểm tra xem nó có thể được cải thiện hay không (bằng cách chuyển song song luồng hoặc sử dụng một giải pháp thay thế khác). Nhưng đo lường, đừng đoán.

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