Java Basics III: Methods and Encapsulation in Java

3.  Methods and Encapsulation in Java

3.1 Scope of Variables

The scope defines lifespan of a variable. There are four different scopes in Java:

  • Local variables
  • Method parameters
  • Instance variables
  • Class variables

3.1.1 Local Variables

Variables defined within a method. These are used mainly to store immediate results of a calculation. They have the shortest life span. In the following code, a local variable avg is defined within the method getAverage():


class Student {

     private double marks1, marks2, marks3;    //instance variables

     private double maxMarks = 100;     //instance variable

     public double getAverage() {

          double avg= 0;   //local variable avg

          avg= ((marks1 + marks2 + marks3) / (maxMarks*3)) * 100;
          //avg inaccessible outside getAverage

          return avg;
     }
}

The scope of a variable can be reduced to a for block or an if statement within a method:

public void localVariableInLoop() {

     for (int ctr= 0; ctr < 5; ++ctr) {  //ctr defined within the loop

          System.out.println(ctr);

     }

     System.out.println(ctr);//ctr inaccessible outside the loop, code would not compile

}
public double getAverage() {

     if (maxMarks > 0) {

          double avg = 0;  //avg local to block

          avg = (marks1 + marks2 + marks3)/(maxMarks*3) * 100;

     return avg;

}

     else {

          avg = 0;   //avg cannot be accessed from here, code would not compile

          return avg;

     }

}

3.1.2 Method Parameters

Variables that accept values within a method those are accessible only in the method that defines them.

class Phone {

     private boolean tested;

     public void setTested(boolean val) {

          tested = val;    //accessible only in setTested method

     }

     public boolean isTested() {

          val= false;   //this line won’t compile

          return tested;

      }

}

The scope of a method parameter may be as long as that of a local variable, or longer, but it can never be shorter.

3.1.3 Instance Variables

An instance variable is available for the life of an object. Declared inside the class, outside of the methods and accessible to all nonstatic methods within the class.

class Phone {
     private boolean tested;   //instance variable
     public void setTested(boolean val) { 
          tested= val;  //tested is accessible here
     }
     public boolean isTested() { 
           return tested;   //tested is accessible here
     }
}

The scope of instance variables is longer than that of local variables or method parameters.

3.1.4 Class Variables

A class variable is defined by using the keyword static. It belongs to a class and not to individual objects of the class, therefore it is shared across all objects.

package com.mobile;
class Phone {
     static boolean softKeyboard= true; //class variable
}
package com.mobile;

class TestPhone {
     public static void main(String[] args) {
          Phone.softKeyboard= false; //accessing variable by using class name
          Phone p1 = new Phone();
          Phone p2 = new Phone();
          System.out.println(p1.softKeyboard);//accessing variable by object name
          System.out.println(p2.softKeyboard);
          p1.softKeyboard = true;
          System.out.println(p1.softKeyboard);
          System.out.println(p2.softKeyboard);
     }
}

A class variable can be changed using the name of the class or an object.

3.1.5. Overlapping Variable Scopes

Different local variables can have different scopes. The scope of local variables may be shorter, equal or as long as the scope of method parameters. Local variables can have a shorter scope if declared in a sub-block in a method.
In order to prevent conflicts with variable names, some rules are necessary:
• A static variable cannot be defined with the same name of an instance variable within the same class.
• Local variables and method parameters cannot use the same name.

3.2.  Object’s Life Cycle

An object’s life cycle lasts from its creation until it goes out of its scope or is no longer referenced by a variable. When an object is accessible, it can be referenced by a variable and can be used by calling its methods and accessing its variables.

3.2.1.    An Object is born

An object is created when the keyword new is used.

class Person {}

class ObjectLifeCycle {

     Person person;    //declaring only a reference

}
class ObjectLifeCycle2 {

     Person person = new Person();//declaring and initializing a person type variable.

}

When an unreferenced object is created, it only executes the relevant constructors of the class, but it cannot be accessed using any reference variable.

class ObjectLifeCycle2 {

     Person person = new Person();  // unreferenced object

     ObjectLifeCycle2() {

          new Person();

     }

}

