0

Can someone explain why when I put lambda expression into Copmarator.compring() in the following code, it's not working. And when I extract the same expression into Function variables, it works fine.

There is two examples (in the Main.java class), one of them is not compiling (The one with Optional) and the other is compiling but throw an error when executed. I've put in comment the not working code to show that it works fine when the expressions are extracted into variables.

package com.slide;

import java.time.LocalDate;
import java.time.Month;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;

import static java.util.Comparator.*;
import static java.util.Comparator.reverseOrder;

public class Main {

    static class Book {
        private int price;
        private LocalDate date;
        private Author author;

        public Book(int price, LocalDate date, Author author) {
            this.price = price;
            this.date = date;
            this.author = author;
        }

        public int getPrice() { return price; }
        public void setPrice(int price) { this.price = price; }
        public LocalDate getDate() { return date; }
        public void setDate(LocalDate date) { this.date = date; }
        public Author getAuthor() { return author; }
        public void setAuthor(Author author) { this.author = author; }

        @Override
        public String toString() {
            String authorName = author != null && author.getName() != null ? author.getName() : "null";
            return "{ price : "
                    .concat(String.valueOf(price))
                    .concat(", year : ")
                    .concat(date != null ? date.toString() : "null")
                    .concat(", author : ")
                    .concat(authorName)
                    .concat("}");
        }
    }

    static class Author {
        private String name;

        public Author(String name) {
            this.name = name;
        }

        public String getName() { return name; }
        public void setName(String name) { this.name = name; }
    }

    static Author john = new Author("John");
    static Author marguerite = new Author("Marguerite");
    static Author charlton = new Author("Charlton");
    static Author gwen = new Author("Gwen");
    static Author marion = new Author("Marion");
    static Author mark = new Author("Mark");
    static Author jerry = new Author("Jerry");
    static Author bob = new Author("Bob");
    static Author dennis = new Author("Dennis");
    static Author mike = new Author(null);


    static Book b1 = new Book(15, LocalDate.of(1983, Month.JANUARY, 1), john);
    static Book b2 = new Book(8, LocalDate.of(1998, Month.AUGUST, 1), marguerite);
    static Book b3 = new Book(14, LocalDate.of(1992, Month.SEPTEMBER, 1), john);
    static Book b4 = new Book(10, LocalDate.of(2016, Month.MAY, 1), charlton);
    static Book b5 = new Book(10, LocalDate.of(1997, Month.FEBRUARY, 1), gwen);
    static Book b6 = new Book(8, LocalDate.of(1986, Month.AUGUST, 1), charlton);
    static Book b7 = new Book(54, LocalDate.of(1998, Month.MARCH, 1), john);
    static Book b8 = new Book(10, LocalDate.of(1997, Month.FEBRUARY, 1), marion);
    static Book b9 = new Book(32, LocalDate.of(2013, Month.JULY, 1), mark);
    static Book b10 = new Book(5, LocalDate.of(1997, Month.AUGUST, 1), jerry);
    static Book b11 = new Book(10, LocalDate.of(2008, Month.MAY, 1), bob);
    static Book b12 = new Book(9, LocalDate.of(1978, Month.FEBRUARY, 1), dennis);
    static Book b13 = new Book(10, LocalDate.of(1995, Month.FEBRUARY, 1), mike);
    static Book b14 = new Book(10, LocalDate.of(1995, Month.FEBRUARY, 1),null);

    public static List<Book> givenBooksWithNulls(){
        return Arrays.asList(b1, b2, b3, b4, null, b5, b6, b7, b8, null, b13, b9, b10, b14, b11, b12);
    }

