Lambdas, Anonymous Inner Classes and When to Prefer What

First things First, Why should I really care about Lambdas?:

  • Most Loved Feature in Java 8.
  • Primary reason for the introduction of Functional Programming in Java.
  • Improve Readability of the code by reducing unnecessary Boiler Plate.
  • Reduce the JAR file size when compared to Anonymous Inner Classes.

How are they be used in Real Life Applications:

Consider a Contacts App where details related to the contact are stored such as the Name, Phone Number, Email Address, Company and Time at which the contact was added.

Contact Entity:

/**
 * Entity representing the contact details.
 */
public class Contacts {
    private String name;
    private String phoneNumber;
    private String emailAddress;
    private String company;
    private Instant contactCreationTime;
}

Generally in the Contact Apps, when a user wants to look at the list of all their Contacts, they are by default shown in the sorted order of their names.

HighlightedSortedContacts-1

The Default sorting for a class can be done in Java by implementing the compareTo method in the Comparable Interface and later whenever the sort method in Collections class is called, the sorting would be done based on the implementation in the compareTo Method.

Code using Comparable:

public class Contacts implements Comparable<Contact> {
    private String name;
    private String phoneNumber;
    private String emailAddress;
    private String company;
    private Instant contactCreationTime;
    public int compareTo(Contact other) {
        return name.compareTo(other.name);
    }
}
// Fill up the Data.
List<Contacts> contacts;
Collections.sort(contacts);

But what if the user wants the list of contacts to be displayed in another way.

  • Sort by Recently Added
  • Sort by companyName
  • Sort by PhoneNumber / Email Address.

The Comparator Interface comes to rescue in this situation. In the comparator interface, the compare method needs to be implemented and based on the implementation, the array would be sorted.

Code using Comparator:

/**
 * Comparator for Sorting based on the Contacts Creation Time
 */
public class ContactsCreationTimeComparator implements Comparator<Contact> {
    public int compare(Contact c1, Contact c2) {
        return c1.getCreationTime().compareTo(c2.getCreationTime());
    }
}
// Fill up the Data.
List<Contacts> contacts;
Collections.sort(contacts, new ContactsCreationTimeComparator());
/**
 * Comparator for Sorting Contacts on the basis of their Company Name
 */
public class ContactsCompanyNameComparator implements Comparator<Contact> {
    public int compare(Contact c1, Contact c2) {
        return c1.getCompanyName().compareTo(c2.getCompanyName());
    }
}
// Fill up the Data.
List<Contacts> contacts;
Collections.sort(contacts, new ContactsCompanyNameComparator());

And similarly other classes can be defined to sort based on other attributes such as phoneNumber and Email Address.

There are multiple problems with this approach:

  • A new class needs to be created to define a new Comparator for each attribute.
  • As the attributes grow the number of classes also grows proportionally.
  • Also the Functionality of each Comparator class is very less so do you require to really create a new class.

To avoid this, Java came up with a concept called Anonymous Inner Classes? Anonymous What? YEAH Anonymous Inner Classes.

As per the name, they’re anonymous that means they don’t have a name and also they’re inner classes which means they’re present inside an other class. Confused? Here’s an example to avoid the confusion.

Code using Anonymous Inner Class:

public class Contacts implements Comparable<Contact> {
    public static Comparator<Contact> creationTimeComparator =
    new Comparator<Contact> () {
        public int compare(Contact c1, Contact c2) {
            return c1.getCreationTime().compareTo(c2.getCreationTime());
        }
    }
    public static Comparator<Contact> companyNameComparator =
    new Comparator<Contact> () {
        public int compare(Contact c1, Contact c2) {
            return c1.getCompanyName().compareTo(c2.getCompanyName());
        }
    }
    private String name;
    private String phoneNumber;
    private String emailAddress;
    private String company;
    private Date dateAdded;
    public int compareTo(Contact other) {
        return name.compareTo(other.name);
    }
}
// Fill up the Data
List<Contacts> contacts;
// Sorting based on name
Collections.sort(contacts);
// Sorting based on Contact Creation Time
Collections.sort(contacts, creationTimeComparator):
// Sorting based on Company Name
Collections.sort(contacts, companyNameComparator);