3.2.2.    Object is Accessible

Once an object is created, it remains accessible until it goes out of scope or its reference variable is explicitly set to null. Also if a reference variable gets reassigned to another object, the previous object becomes inaccessible.

class ObjectLife1 {

     public static void main(String args[]) {

          Exam myExam = new Exam();     //Object created

          myExam.setName("OCA Java Programmer 1");

          myExam = null;      //reference set to null

          myExam = new Exam();       //another object creation

          myExam.setName("PHP");

     }

}

After the reference variable gets assigned to null in the previous example, the first object is considered garbage by Java.

3.2.3.    Object is Inaccessible

An object can become inaccessible if it can no longer be referenced by any variable, if it goes out of scope, if an object’s reference variable is assigned an explicit null value or if it is reinitialized.

When an object can no longer be accessed it is marked as eligible to be garbage collected. A user cannot control or determine the execution of a garbage collector as it is controlled by the Java Virtual Machine. It can be never be determined when a particular object will be garbage collected.

3.3.  Create methods with arguments and return values

A method is a group of statements identified with a name. Methods are used to define the behavior of an object. A method can perform different functions:

3.3.1.    Return type of a method

A method may or may not return a value, a void method does not return a value, a method can return a primitive value or an object of any class. The return type can be any of the eight primitive types defined in Java, the name of any class or an interface.

A void method result cannot be assigned to a variable, in that case the code would not compile. The assigned variable must also be compatible with the returned value.

3.3.2.    Method Parameters

Method parameters are the variables that appear in the definition of a method and specify the type and number of values that a method can accept. No limit exists on the number of parameters that can be put within a method, but it is not a good practice to use more than five or six. A parameter that can accept variable arguments can be defined with ellipsis (…). The ellipsis indicates that the method parameter may be passed as an array or multiple comma-separated values. It can only be done once per method and must be the last variable in the parameter list:

Public int daysOff(int… days){

     Int daysOff = 0;

for(int i = 0; i < days.length; i++ )

          daysOff += days[i];

     return daysOff;

}

 

3.3.3.    Return Statement

This statement is used to exit from a method, with or without a value. If a method returns a value, the return statement must be followed by a return value. Methods that do not return a value (void) are not required to define a return statement. The return statement must be the last statement to execute in a method, the compiler will fail to compile if there’s code after it:

 

void setNumber(double val){

     return;

     val = 3;    //return must be the last statement to execute

}

&nbsp;

void setNumber2(double val){

     if(val < 0)

          return;   //return is the last statement to be executed

     else

          val++;

}

In this scenario, the return statement is not the last statement, but it will be the last statement to execute when the parameter val is less than zero.

3.4.  Create an overloaded method

Overloaded methods are methods with the same name, defined In the same class but with different argument lists.

For example the System.out.println() accepts different types of parameters:

int intVal = 10;

boolean boolVal = false;

String name = "eJava";

System.out.println(intVal);   //prints an integer

System.out.println(boolVal);  //prints a boolean

System.out.println(name); // prints a string

 

3.4.1.    Argument list

The argument lists of an overloaded method can differ in terms of any of these options:

  • Change in the number of parameters that are accepted
  • Change in the types of parameters that are accepted
  • Change in the positions of the parameters that are accepted (based on parameter type, not variable names)

 

double calcAverage(int marks1, double marks2) {

     return (marks1 + marks2)/2.0;

}

double calcAverage(int marks1, int marks2, int marks3) {

     return (marks1 + marks2 + marks3)/3.0;

}

 

In the previous example the methods differ in the number of parameters.

 

class MyClass {

      double calcAverage(double marks1, int marks2) {

            return (marks1 + marks2)/2.0;

      }

     double calcAverage(int marks1, double marks2) {

            return (marks1 + marks2)/2.0;

      }

      public static void main(String args[]) {

            MyClass myClass = new MyClass();

            myClass.calcAverage(2, 3);    // compiler can’t determine method to use

     }

}
 

Because int can be passed to a variable type of double, the values 2 and 3 can be passed to both methods, in this scenario the compilation fails.

3.4.2.    Return type

Methods can’t be defined as overloaded if they only differ in their return types.

