Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Lambda and Streams in For each and returning result

i am have been learning lambda and streams lately and have kind of been thrown into the deep end really early.

I currently have an array list of books, a user types in a word and if the word equals the books author or title, the books toString(all attributes of the book nicely formatted) is called and returned. Very easy without lambda. But with lambda i just cant seem to figure out how to get it all to work out.

Additionally with the lambda i have to filter out all the books in the array that have a status of damaged or deleted.

The thing is i have to return the results from the stream to eventually display in a gui but cant seem to return any value of a stream.

I have a predicate which will try to do the matching of input parameters. I have no idea if this is right or not and i am pretty burnt out with trying.

I was just wondering the necessary changes i need to make to get this to work?

public String getBookByTitleOrAuthor(String titleOrAuthor) {
    books.stream()
         .filter(BookPredicate.matchTitleOrAuthor(titleOrAuthor))
         .filter(returnedBook -> returnedBook.getBookStatus() != 
            Book.bookStatus.Damaged && returnedBook.getBookStatus() != 
            Book.bookStatus.Deleted)
         .forEach(returnedBook -> returnedBook.toString());

}


// My predicate
public static Predicate<Book> matchTitleOrAuthor(String titleOrAuthor) {
    return b -> titleOrAuthor.equals(b.getTitle()) || 
    titleOrAuthor.equals(b.getAuthor());
}

Thanks in advance guys! sorry if this is a silly question.

My book status enum and getBookStatus:

 public bookStatus getBookStatus() {
         return this.bookStatus;
    }

      public enum bookStatus {
       Available,
       Reserved,
       Borrowed,
       Damaged,
       Deleted

     }
like image 333
awyeanah2 Avatar asked Oct 16 '22 15:10

awyeanah2


2 Answers

forEach returns void whereas you need a String. I wouldn't go with String, though. The method says "I return a book by title or author", so the user would expect a Book instance.

public Book getBookByTitleOrAuthor(String titleOrAuthor) {
    return books.stream()
                .filter(BookPredicate.matchTitleOrAuthor(titleOrAuthor))
                .filter(b -> {
                               final Book.bookStatus status = b.getBookStatus();
                               return status != null && 
                                      status != Book.bookStatus.Damaged && 
                                      status != Book.bookStatus.Deleted; 
                })  // can be moved into a BookPredicate method as well
                .findAny()
                .orElseThrow(() -> new IllegalArgumentException("There is no book for the given author or title."));
}
like image 64
Andrew Tobilko Avatar answered Oct 20 '22 22:10

Andrew Tobilko


We can do it with many ways. We should write clean code here and try to avoid multiple add conditions in single filter and move filters which has high computational cost to lower order. Secondly, you'll get list of matched books here like:

public List<String> getBookByTitleOrAuthor(String titleOrAuthor) {
        return books
            .stream()
            .filter(returnedBook.getBookStatus() != Book.bookStatus.Damaged)
            .filter(returnedBook.getBookStatus() != Book.bookStatus.Deleted)
            .filter(matchTitleOrAuthor(titleOrAuthor))
            .map(Book::toString)
            .collect(Collectors.toList());

}

And if you want only first match then use this:

public String getBookByTitleOrAuthor(String titleOrAuthor) {
        return books
            .stream()
            .filter(returnedBook.getBookStatus() != Book.bookStatus.Damaged)
            .filter(returnedBook.getBookStatus() != Book.bookStatus.Deleted)
            .filter(matchTitleOrAuthor(titleOrAuthor))
            .map(Book::toString)
            .findFirst()
            .orElse(null);
}

And if you want list of books then no need of map:

public List<Book> getBookByTitleOrAuthor(String titleOrAuthor) {
        return books
            .stream()
            .filter(returnedBook.getBookStatus() != Book.bookStatus.Damaged)
            .filter(returnedBook.getBookStatus() != Book.bookStatus.Deleted)
            .filter(matchTitleOrAuthor(titleOrAuthor))
            .collect(Collectors.toList());

}

Here if you see, code is more clean, easy to read and match filter apply only when first two are true Damaged and Deleted.

like image 1
Tayyab Razaq Avatar answered Oct 20 '22 23:10

Tayyab Razaq