    public static void main(String[] args) {
        List<Book> books = givenBooksWithNulls();
        Function<Book, Integer> fPrice = b -> b == null ? null : b.getPrice();
        Function<Book, String> fAuthor = b -> b == null ? null : b.getAuthor() == null ? null : b.getAuthor().getName();
        // Woks good with the Functions as Variables
        books.stream()
                .filter(b -> b == null || b.getDate().getYear() > 1990)
                .sorted(comparing(fPrice, nullsLast(naturalOrder()))
                        .thenComparing(fAuthor, nullsLast(reverseOrder()))
                )
                .forEach(System.out::println);

        // It doesn't work when I put the content of the Functions inline
        // Java says getPrice() cannot be find in java.lang.Object
        // it seems like comparing doesn't like the Lambda expression
//        books.stream()
//                .filter(b -> b == null || b.getDate().getYear() > 1990)
//                .sorted(comparing(b -> b == null ? null : b.getPrice(), nullsLast(naturalOrder()))
//                        .thenComparing(b -> b == null ? null : b.getAuthor() == null ? null : b.getAuthor().getName(), nullsLast(reverseOrder()))
//                )
//                .forEach(System.out::println);


        System.out.println("====================================");

        // Other example using Optional

        Predicate<Book> fFilter = b -> Optional.ofNullable(b)
                .map(bb -> bb.getDate().getYear() > 1990)
                .orElse(false);

        Function<Book, Integer> fPrice2 = b -> Optional.ofNullable(b)
                .map(Book::getPrice)
                .orElse(null);

        Function<Book, String> fAuthor2 = b -> Optional.ofNullable(b)
                .map(Book::getAuthor)
                .map(Author::getName)
                .orElse(null);

        // This also works when I use the Predicate and Functions as variables
        givenBooksWithNulls().stream()
                .filter(fFilter)
                .sorted(comparing(fPrice2, nullsLast(naturalOrder()))
                        .thenComparing(fAuthor2, nullsLast(reverseOrder()))
                )
                .forEach(System.out::println);

        // Same thing Here, not working when we replace the variables by their content of Lambda expression
//        givenBooksWithNulls().stream()
//                .filter(b -> Optional.ofNullable(b)
//                        .map(bb -> bb.getDate().getYear() > 1990)
//                        .orElse(false))
//                .sorted(comparing(b -> Optional.ofNullable(b)
//                        .map(Book::getPrice)
//                        .orElse(null), nullsLast(naturalOrder()))
//                        .thenComparing(b -> Optional.ofNullable(b)
//                                .map(Book::getAuthor)
//                                .map(Author::getName)
//                                .orElse(null), nullsLast(reverseOrder()))
//                )
//                .forEach(System.out::println);
    }
}

UPDATE :

I'm separating the code in this update for those who prefer it separated.

Author.java

package com.slide;

public class Author {
    private String name;

    public Author(String name) {
        this.name = name;
    }

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
}

Book.java

package com.slide;

import java.time.LocalDate;

public class Book {
    private int price;
    private LocalDate date;
    private Author author;

    public Book(int price, LocalDate date, Author author) {
        this.price = price;
        this.date = date;
        this.author = author;
    }

    public int getPrice() { return price; }
    public void setPrice(int price) { this.price = price; }
    public LocalDate getDate() { return date; }
    public void setDate(LocalDate date) { this.date = date; }
    public Author getAuthor() { return author; }
    public void setAuthor(Author author) { this.author = author; }

    @Override
    public String toString() {
        String authorName = author != null && author.getName() != null ? author.getName() : "null";
        return "{ price : "
                .concat(String.valueOf(price))
                .concat(", year : ")
                .concat(date != null ? date.toString() : "null")
                .concat(", author : ")
                .concat(authorName)
                .concat("}");
    }
}

DataExample.java

package com.slide;

import java.time.LocalDate;
import java.time.Month;
import java.util.Arrays;
import java.util.List;

public class DataExample {
    static Author john = new Author("John");
    static Author marguerite = new Author("Marguerite");
    static Author charlton = new Author("Charlton");
    static Author gwen = new Author("Gwen");
    static Author marion = new Author("Marion");
    static Author mark = new Author("Mark");
    static Author jerry = new Author("Jerry");
    static Author bob = new Author("Bob");
    static Author dennis = new Author("Dennis");
    static Author mike = new Author(null);


    static Book b1 = new Book(15, LocalDate.of(1983, Month.JANUARY, 1), john);
    static Book b2 = new Book(8, LocalDate.of(1998, Month.AUGUST, 1), marguerite);
    static Book b3 = new Book(14, LocalDate.of(1992, Month.SEPTEMBER, 1), john);
    static Book b4 = new Book(10, LocalDate.of(2016, Month.MAY, 1), charlton);
    static Book b5 = new Book(10, LocalDate.of(1997, Month.FEBRUARY, 1), gwen);
    static Book b6 = new Book(8, LocalDate.of(1986, Month.AUGUST, 1), charlton);
    static Book b7 = new Book(54, LocalDate.of(1998, Month.MARCH, 1), john);
    static Book b8 = new Book(10, LocalDate.of(1997, Month.FEBRUARY, 1), marion);
    static Book b9 = new Book(32, LocalDate.of(2013, Month.JULY, 1), mark);
    static Book b10 = new Book(5, LocalDate.of(1997, Month.AUGUST, 1), jerry);
    static Book b11 = new Book(10, LocalDate.of(2008, Month.MAY, 1), bob);
    static Book b12 = new Book(9, LocalDate.of(1978, Month.FEBRUARY, 1), dennis);
    static Book b13 = new Book(10, LocalDate.of(1995, Month.FEBRUARY, 1), mike);
    static Book b14 = new Book(10, LocalDate.of(1995, Month.FEBRUARY, 1),null);