3.4.3.    Access modifier

Methods can’t be defined as overloaded if they only differ in their access modifiers.

3.5. Constructors of a class

Constructors are methods that create and return an object of the class in which they are defined. They have the same name of the class where they are defined and they don’t specify a return type, not even void.

3.5.1.    User defined constructors

If the author of the class defines a constructor it is known as user defined constructor. It can be used to assign default values to the variables of the class. If a return type is specified, Java will treat it as another method and not as a constructor.

class Employee {

     void Employee() { //return type specified, not a constructor

           System.out.println("not a Constructor ");

     }

}

class Office {

     public static void main(String args[]) {

           Employee emp = new Employee();

          emp.Employee();// calling the Employee method with void return type

     }
}

 

3.5.1.1. Initializer block

It is a block defined in a class that is not a part of any method. It gets executed for every object that is created for a class.

   
class example {

     {

           System.out.println("Initializer Block");

     }

}

 

3.5.2.    Default constructor

In the absence of a user defined constructor, Java inserts a default constructor; it doesn’t accept method arguments and assigns default values to all the instance variables.

3.5.3.    Overloaded constructors

Overloaded constructors can be defined in the same way as overloaded methods. Overloaded constructors can be called within other constructors by using the keyword this.

 

   
class Employee {

     String name;

     int age;

     Employee() {   //no arg constructor

         this(null, 0); //invoking the other constructor

     }

     Employee(String newName, int newAge) {   //constructor with 2 arguments

           name = newName;

           age = newAge;

     }

}

Constructors cannot be called from any other method that is not a constructor of the class.

 

3.6.  Accessing object fields

 

3.6.1.    What is an object field?

It is another name for an instance variable defined in a class.

3.6.2.    Read and write object fields

To access an object field of a class you can use the set method or use the variable name.

   
obj.name = "Selvan";//as long as access modifier of the variable name is not private

obj.setName("Harry");

 

3.6.3.    Calling methods on objects

Java uses the dot notation to execute a method on a reference variable. When calling a method, the exact number of parameters must be passed on it, literal values and variables are also acceptable when invoking a method.

   
Employee e1 = new Employee();

String anotherVal = "Harry";

e1.setName("Hanna");

e1.setName(anotherVal);

Values returned from a method are also accepted.

   
e2.setName(e1.getName());

 

3.7. Apply encapsulation principles to a class

 

3.7.1.    Need for encapsulation

Sometimes when a class is defined, some parts of it must be hidden to other classes to prevent unexpected behavior or security problems.

3.7.2.    Apply encapsulation

Encapsulation is the concept of defining variables and methods together in a class. The private members of a class are used to hide the internal information to other classes.

 
class example {

     private double num1;

}

 

3.8.  Passing objects and primitives to methods

 

3.8.1.    Passing primitives to methods

It is okay to define a method parameter with the same name as an instance variable or object field. Within a method, a method parameter takes precedence over an object field.

 
class Employee {

     int age;

     void modifyVal(int age) {

           age= age+ 1;

           System.out.println(age);

     }

}

In the previous example, in order to reference the instance variable age, the keyword this must be used before.

3.8.2.    Passing object references to methods

There are two main cases:

  • When a method reassigns the object reference passed to it to another variable
  • When a method modifies the state of the object reference passed to it

 

3.8.2.1.Methods reassign the object references passed to them

 

In the following example, since the object references are passed to other variables, the state of the objects remain intact.

   
class Person {

     private String name;

     Person(String newName) {

          name = newName;

}

     public String getName() {

          return name;

     }

     public void setName(String val) {

          name = val;

     }

}

class Test {

     public static void swap(Person p1, Person p2) {

          Person temp = p1;

          p1 = p2;

          p2 = temp;

     }

     public static void main(String args[]) {

          Person person1 = new Person("John");

          Person person2 = new Person("Paul");

          System.out.println(person1.getName()

          + ":" + person2.getName());      //prints: John : Paul

          swap(person1, person2);

          System.out.println(person1.getName()

          + ":" + person2.getName());   //prints: John : Paul

     }

}

 

