Accessing and filtering Java class list elements by id

Viewed 725

I have a result of type User as follows:

List<User> result  = (getting values from db)

the properties of class User that I need are:

private Integer id,
private String name, 
private List<Books> books = new ArrayList<>(); 

etc.

And Class Books has a field Id to.

Given that I have the user Id, and the books Id. I would like to access the other results inside the array book, for that book id.

So I need to -> give the result variable a specific userId, and access books property for that user, then give a bookId, and access book properties for that book id, in that user. I tried using java streams:

List<BooksDetails> userBook = result.stream()
  .filter(user -> user.getId() == 1)
  .collect(Collectors.toList())
  .get(0)
  .getBooks()
  .stream()
  .filter(book -> book.getId() == 2)
  .collect(Collectors.toList())
  .get(0)
  .getBooksDetails();

But sometimes I get index out of bound error when accessing get(0) element. And it seems there might be a cleaner solution to this. What approaches can I use to accessing nested properties by giving the associated id's in a list? OR would I have to convert it to a nested map instead?

3 Answers

It sounds like you want to do the following:

  • filter users for userId == 1
  • then flatMap the Books list
  • filter those books for bookid == 2
  • and then get the book details and store in a list
record BooksDetails(){};
record Books(int getId, List<BooksDetails> getBooksDetails){}
record User(int getId, List<Books> getBooks){}
  
List<User> result = new ArrayList<>();
         
Optional<List<BooksDetails>> bd = result.stream()
     .filter(user -> user.getId() == 1)
     .flatMap(b->b.getBooks().stream())
     .filter(book -> book.getId() == 2)
     .findFirst()
     .map(Books::getBooksDetails);

Update

I have updated my answer to include records which shows my understanding of your classes. Since the ultimate result is based on a book Id, I am presuming that 1) it is unique. and 2) there are no duplicate books in the list. So this will return an Optional<List<BooksDetails>> which contains the list of book details for that bookId for the userId. If not book is found, it returns an empty optional.

Also,I replaced the flatMap with a Lambda and it worked.

List result = (getting values from db)

Might be not the answer you expect, but this predicate-filter (for user.id == 1 and book.id == 2) & find-first probably could be implemented in a layer before the result, as (database/SQL) query.

For example:

SELECT u.*
FROM users u 
JOIN books b ON b.user_id = u.id
WHERE u.id = 1 AND b.id = 2 

You can use stream to filter list based on id. Then based on filteredList is empty or not, you will get your data.

import java.util.*;
import java.util.stream.*;

public class MyClass {
    public static void main(String args[]) {
        final List<Book> books = Arrays.asList(new Book(1, 1, "details1"), new Book(2, 1, "details2"));
        
        final List<User> users = Arrays.asList(new User(1, "name1", books.stream().filter(b -> b.userId == 1).collect(Collectors.toList())),
            new User(2, "name2", books.stream().filter(b -> b.userId == 2).collect(Collectors.toList())), new User(3, "name3", null));
      
      
        final int userId = 1, bookId = 2;
        
        final Book filteredBook = getFilteredBook(userId, bookId, users);
      
        if (null != filteredBook) {
            // put your logic here
            System.out.println(filteredBook.getDetails());
        }

    }
    
    private static Book getFilteredBook(final int userId, final int bookId, final List<User> users) {
        final User filteredUser = users.stream().filter(u -> u.id == userId).findFirst().orElse(null);
        
        if (null == filteredUser) {
            // log your  appropriate error message here
            System.out.println("No user with id: " + userId);
            
            return null;
        }
        
        
        final List<Book> books = filteredUser.books;
        
        if (null == books || books.isEmpty()) {
            // log your  appropriate error message here
            System.out.println("No books associated with userId: " + userId);
            
            return null;
        }
        
        
        final Book filteredBook = books.stream().filter(b -> b.id == bookId).findFirst().orElse(null);
        
        if (null == filteredBook) {
            // log your  appropriate error message here
            System.out.println("No book with id: " + bookId + " associated with userId: " + userId);
            
            return null;
        }
        
        return filteredBook;
    }
    
    
}

class User {
    public final int id;
    public final String name;
    public final List<Book> books;
    
    public User(int id, String name, List<Book> books) {
        this.id = id;
        this.name = name;
        this.books = books;
    }
    
    @Override
    public String toString() {
        return "[id: " + id + ", name: " + name + "]";
    }
}

class Book {
    public final int id;
    public final int userId;
    public final String details;
    
    public Book(int id, int userId, String details) {
        this.id = id;
        this.userId = userId;
        this.details = details;
    }
    
    public String getDetails() {
        return this.details;
    }
    
    @Override
    public String toString() {
        return "[id: " + id + ", userId: " + userId + ", details: " + details + "]";
    }
}
Related