0

I have got two cases of method referance:

Case 1:

public class Main {

    static List<Person> personList = List.of(
            new Person("Daria", 27, List.of(new Book("Potop", "H. Sienkiewicz"),
                    new Book("Dywizjon 303", "A. Fiedler"))),
            new Person("Ola", 34, List.of(new Book("Czysty kod", "R.C. Martin"),
                    new Book("Hobbit", "J. R. R. Tolkien"))),
            new Person("Ala", 53, List.of(new Book("Potop", "H. Sienkiewicz"),
                    new Book("Wladca pierscieni", "J. R. R. Tolkien"))),
            new Person("Julka", 12, List.of(new Book("Dywizjon 303", "A. Fiedler"),
                    new Book("Hobbit", "J. R. R. Tolkien"))),
            new Person("Zuzia", 7, List.of(new Book("Robinson Crusoe", "D. Defoe"),
                    new Book("Alicja w krainie czarow", "L. Carroll")))
    );

    public static void main(String[] args) {
        streamWithFunction_map();
    }

    public static void streamWithFunction_map(){

        // map() - let you convert object to sth else
        // List<String> to uppercase
        List<String> namesInCapitalList = personList.stream().map(person -> person.getName().toUpperCase()).collect(Collectors.toList());
        namesInCapitalList.forEach(System.out::println); //DARIA OLA ALA JULKA ZUZIA

        // list of objects to list of Integers
        List<Integer> ageList = personList.stream().map(Person::getAge).collect(Collectors.toList());
        ageList.forEach(System.out::println); // 27 34 53 12 7

        // list of object A to list of object b
        List<BookLoanRecord> bookLoanRecordList_way1 = personList.stream()
                .map(person -> person.getBooks().stream().map(BookLoanRecord::createLoanRecordStaticVersion).collect(Collectors.toList()))
                .flatMap(Collection::stream)
                .collect(Collectors.toList());

        List<BookLoanRecord> bookLoanRecordList_way2 = personList.stream()
                .map(person -> person.getBooks().stream().map(Book::createLoanRecordNotStaticVersion).collect(Collectors.toList()))
                .flatMap(Collection::stream)
                .collect(Collectors.toList());

        List<BookLoanRecord> bookLoanRecordList_way3 = personList.stream()
                .map(Person::getBooks)
                .flatMap(Collection::stream)
                .map(Book::createLoanRecordNotStaticVersion)
                .collect(Collectors.toList());
    }
}

Class Book

public class Book {

    String title;
    String author;

    public Book(String title, String author) {
        this.title = title;
        this.author = author;
    }

//getters & setters
    

    // The same method as createLoanRecordStaticVersion but without static
    public  BookLoanRecord createLoanRecordNotStaticVersion(){
        return new BookLoanRecord(this.title, this.author, LocalDateTime.now());
    }
}

Class BookLoanRecord

public class BookLoanRecord {

    String title;
    String author;
    LocalDateTime dateTime;

    public BookLoanRecord(String title, String author, LocalDateTime dateTime) {
        this.title = title;
        this.author = author;
        this.dateTime = dateTime;
    }

    //getters & setters

    //Needs to be static if we want to use it as method reference in stream
    public static BookLoanRecord createLoanRecordStaticVersion(Book book){
        return new BookLoanRecord(book.title, book.author, LocalDateTime.now());
    }

}

Both method referances work fine in this case (BookLoanRecord::createLoanRecordStaticVersion, Book::createLoanRecordNotStaticVersion).

Case 2:

public class Main {
    public static void main(String[] args) {
        //  returnTime();
        generateRandomPerson();
    }



    public static Person generateRandomPerson() {
//        return PersonGenerator.generateRandomPerson(); //all good here
        return PersonGenerator::generateRandomPerson;  //error: 

    }
}

PersonGenerato class:

public class PersonGenerator {

    private static final Random random = new Random();

    private static final List<String> femaleNamesList = List.of("Diana", "Daria", "Ola", "Ala", "Julka", "Monika", "Magda");
    private static final List<String> maleNamesList = List.of("Adam", "Tomek", "Arek", "Olek", "Alek", "Julek");
    private static final List<String> surnameList = List.of("Newton", "Smith", "Nowak", "Kowalczyk", "Robinson");

    public static Person generateRandomPerson(){

        Sex sex = Sex.getRandomSex();
        String surname= generateRandomSurname();
        String name = generateRandomName(sex);

        return new Person(name, surname, sex);
    }

    private static String generateRandomSurname(){
        return surnameList.get(random.nextInt(surnameList.size()));
    }