3.8.2.2. Methods modify the state of the object references passed to them

In the following example, the method modifies the value of both objects person1 and p1:

  
class Test {

     public static void resetValueOfMemberVariable(Person p1) {

          p1.setName("Rodrigue");

     }

     public static void main(String args[]) {

          Person person1 = new Person("John");

          System.out.println(person1.getName());   //prints John

          resetValueOfMemberVariable(person1);  //changes the name

          System.out.println(person1.getName());  //Prints Rodrigue

     }

}

This entry is the third part of of a series on Java Basics, for further reading:
Java Basics I
Java Basics II

Java Basics

1.1   The structure of a Java class and source code file

1.1.1       Structure of a Java class

A class can define multiple components for example:

  • Package statement
  • Import statement
  • Comments
  • Class declarations and definitions
  • Variables
  • Methods
  • Constructors

1.1.1.1       Package Statement

All Java classes are part of a package; if a class is not defined in a named package it becomes part of a default package, which doesn´t have a name. If a class includes a package statement, it must be the first statement in the class definition (it cannot appear within a class declaration or after the class declaration) and if present, it must appear exactly once in a class:

package certification;
//should be the first statement in a class

class Course{
}

1.1.1.2 Import Statement

Classes and interfaces from the same package can use each other without prefixing the package name. But to use a class or interface from another package, the import statement can be used:

package University;
import certification.ExamQuestion;
// import must be placed after the package

class AnnualExam{
     examQuestion eq;
}

The import statement follows the package but precedes the class declaration.

1.1.1.3 Comments

The comments in Java code can be placed at multiple places in a class. To place multiline comments, start with /* and end with */ . End-of-line comments start with // and are placed at the end of a line of code. Comments can be placed before the package statement.

/**
* @author JRamirez // first name initial + last name End-of-line within a multiline
* @version 0.0.2
*
* Class to store the details of a monument
*/
package uni; // package uni End-of-line comment

class Monument {
     int startYear;
}

1.1.1.4 Class Declaration

Components of a class declaration:

  • Access modifiers
  • Nonaccess modifiers
  • Class name
  • Name of the base class, if the class is extending another class
  • All implemented interfaces, if the class is implementing any interfaces
  • Class body (class fields, methods, constructors), included within a pair of curly

braces, {}

Example:

public final class Runner extends Person implements Athlete {}

1.1.1.5 Compulsory and optional elements of a class:

Compulsory

  • Keyword class
  • Name of the class
  • Class body, marked by the opening and closing curly braces, {}

Optional

  • Keyword class Access modifier, such as public
  • Nonaccess modifier, such as final
  • Keyword extends together with the name of the base class
  • Keyword implements together with the name of the interfaces being implemented

1.1.1.6 Class Definition

The use of a class is to specify the behavior and properties of an object using methods and variables. It is a design from which an object can be created.

1.1.2       Structure and components of a Java source code file

A  Java  source code  file  is  used  to  define  classes  and  interfaces.  All your Java code should be defined in Java source code files (text files whose names end with .java)

1.1.2.1   Definition of interfaces in a Java source code file

An interface is a grouping of related methods and constants, but the methods in an

interface cannot define any implementation.

interface Controls {
     void changeChannel(int channelNumber);
     void increaseVolume();
     void decreaseVolume();
}

1.1.2.2 Definition of single and multiple classes in a single java source code file

A single class or interface can be defined in a single Java source code or multiple classes and interfaces can be within the same source code. The classes and interfaces can be defined in any order of occurrence in a Java source code file. If a public class or interface is defined, its name should match the name of the Java source code file.

1.1.2.3  Application of package and import statements in Java source code files

When an import or package statement is used within a java source code file, it applies to all classes and interfaces defined in that code.

1.2 Executable Java applications

1.2.1    Executable Java classes versus nonexecutable Java clases

An executable Java class is a class which, when handed over to the Java Virtual Machine, starts its execution at a particular point in the class in the main method. A nonexecutable class doesn’t have it. The programmer designates an executable class from all of the files of an application.

1.2.2  Main method

The first requirement in creating an executable Java application is to create a class with  a  method  whose  signature  (name  and  method  arguments)  match  the main method.