As you can see the above code, creationTimeComparator and companyNameComparator are the Anonymous Inner Classes. They also don’t have any name. The Instance was declared on the fly. Also they’re present inside the Contacts class.

The above code solves the problem of creating unnecessary classes for each attribute.

But This code can be simplified much further using the concepts of Lambdas in Java 8.

Code using Lambda:

public class Contacts implements Comparable<Contact> {
    public static Comparator<Contact> creationTimeComparator =
   (c1, c2) -> c1.getCreationTime().compareTo(c2.getCreationTime());
    public static Comparator<Contact> companyNameComparator =
    (c1, c2) -> return c1.getCompanyName().compareTo(c2.getCompanyName());
    private String name;
    private String phoneNumber;
    private String emailAddress;
    private String company;
    private Date dateAdded;
    public int compareTo(Contact other) {
        return name.compareTo(other.name);
    }
}
// Fill up the Data
List<Contacts> contacts;
// Sorting based on name
Collections.sort(contacts);
// Sorting based on Contact Creation Time
Collections.sort(contacts, creationTimeComparator):
// Sorting based on Company Name
Collections.sort(contacts, companyNameComparator);

As you can see, In the Lambda code we omitted the constructor new Comparator<Contact>() and we also omitted the overridden compare method.

But how could the compiler understand all that ?

  • This is using the Comparator< Contact> data type which we declared.
  • So whenever a compiler sees a type Comparator< Contact> and a Lambda corresponding to it, it figures out that the instance we’re creating is for Comparator.
  • Also as there is only a single method in the Comparator interface the compiler can understand that the method which we’re implementing is the compare method and the parameters passed (c1, c2) are the parmeters of that method.

Advantages of using Lambdas:

  • Cleaner Code: Avoids a lot of boiler plate code such as declaration of constructor, overriding method which can be figured out by the compiler.
  • Less Space: The compiler genereates a new class in the case of every Anonymous Inner Class declaration. For example in the above Contacts code where AnonymousInnerClasses are used, the compiler would generate 2 extra classes for the 2 extra anonymous used Contacts$1.class, Contacts$2.class. Like all classes, these are to be loaded and verified before setup. But in the case of Lambda, the compiler doesn’t create a new class for every lambda declared. So in case of lambda we decrease the setup time and also unnecessary creation of classes.

So then in what scenarios can Lambdas be preferred instead of Anonymous Inner Classes:

  • Lambdas can be used only for Interfaces with only a single abstract method to be overridden. They cannot be used in the case of Interfaces with more than a single method to be overridden.
  • Also Lambdas cannot be used in the case of Abstract Classes even if there’s a single method to be overridden. This is due to the complexity involved in implementation.

Apart from the Comparator Interface discussed above, Java has additional interfaces where lambdas can be applied. These are called as Functional Interfaces which have only a single Abstract Method.

Standard Functional Interfaces in Java:

  • Supplier: Takes no argument and returns a value
public interface Supplier<T> {
   /**
    * Takes no argument and returns a value.
    *
    * @return a result
    */
   T get();
}
  • Predicate: Represents a function that takes an argument and returns a boolean.
public interface Predicate<T> {
   /**
    * Evaluates this predicate on the given argument.
    *
    * @param t the input argument
    * @return {@code true} if the input argument matches the predicate,
    * otherwise {@code false}
    */
   boolean test(T t);
}
  • Consumer: Consumer represents a function that takes an argument and returns nothing, essentially consuming its argument
public interface Consumer<T> {
   /**
    * Performs operation on the given argument.
    *
    * @param t the input argument
    */
   void accept(T t);
}
  • Function: Represents a function which takes one argument and returns different types of argument.
public interface Function<T, R> {
   /**
    * Applies this function to the given argument.
    *
    * @param t the function argument
    * @return the function result
    */
   R apply(T t);
}

These standard functional interfaces are used in Streams API which makes Functional Programming possible in Java. How are they used is a topic with larger scope and will be discussed separately.

References:

No Comments

Post A Comment