Lesson Thursday

Now that we have the majority of the central functionality for our app, we can review our Trello board and we'll see that many of our tasks have been completed, the hard ones at least. It's easy to see how with steps that should feel familiar to us, we can go ahead and build out our app to add the missing functionality.

Let's check our Trello board:

We've accomplished a ton!

  1. As a user, I want to see all Restaurants in a specific zip code. ***Not yet.*
  2. As a user, I want to see one individual Restaurant DONE!
  3. As a user, I want to see a specific information such as the Restaurant's name, address, phone number, website, and email DONE!
  4. As a user, I want to see which kind of Foodtypes asingle Restaurant is associated with DONE!
  5. As a user, I want to see all Restaurants of a specific Foodtype (say, Pho, or Brunch) so I can browse through them DONE!
  6. As a user, I want to be able to leave a Review of a Restaurant DONE!
  7. As a user, I want to see the average rating for a RestaurantNot yet.
  8. As a user, I want to see all Reviews for a Restaurant DONE!
  9. As an admin, I want to be able to add a Restaurant to the database DONE!
  10. As an admin, I want to delete Restaurants that close. You may have tests for this, but frontend routes have not yet been added.
  11. As an admin, I want to edit Restaurants where details change Not yet.
  12. As an admin, I want to delete Reviews that are from trolls and spammers Not yet.
  13. As an admin, I want to add a Foodtype so a Restaurant can be associated with it DONE!

Adding on Bells and Whistles

Instead of accomplishing the tasks listed out above, let's finish things up by adding one more piece of functionality: sorting reviews by the time they were created.

If you think about it, a bad review from two years ago isn't nearly as important as a bad review from last week - it would be great if we could sort our reviews newest to oldest, instead of oldest to newest.

It would be easy to do this in SQL, as we already know that SQL has powerful queries that allow us to sort data and retrieve sorted lists. It's also much faster than any Java sorting algorithm. But sorting custom Objects is useful skill to have, so let's use this opportunity to learn a.) more about time, and b.) more about sorting custom objects.

The Struggle with Sorting

It is actually quite easy to sort Collections of things in Java - at least, if those Collections are of simple object types such as Strings, Integers, Timestamps or any other data type that Java is familiar with:

Sorting the chars inside a String:

public static String sortString(String input)
    {
        char[] tempArray = input.toCharArray();
        String tempString = Arrays.sort(tempArray);//sort is a static method on the Array class.
        return tempString
    }

or, in the case of an ArrayList:


List<Double> someDoubles = new ArrayList();
    someDoubles.add(0.5);
    someDoubles.add(1.8);
    someDoubles.add(0.1);
    someDoubles.add(0.1);
    someDoubles.add(2.34);
    someDoubles.add(1.71);
    someDoubles.add(0.92);
    someDoubles.add(0.02);

    Collections.sort(someDoubles); //will sort them largest to smallest
    Collections.reverse(someDoubles); //sort in reverse order

Easy enough.

Where it gets more complex is when we want to sort custom objects, because similar to our equals() and hashCode() methods, Java needs to know a lot more specifically HOW to sort a custom object. Without instruction as to which property to sort by, we won't get very far.

Comparator vs. Comparable

There are two main ways to sort custom object types in Java, and both work quite similar, but they do require an understanding of interfaces, which we weren't very familiar with until this week. Now that we know about how interfaces work, we can understand and implement Comparators and Comparables to sort custom data.

Let's look at this simple class:

Teapot.java
public class Teapot {
  private String manufacturer;
  private Date dateOfManufacture;
  private Double capacityInFluidOz;

  public Teapot(String manufacturer, Date dateOfManufacture, Double capacityInFluidOz){
    this.manufacturer = manufacturer;
    this.dateOfManufacture = dateOfManufacture;
    this.capacityInFluidOz = capacityInFluidOz;
  }
}

It's not obvious which property should be used to sort a list of Teapot objects. You could argue that it would be great to sort Teapots in different ways - size, manufacturer, or date. Hmm. It would be great if we could have different ways to sort objects.

Sorting with Comparator

Comparator is an interface we can use to sort by - and using it is very similar to the interfaces we have already written and implemented. Implementing Comparator requires that the implementing class have a specific method called compare that returns an int. Let's implement it for our class and see how it works. We'll sort the Teapot instances based on their manufacturer first.

  public class Teapot implements Comparator<Teapot>{
    private String manufacturer;
    private Date dateOfManufacture;
    private Double sizeInFluidOz;

    public Teapot(String manufacturer, Date dateOfManufacture, Double sizeInFluidOz){
      this.manufacturer = manufacturer;
      this.dateOfManufacture = dateOfManufacture;
      this.capacityInFluidOz = capacityInFluidOz;
    }

    public int compare(Teapot object1, Teapot object2){
      return object1.manufacturer - object2.manufacturer;
    }
  }

The compare method compares the Teapot objects based on the manufacturer. If the method returns 0, then the two objects are equal. If object1 is greater than object2, 1 is returned. If the opposite is true, and object2 is greater than object1, then -1 is returned. It's easy enough to do some branching based on what the return value might be, and perform some action in response.

Of course you are wondering how String properties can be greater or lesser than each other. Well, the String class contains an equals() method similar to the ones we have written for our classes. This built-in equals method can accurately determine alphanumeric order, making sure "Amsterdam" comes before "Bloomingville". This method is a key part of the functionality that makes compare work under the hood.

The comparable interface works very similarly - we can even implement both in the same class, which is useful, as it allows us to sort the same object two ways:

public class Teapot implements Comparator<Teapot>, Comparable<Teapot>{
  private String manufacturer;
  private Date dateOfManufacture;
  private Double capacityInFluidOz;

  public Teapot(String manufacturer, Date dateOfManufacture, Double capacityInFluidOz){
    this.manufacturer = manufacturer;
    this.dateOfManufacture = dateOfManufacture;
    this.capacityInFluidOz = capacityInFluidOz;
  }

  public int compare(Teapot object1, Teapot object2){
    return object1.manufacturer - object2.manufacturer;
  }

  public int compareTo(Teapot otherTeapot){
    return (this.capacityInFluidOz) - otherTeapot.capacityInFluidOz;
  }
}

compareTo also returns 0, -1 or +1.

Why use one over the other?

Comparator has a clear benefit - we have to be able to edit the class where we implement Comparable, but this isn't necessary for Comparator. Let me explain:

public class ChinaShop implements Comparator<Teapot> {
  //properties, constructor, getters, setters
Teapot firstTeapot = new Teapot("Amsterdam", 12/12/2017, 8.5);
Teapot secondTeapot = new Teapot("Bloomingville", 4/5/2009, 12.0);

  public int compare(Teapot firstTeapot, secondTeapot){
    return firstTeapot.manufacturer - secondTeapot.manufacturer;
  }
}

Because compare requires two instances, it is longer and a bit more unwieldy; but if we are not allowed to modify the Teapot class to add the Comparable interface methods necessary, this is the only way we could sort Teapot objects outside of their class.

If you are interested in learning more about Comparator and Comparable, give this blog post a read through.

In our next lesson, we'll get to sorting Reviews by creation time!