public class HelloExam {
         public static void main(String args[]) {
         System.out.println("Hello exam");
    }
}

This main method should comply with the following rules:

  • The method must be marked as a public method.
  • The method must be marked as a static method.
  • The name of the method must be main.
  • The return type of this method must be void.
  • The method must accept a method argument of a String array or a variable argument of type String

The keywords public and static can be interchanged:

public static void main(String[] args)

static public void main(String[] args)

1.3       Java Packages

1.3.1       The need for packages

Packages are used to group classes and interfaces, they also provide protection and namespace management. Subpackages can also be created within the packages.

1.3.2       Defining classes in a package using the package statement

//The first statement in a class or interface must be the package statement:

package certification;

class ExamQuestion {

     //..code

}

Rules about packages:

  • Per Java naming conventions, package names should all be in lowercase.
  • The package and subpackage names are separated using a dot (.).
  • Package names follow the rules defined for valid identifiers in Java.
  • For packaged classes and interfaces, the package statement is the first statement in a Java source file (a .java file). The exception is that comments can appear before or after a package statement.
  • There can be a maximum of one package statement per Java source code file (.java file).
  • All the classes and interfaces defined in a Java source code file will be defined in the same package. There is no way to package classes and interfaces defined within the same Java source code file in different packages.

1.3.2.1       Directory Structure and Package Hierarchy

The hierarchy of the packaged classes and interfaces should match the hierarchy of the directories in which those classes are defined.

1.3.3       Using simple names with import statements

For using classes and interfaces in other classes of your code, there are two options, using the fully qualified name or using the import statement with a simple name of the class or package.

import1


package office;

 class Cubicle {

     home.LivingRoom livingRoom;

}

package office;

import home.LivingRoom;

class Cubicle {

     LivingRoom livingRoom;

}

1.3.4       Using packaged classes without using the import statement

By using its fully qualified name, a class or interface can be used without an import statement.


class AnnualExam {

     certification.ExamQuestion eq;

}

This is often used when there are multiple classes and interfaces with the same name, because the import statement cannot be used in that case.


class AnnualExam {

     java.util.Date date1;

     java.sql.Date date2;

}

1.3.5       Importing a single or all members of a package

By using an asterisk, all the public classes, members and interfaces of a package can be imported at once.


import certification.*;

 class AnnualExam {

     ExamQuestion eq;

     MultipleChoice mc;

}

Importing a class in Java doesn’t add to the size of the file.

1.3.6       Importing Recursively

By using an asterisk, classes from a subpackage are not imported, only classes from the main package.

1.3.7       Default Package Import

If no explicit package is defined for classes or interfaces, they are imported in the default package automatically in all the classes and interfaces in the same directory.


class Person {

     // code

}

class Office {

     Person p;

}

A class from a default package can’t be used in any named packaged class.

1.3.8 Static Imports

To import an individual static member of a class or all its static members, the import static statement must be used.

package certification;

public class ExamQuestion {

     static public int marks;

     public static void print() {

          System.out.println(100);

     }

}
package university;

import static certification.ExamQuestion.marks;

class AnnualExam {

     AnnualExam() {

           marks = 20;

      }

}

//Importing all of the static members:

package university;

import static certification.ExamQuestion.*;

class AnnualExam {

     AnnualExam() {

          marks = 20;

          print();

     }

}

1.4       Java Access Modifiers

1.4.1       Access Modifiers

Access modifiers control the accessibility of a class or interface and its members, by other classes and interfaces.

They can be applied to classes, interfaces, and their members (instance and class variables and methods). Local variables and method parameters can’t be defined using access modifiers.

Java defines four access modifiers:

  • public(least restrictive)
  • protected
  • default
  • private(most restrictive)

1.4.3       Public Access Modifier

Classes and interfaces defined using the public access  modifier  are  accessible  across  all  packages,  from  derived  to  unrelated classes.

1.4.4       Protected Access Modifier

Classes and interfaces defined using the protected access modifier are accessible to classes and interfaces in the same package and all derived classes even in separate packages. They cannot be accessed by unrelated classes in other packages.

