Grim
Grim

Reputation: 2097

Function interface as a lambda to sort a collection?

I am studying lambda expressions and I am struggling with on how to use the java.util.function.Function to sort a collection. Could someone help me or at give me some pointers on how to achieve this?

I have a book POJO and a class that stores books in a collection. I am trying to use a lambda expression of the Function interface to return the same collection but sorted. I could use Collections.sort() and return it that way but I was thinking there was a way to do it with the Function interface.

public class BookTable {

  private Map<Integer, Book> bookMap;

  public BookTable() {
      this.bookMap = new HashMap<>();
  }

  public void addBook(Book book) {
     bookMap.put(size(), book);
  }

  public int size() {
      return bookMap.size();
  }

  public List<Book> getRecord(int key) {
      return Collections.singletonList(bookMap.get(key));
  }

  public List<Book> getRecordsWhere(Predicate<Book> predicate) {
      return bookMap.values()
              .stream()
              .filter(predicate)
              .collect(Collectors.toList());
  }

  public List<Book> getSortedRecords(Function<Book, Comparable> fieldExtractor) {
      // Return sorted list....
  }
}

Book POJO

public class Book {

  private String title;
  private String author;

  public String getTitle() {
      return title;
  }

  public void setTitle(String title) {
      this.title = title;
  }

  public String getAuthor() {
      return author;
  }

  public void setAuthor(String author) {
     this.author = author;
  }
}

Just a quick test...

public class BookTableTest {

  public static void main(String[] args) {

      File file = new File("booklist.csv");
      BookTable table = new BookTable();

      Book book1 = new Book();
      book1.setAuthor("Author 1");
      book1.setTitle("Title 1");

      Book book2 = new Book();
      book2.setAuthor("Book 2 Author 1");
      book2.setTitle("Book 2 Title 1");

      Book book3 = new Book();
      book3.setAuthor("The best author");
      book3.setTitle("The best title");        

      table.addBook(book3);
      table.addBook(book1);
      table.addBook(book2);

      System.out.println("## Sorted by Title");
      System.out.println(table.printRecords(table.getSortedRecords(Book::getTitle)));

      System.out.println();
      System.out.println("## Sorted by Author");
      System.out.println(table.printRecords(table.getSortedRecords(Book::getAuthor)));

  }
}

Upvotes: 9

Views: 4264

Answers (4)

Grim
Grim

Reputation: 2097

I wanted a solution using the Function interface but John Kugelman's answer makes a really good point....This is the solution I ended up using after a few hours of research.

Comparable is a generic type, so I should have parameterized it but parameterizing it didn't work.

What I was trying to do:

public List<Book> getSortedRecords(Function<Book, Comparable> fieldExtractor) {

    return bookMap
            .values()
            .stream()
            .sorted(Comparator.comparing(fieldExtractor))
            .collect(Collectors.toList());
}

The method didn't know the type of the field that was going to be extracted from the book. The solution was to make the method itself generic:

public <T extends Comparable<? super T>> List<Book> getSortedRecords(Function<Book, T> fieldExtractor) {

    return bookMap
            .values()
            .stream()
            .sorted(Comparator.comparing(fieldExtractor))
            .collect(Collectors.toList());
}

Upvotes: 0

Saravana
Saravana

Reputation: 12817

Here is an implementation

List<Book> getSortedRecords(Predicate<Book> predicate, Comparator<Book> comparator) {
    return this.bookMap.values().stream().filter(predicate).sorted(comparator).collect(Collectors.toList());
}

where the Predicate and Comparator can be passed as a arguments for getSortedRecords like below

getSortedRecords(b -> b != null, Comparator.comparing(Book::getTitle))

Upvotes: 1

John Kugelman
John Kugelman

Reputation: 361879

Part of the value of streams is not having to reinvent all of the sorting, filtering, and collection methods. Instead of having filter method, a sort method, a print method, etc., I would simply have one method that returns a Collection<Book>. Let the caller do whatever they want with that collection.

public Collection<Book> getRecords() {
    return bookMap.values();
}

...

table.getRecords().stream()
   .filter(book -> book.getAuthor().equals("Charles Dickens"))
   .sorted(Comparator.comparing(Book::getTitle))
   .forEach(System.out::println);

The advantage of this approach is that it allows the user to chain together the different operations however they like. They can filter and sort and print if that's what they want to do.

Upvotes: 8

Leonardo Andrade
Leonardo Andrade

Reputation: 616

You can use the method sorted of stream. https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#sorted--

Upvotes: 0

Related Questions