    public static List<Book> givenBooksWithNulls(){
        return Arrays.asList(b1, b2, b3, b4, null, b5, b6, b7, b8, null, b13, b9, b10, b14, b11, b12);
    }
}

Main.java

package com.slide;

import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;

import static java.util.Comparator.*;
import static java.util.Comparator.reverseOrder;

public class Main {

    public static void main(String[] args) {
        List<Book> books = DataExample.givenBooksWithNulls();
        exampleOne(books);
        System.out.println("====================================");
        exampleTwo(books);

    }

    private static void exampleOne(List<Book> books) {
        Function<Book, Integer> fPrice = b -> b == null ? null : b.getPrice();
        Function<Book, String> fAuthor = b -> b == null ? null : b.getAuthor() == null ? null : b.getAuthor().getName();
        // Woks good with the Functions as Variables
        books.stream()
                .filter(b -> b == null || b.getDate().getYear() > 1990)
                .sorted(comparing(fPrice, nullsLast(naturalOrder()))
                        .thenComparing(fAuthor, nullsLast(reverseOrder()))
                )
                .forEach(System.out::println);

        // It doesn't work when I put the content of the Functions inline
        // Java says getPrice() cannot be find in java.lang.Object
        // it seems like comparing doesn't like the Lambda expression
//        books.stream()
//                .filter(b -> b == null || b.getDate().getYear() > 1990)
//                .sorted(comparing(b -> b == null ? null : b.getPrice(), nullsLast(naturalOrder()))
//                        .thenComparing(b -> b == null ? null : b.getAuthor() == null ? null : b.getAuthor().getName(), nullsLast(reverseOrder()))
//                )
//                .forEach(System.out::println);
    }

    private static void exampleTwo(List<Book> books) {
        // Other example using Optional

        Predicate<Book> fFilter = b -> Optional.ofNullable(b)
                .map(bb -> bb.getDate().getYear() > 1990)
                .orElse(false);

        Function<Book, Integer> fPrice2 = b -> Optional.ofNullable(b)
                .map(Book::getPrice)
                .orElse(null);

        Function<Book, String> fAuthor2 = b -> Optional.ofNullable(b)
                .map(Book::getAuthor)
                .map(Author::getName)
                .orElse(null);

        // This also works when I use the Predicate and Functions as variables
        books.stream()
                .filter(fFilter)
                .sorted(comparing(fPrice2, nullsLast(naturalOrder()))
                        .thenComparing(fAuthor2, nullsLast(reverseOrder()))
                )
                .forEach(System.out::println);

        // Same thing Here, not working when we replace the variables by their content of Lambda expression
//        books.stream()
//                .filter(b -> Optional.ofNullable(b)
//                        .map(bb -> bb.getDate().getYear() > 1990)
//                        .orElse(false))
//                .sorted(comparing(b -> Optional.ofNullable(b)
//                        .map(Book::getPrice)
//                        .orElse(null), nullsLast(naturalOrder()))
//                        .thenComparing(b -> Optional.ofNullable(b)
//                                .map(Book::getAuthor)
//                                .map(Author::getName)
//                                .orElse(null), nullsLast(reverseOrder()))
//                )
//                .forEach(System.out::println);
    }
}
Samy
  • 121
  • 3
  • 9

3 Answers3

2

With the variable typed to Function<Book, Integer> the compiler knows the exact generic types. This information is missing in the inline version. Specifying explicit type parameters come to help then as follows:

.sorted(
    Comparator.<Book, Integer> comparing( ...

See also this answer.

Tim Biegeleisen
  • 502,043
  • 27
  • 286
  • 360
Michal
  • 2,353
  • 1
  • 15
  • 18
1

The problem is the resolution of the generic types. I am not definitely sure, but I think it is the signature of thenComparing expecting a Function<? super T, ...>. That means, the "root"-Comparator is bound to sth. "super" T, i.e. to the broadest type possible, i.e. Object.

Explicitly defining the generic type, would also make it compile (see Michal's anwser):

.sorted(Comparator.<Book, Integer> comparing(b -> b.getPrice())
   .thenComparing(...))
M.F
  • 403
  • 2
  • 10
0

IntellIj tells b is an Object, not a Book:

IntellIj error

In order to have it working, cast b to a Book:

books.stream()
        .filter(b -> b == null || b.getDate().getYear() > 1990)
        .sorted(comparing((Book b) -> b == null ? null : b.getPrice(), nullsLast(naturalOrder()))
                .thenComparing(b -> b == null ? null : b.getAuthor() == null ? null : b.getAuthor().getName(), nullsLast(reverseOrder()))
        )
        .forEach(System.out::println);

Same applies to the second part.

Abrikot
  • 965
  • 1
  • 9
  • 21