1.4.5       Default Access (package access)

Classes and interfaces defined without any explicit access modifier are defined with package accessibility (default accessibility). They can only be accessed by classes and interfaces defined in the same package.

1.4.6       Private Access Modifier

The members of a class defined using the private access modifier are accessible only to themselves. Private members are not accessible outside the class they are defined.

1.5       Nonaccess Modifiers

1.5.1       Abstract Modifier

When added to the definition of a class, interface, or method, the abstract modifier changes  its  default  behavior.

1.5.1.1       Abstract Class

When  the abstract keyword  is  prefixed  to  the  definition  of  a  concrete  class,  it changes it to an abstract class. An abstract class can’t be instantiated. An abstract class can be defined without any abstract methods but a concrete class cannot define an abstract method.

1.5.1.2       Abstract Interface

An interface is an abstract entity by default. The Java compiler automatically adds the keyword abstract to the definition of an interface.

1.5.1.3       Abstract Method

An abstract method doesn’t have  a  body.  Usually, an abstract method is implemented by a derived class.

1.5.1.4       Abstract Variables

No type of variable can be defined as abstract.

1.5.2       Final Modifier

The keyword final changes the default behavior of a class, variable, or method.

1.5.2.1       Final Class

A class defined final cannot be extended by other classes.

1.5.2.2       Final Interface

No interface can be marked as final.

1.5.2.3       Final Variable

A final variable can only be assigned a value once.

1.5.2.4       Final Method

A final method defined in a base class cannot be overridden in a derived class.

1.5.3       Static Modifier

Can be applied to the definitions of variables, methods,  classes,  and  interfaces.

1.5.3.1       Static Variables

They are common to all instances of a class and are not unique. They are shared by all the objects of the class. Static variables may be accessed even when no instances of a class have been created.

1.5.3.2       Static Methods

Static methods aren’t associated with objects and can’t use any of the instance variables of a class. They can be used to use or manipulate static variables of a class.

Nonprivate static variables and methods can be inherited by derived classes and can be redefined within the derived class.

1.5.3.3       What can a static method access?

Non-static variables and methods can access static variables and methods. Static methods and variables cannot access the instance methods of a class.

 

 

 

This entry is the first part of of a series on Java Basics, for further reading:
Java Basics II
Java Basics III

Improving Code With Fluent Interfaces

Fluent What…?

Fluent interface. A “fluent interface” is a concept originally coined by a couple smart guys called Martin Fowler and Eric Evans back in 2005. Rumor has it that once upon a time both guys were eating a huge bag of Cheetos while watching at an old episode of Dr. Who late at night and then aliens came out of the TV screen and granted them this coding style as a gift. Since then, it has become widely used by developers who have gradually started to worry more and more about their source code readability. OK, the story is all BS but it is more interesting than saying they just agreed on the concept in like 10 minutes after a workshop. 😦

Anyways, as I just mentioned, the main purpose of this concept is to improve code readability by using a wrapper class that exposes existing functionality in the form of chained methods. Chained methods are methods that after they complete their work, they return an instance of the objects that contains them so more methods can be called subsequently. Of course, this is all quite confusing so let’s look at some code.

Fluent Threads

For this particular topic, I am going to create an example using one of the most loved features in Java: Threads and Runnables. So, let’s say I want my application to fire a long-running operation in a separate thread so, after the operation starts, my application can get stuck in a loop printing a message notifying the user that the operation is still running. After the operation completes, we print a message saying it so. Quite useless application we have here, but will help demonstrate our example.

Traditionally we would have some code like this to achieve what we want:

package com.codenough.demos.fluentthreads;

public class Main {
    private static Boolean operationThreadIsRunning = false;

    public static void main(String[] args) throws Exception {
        setOperationStatus(false);

        System.out.println("Creating thread...");

        Runnable runnableOperation = new Runnable() {
            @Override
            public void run() {
                setOperationStatus(true);

                try {
                    Thread.sleep(5000);
                    System.out.println("Operation execution finished.");
                } catch (Exception e) {
                    System.out.println("An error ocurred while executing operation.");
                }

                setOperationStatus(false);
            }
        };

        Thread operationThread = new Thread(runnableOperation);

        operationThread.start();

        System.out.println(&quot;Thread created. Now running.&quot;);

        while(true) {
            Thread.sleep(1000);

            if (operationThreadIsRunning) {
                System.out.println("Still waiting for thread...");
            }
            else {
                break;
            }
        }

        System.out.println("Thread execution completed.");
    }