    private static String generateRandomName(Sex sex){
        if (sex == Sex.MALE){
            return maleNamesList.get(random.nextInt(maleNamesList.size()));
        } else {
            return femaleNamesList.get(random.nextInt(femaleNamesList.size()));
        }
    }
}

Adding lacking classes:

public class Person {

    private String name;
    private String surname;
    private Sex sex;

    public Person(String name, String surname, Sex sex) {
        this.name = name;
        this.surname = surname;
        this.sex = sex;
    }

    public Person() {
    }

    // setters and getters 

}

and

public enum Sex {
    MALE,
    FEMALE;

    public static Sex getRandomSex(){
        Random random = new Random();
        return values()[random.nextInt(values().length)];

    }
}

And here I'm getting this error: Person is not a functional interface in method generateRandomPerson()

The question is why ? I don't understand why case 1 is ok and I'm getting an error in case 2. Why it wants class Person to be functional interface? What needs to be change to use method referance in case 2 as well ?

Lilinet
  • 13
  • 4
  • 1
    How is case 1 related to your question at all? Why do you think case 2 should work at all? I'm not sure where your misunderstanding is exactly. – Sweeper Jan 09 '22 at 15:50
  • I thought that case 1 and case 2 are "almost the same". I didn't understand why it works for case 1 and didn't for case 2. But after seeing answers below I see it was wrong assumption. – Lilinet Jan 09 '22 at 16:02
  • Watch out for errors. For example, `sex == Sex.MALE` should be `sex.equals(Sex.MALE)`. Other improvements should include having `Book` as a field of `BookLoanRecord` rather than all the fields that are also in `Book`. Imagine if `Book` had 20 fields instead of two? – hfontanez Jan 09 '22 at 17:05
  • @Sweeper this is just a case of information overload. I can understand that as a new member or StackOverflow, the OP might be paranoid about providing not enough information. I agree, nothing in case 1 is relevant to the question and the (trivial) classes `Person` and `Sex` in case two, are not even there LOL - (which means the code is not reproducible) – hfontanez Jan 09 '22 at 17:09
  • @Lilinet inside your `main` method, don't forget to call `get()` method to get the `Person` from the `Supplier`. For example `Person p1 = generateRandomPerson().get();` – hfontanez Jan 09 '22 at 17:32
  • @hfontanez thanks for tips. Though I have got one question. Why should we use equal instead of == for Enum? I know that it makes a big difference for other objects like String but for Enum I thought it is more like "preferances" matter. Attaching useful thread: https://stackoverflow.com/questions/1750435/comparing-java-enum-members-or-equals – Lilinet Jan 09 '22 at 18:16
  • @Lilinet because `==` should be used only to compare primitive values. For objects, you should ALWAYS use `equals()`. The caveat is that the class you are comparing MUST override `equals`. An `Enum` is an object AND it overrides `equals()`. – hfontanez Jan 09 '22 at 18:21
  • From the [Java Language Specification, 8.9.1](https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.9.1): *Because there is only one instance of each enum constant, it is permitted to use the == operator in place of the equals method when comparing two object references if it is known that at least one of them refers to an enum constant.* – passer-by Jan 09 '22 at 18:40
  • @passer-by for someone who is learning and is unaware of how to compare object references, it is better policy to stick to using `==` for primitives. _Permitted_ doesn't mean _recommended_. It is always best to stick to best practices. – hfontanez Jan 09 '22 at 19:01
  • @hfontanez - I disagree, because your advice is fundamentally deceptive about the nature of an enum. I understand Knuth's dictum about it being ok to tell a few lies, but I don't believe it applies in this specific instance. Or to put it another way -- the OP wrote **correct code** and you're telling him it's wrong. – passer-by Jan 09 '22 at 19:45
  • He wrote an enum comparison with equals. This is correct per the designers of the language. Advice to the contrary is incorrect; it makes straightforward comparisons needlessly prolix. And there's no need to be insulting. – passer-by Jan 10 '22 at 00:01

2 Answers2

1

It is because method reference is a reference to a function. Your method is expecting a Person type as a return type and you are trying to return a Supplier. You need to change the return type from Person to Supplier<Person>

Tarun
  • 121
  • 7
0

If you compare the method signature you'll see the difference.

Stream map(Function<? super T,? extends R> mapper)

Here the method expects a Function (Represents a function that accepts one argument and produces a result)

public static Person generateRandomPerson()

Here the expected return value is a Person, not a method producing a Person.

peterulb
  • 2,869
  • 13
  • 20