0

I was going through the exercises in Java 8 in Action and I came across this question. There are 2 classes Trader and Transaction described as follows:

public class Trader {
    private final String name;
    private final String city;

    public Trader(String n, String c) {
        this.name = n;
        this.city = c;
    }

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

    public String getCity() {
        return this.city;
    }

    public String toString() {
        return "Trader:" + this.name + " in " + this.city;
    }
}
public class Transaction {
    private final Trader trader;
    private final int year;
    private final int value;

    public Transaction(Trader trader, int year, int value) {
        this.trader = trader;
        this.year = year;
        this.value = value;
    }

    public Trader getTrader() {
        return this.trader;
    }

    public int getYear() {
        return this.year;
    }

    public int getValue() {
        return this.value;
    }

    public String toString() {
        return "{" + this.trader + ", " + "year: " + this.year + ", " + "value:" + this.value + "}";
    }
}

Traders and a list of transactions are created as follows:

        Trader mario = new Trader("Mario", "Milan");
        Trader alan = new Trader("Alan", "Cambridge");
        Trader brian = new Trader("Brian", "Cambridge");
        Trader raoul = new Trader("Raoul", "Cambridge");
        List<Transaction> transactions = Arrays.asList(new Transaction(brian, 2011, 300),
                new Transaction(raoul, 2012, 1000), new Transaction(raoul, 2011, 400),
                new Transaction(mario, 2012, 710), new Transaction(mario, 2012, 700), new Transaction(alan, 2012, 950));

The question is to find all traders from Cambridge and sort them by name. The solution given to this problem in the book is as follows:

List<Trader> traders = transactions.stream()
                               .map(Transaction::getTrader)
                               .filter(trader -> trader.getCity().equals("Cambridge"))
                               .distinct()
                               .sorted(comparing(Trader::getName))
                               .collect(toList());

The answer provided above returns the correct results but I was wondering how and why is distinct() using the name field to return distinct traders?

karan mirani
  • 168
  • 1
  • 3
  • 7
  • 1
    distinct isn't using any name field – Stultuske Jul 03 '19 at 11:29
  • 2
    `distinct()` uses the `equals()` method of your objects, so it depends on the implementation of this method. – Samuel Philipp Jul 03 '19 at 11:29
  • @karan mirani if you are looking for more advanced ways to do *distinct* filtering on custom comparisons and streams feel free to check [this thread](https://stackoverflow.com/questions/23699371/java-8-distinct-by-property). – dbl Jul 03 '19 at 12:02

2 Answers2

4

distinct() is not using name field to return distinct traders. It uses the equals method to determine if two Traders are identical. Since Trader doesn't override equals, the only reason this code appears to work is that it passes the same instance (referenced by raoul) to two Transactions, so the default equals implementations (which checks ==) is sufficient to determine that they are identical.

Hence the output List is:

[Trader:Alan in Cambridge, Trader:Brian in Cambridge, Trader:Raoul in Cambridge]

However, if you make the following change:

Trader mario = new Trader("Mario", "Milan");
Trader alan = new Trader("Alan", "Cambridge");
Trader brian = new Trader("Brian", "Cambridge");
Trader raoul = new Trader("Raoul", "Cambridge");
Trader raoul2 = new Trader("Raoul", "Cambridge");
List<Transaction> transactions = Arrays.asList(new Transaction(brian, 2011, 300),
        new Transaction(raoul, 2012, 1000), new Transaction(raoul2, 2011, 400),
        new Transaction(mario, 2012, 710), new Transaction(mario, 2012, 700), new Transaction(alan, 2012, 950));

You'll see that now, since raoul and raoul2 are not equal (even though they have the same name), they will both appear in the output List:

[Trader:Alan in Cambridge, Trader:Brian in Cambridge, Trader:Raoul in Cambridge, Trader:Raoul in Cambridge]
Eran
  • 387,369
  • 54
  • 702
  • 768
  • Nice and clear explanation. Just I would like to add the following. This is the "expected" behavior as both of the traders operate at the same city and share a common name, but still are two different persons. The purpose of overriding `equals` and `hashcode` in the case would be that we could verify one's identity by taking into account any unique and personal id, like driving license card id, insurance number, trader license id, etc. By design, if we are instantiating two different instances of a type which share values with one another, they are definitely not the same object. – dbl Jul 03 '19 at 11:54
1

Stream.distinct() compare objects by Object.equals(Object) https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#distinct--

in this example, you have 4 Trader objects which are compared by the default implementation of Object.equals() since Trades class doesn't override equals method

Vitalik K
  • 11
  • 2