    public static void setOperationStatus(Boolean isRunning) {
        operationThreadIsRunning = isRunning;
    }
}

Again, this code does is just fine. But someone with limited knowledge of Java (or programming at all) would have a hard time trying to figure out  what the hell is a Thread or Runnable. Let alone @Override. So, for the sake of easy readability we can modify the code structure a little bit so, instead of creating threads and runnables, we can do something like this:

createTaskFor(new Action() {
    @Override
    public void onExecute() {
        // Long-running operation..
    }
})
.onErrorUse(new ErrorHandler() {
    @Override
    public void onErrorCaught(Exception exception) {
        // Handle errors..
    }
})
.thenExecute();

Much better, huh? Now readers should only worry about guessing what the friendly @Override annotations are for.

Behind The Scenes

This lovely syntax is possible thanks to a wrapper class working behind the scenes called Task which is full o’ chained methods which return its own instance so we can keep calling and calling methods like there’s no tomorrow until we are ready to run the actual task by calling the thenExecute() method. All for the sake of syntactic sugar.

Here is the implementation of the Task class:

package com.codenough.demos.fluentthreads.threading;

public class Task {
    private Boolean isRunning;
    private ErrorAction errorAction;
    private final Action action;

    public Task(Action action) {
        this.action = action;
        this.isRunning = false;
    }

    public Boolean isRunning() {
        return this.isRunning;
    }

    public Task onErrorUse(ErrorAction errorAction) {
        this.errorAction = errorAction;

        return this;
    }

    public Task thenExecute() {
        Runnable runnableAction = new Runnable() {
            @Override
            public void run() {
                try {
                    isRunning = true;
                    action.onExecute();
                    isRunning = false;
                }
                catch(Exception exception) {
                    errorAction.onErrorCaught(exception);
                }
            }
        };

        new Thread(runnableAction).start();

        return this;
    }

    public static Task createTaskFor(Action action) {
        return new Task(action);
    }
}

As you might notice on most methods, they return their own class instance. These are chained methods and, in this case, are used to configure the task. I have taken advantage of Java static imports so I can call the createTaskFor method in the main method without having to reference the Task class at all on it; making our fluent interface totally unobtrusive. What a good boy. 😉

Now our main method looks a little something like this:

package com.codenough.demos.fluentthreads;

import static com.codenough.demos.fluentthreads.threading.Task.*;
import com.codenough.demos.fluentthreads.threading.*;

public class Main {
    public static void main(String[] args) throws Exception {
        System.out.println("Creating task..");

        Task task =
            createTaskFor(new Action() {
                @Override
                public void onExecute() {
                    try {
                        Thread.sleep(5000);
                        System.out.println("Task internal action execution finished.");
                    }
                    catch(InterruptedException exception) {
                        throw new Error("Thread sleep was interrupted.", exception);
                    }
                }
            })
            .onErrorUse(new ErrorHandler() {
                @Override
                public void onErrorCaught(Exception exception) {
                    System.out.println("An error ocurred while executing task internal action.");
                }
            })
            .thenExecute();

        System.out.println("Task created. Now running.");

        while(true) {
            Thread.sleep(1000);

            if (task.isRunning()) {
                System.out.println("Still waiting for task...");
            }
            else {
                break;
            }
        }

        System.out.println("Task execution completed.");
    }
}

Benefits

Fluent interfaces leverage existing language features (which are not that user-friendly) to improve code readability. This allows us to produce awesome code with superior quality since is a lot easier to read, understand and maintain (QAs will be grateful, which is good). Of course, additional code and effort is required to implement fluent interfaces but in the end it all pays when we, for example, have to implement additional features or modify existing code in a codebase we might have last touched ages ago. You’ll save thousands of dollars on aspirin tablets.

Example

Further Reading