Build a Fluent Interface in Java in Less Than 5 Minutes

Andrew (he/him) - Jan 31 '19 - - Dev Community

(Skip to the good part!)

Building a Basic Class

Building classes in Java is EASY! All you need is a class declaration inside a file with the same name as the class (but with a .java at the end of it). A minimal example would be something like

// MyClassName.java

// class must be public and created in a file called <classname>.java
public class MyClassName {
  // no other code needed!
}
Enter fullscreen mode Exit fullscreen mode

You can put the above code into a file named MyClassName.java and load it in the jshell with the /open command:

jshell> /open MyClassName.java

jshell> MyClassName j = new MyClassName()
j ==> MyClassName@6c3708b3
Enter fullscreen mode Exit fullscreen mode

...that's it! Because MyClassName (like all classes) extends Object, we get some built-in methods if we type j. in the jshell and hit the tab key:

jshell> j.
equals(       getClass()    hashCode()    notify()      notifyAll()   toString()    wait(         
Enter fullscreen mode Exit fullscreen mode

Building a Custom Constructor

Of course, this isn't very exciting. Often, we'll create our own constructors to give objects a certain state (instance variables, etc.). Let's add a few private variables which we can set via a constructor:

// MyClassName2.java

// class must be public and created in a file called <classname>.java
public class MyClassName2 {

  private int instanceInt;
  private double instanceDouble;
  private String instanceString;

  public MyClassName2 (int myInt, double myDouble, String myString) {
    this.instanceInt = myInt;
    this.instanceDouble = myDouble;
    this.instanceString = myString;
  }
}
Enter fullscreen mode Exit fullscreen mode

Now if we try to create an instance of MyClassName2 in the jshell using the default no-argument constructor, we get an error:

jshell> /open MyClassName2.java

jshell> MyClassName2 j2 = new MyClassName2()
|  Error:
|  constructor MyClassName2 in class MyClassName2 cannot be applied to given types;
|    required: int,double,java.lang.String
|    found: no arguments
|    reason: actual and formal argument lists differ in length
|  MyClassName2 j2 = new MyClassName2();
|                    ^----------------^
Enter fullscreen mode Exit fullscreen mode

...this is because the default zero-argument constructor is only provided by Java if no other constructors have been defined. Since we defined our three-argument (int, double, String) constructor above, that's the only one we have access to at the moment. So let's create a MyClassName2 object:

jshell> MyClassName2 j2 = new MyClassName2(42, 3.14, "bonjour le monde!")
j2 ==> MyClassName2@6c3708b3
Enter fullscreen mode Exit fullscreen mode

...great! It worked!

Getters and Setters

Getters

To get anything out of this object, though, we'll need some getters. Let's add those:

// MyClassName3.java

// class must be public and created in a file called <classname>.java
public class MyClassName3 {

  private int instanceInt;
  private double instanceDouble;
  private String instanceString;

  public MyClassName3 (int myInt, double myDouble, String myString) {
    this.instanceInt = myInt;
    this.instanceDouble = myDouble;
    this.instanceString = myString;
  }

  // getters are methods, so they have a return type
  public int getMyInt() {
    return this.instanceInt;
  }

  // we use 'this' to refer to the object calling the method
  public double getMyDouble() {
    return this.instanceDouble;
  }

  // ('this' isn't strictly necessary here, but it can make the code clearer)
  public String getMyString() {
    return this.instanceString;
  }

}
Enter fullscreen mode Exit fullscreen mode

Now, we can create an object with some parameters and extract those parameters later!

jshell> /open MyClassName3.java

jshell> MyClassName3 j3 = new MyClassName3(17, 1.618, "hola mundo!")
j3 ==> MyClassName3@335eadca

jshell> j3.getMyInt()
$5 ==> 17

jshell> j3.getMyDouble()
$6 ==> 1.618

jshell> j3.getMyString()
$7 ==> "hola mundo!"
Enter fullscreen mode Exit fullscreen mode

Setters

Setters let us change the state of an object. In other words, they let us change the values held in an objects instance variables. Setters are just methods that take a parameter and set an instance variable equal to that parameter. Let's add some:

// class must be public and created in a file called <classname>.java
public class MyClassName4 {

  private int instanceInt;
  private double instanceDouble;
  private String instanceString;

  public MyClassName4 (int myInt, double myDouble, String myString) {
    this.instanceInt = myInt;
    this.instanceDouble = myDouble;
    this.instanceString = myString;
  }

  // getters are methods, so they have a return type
  public int getMyInt() {
    return this.instanceInt;
  }

  // we use 'this' to refer to the object calling the method
  public double getMyDouble() {
    return this.instanceDouble;
  }

  // ('this' isn't strictly necessary here, but it can make the code clearer)
  public String getMyString() {
    return this.instanceString;
  }

  public void setMyInt (int newInt) {
    this.instanceInt = newInt;
  }

  public boolean setMyDouble (double newDouble) {
    this.instanceDouble = newDouble;
    return true;
  }

  public String setMyString (String newString) {
    this.instanceString = newString;
    return this.instanceString;
  }

}
Enter fullscreen mode Exit fullscreen mode

Setters should describe (in the method name) what instance variable they're setting, and they usually only take a single parameter (which will be assigned to the instance variable indicated by the name of the method).

But the philosophy on return values from setters falls into several camps:

  • If your setter will always, without fail successfully set the value, some people will return void from a setter method.
  • If there's a chance your setter could fail, you could also return boolean, with true indicating that the instance variable was successfully assigned the provided value
  • Or, you could just return the instance variable in question at the end of the method. If it's successfully been changed, the return value will reflect that.

These three philosophies have been applied, in order, to the setters given above. Let's see them in action:

jshell> MyClassName4 j4 = new MyClassName4(19, 2.71828, "hello world!")
j4 ==> MyClassName4@72b6cbcc

jshell> j4.getMyInt(); j4.setMyInt(23); j4.getMyInt()
$10 ==> 19
$12 ==> 23

jshell> j4.getMyDouble(); j4.setMyDouble(1.2345); j4.getMyDouble()
$13 ==> 2.71828
$14 ==> true
$15 ==> 1.2345

jshell> j4.getMyString(); j4.setMyString("aloha honua"); j4.getMyString()
$16 ==> "hello world!"
$17 ==> "aloha honua"
$18 ==> "aloha honua"
Enter fullscreen mode Exit fullscreen mode

We can see (in the non-existent step $11) that setMyInt() returns void. No value is shown in the jshell.

setMyDouble() returns true, though, as the value was successfully changed. We can see that in the before ($13) and after ($15) steps.

Finally, setMyString() returns the value of this.instanceString at the end of the method. Since the instance variable has successfully been changed, the return value reflects that.

Fluent Interfaces

But we can return any sort of value we want from a method! There's no reason why we couldn't return -14 or "r2349jp3giohtnr" or null from any one of those setters. There's no restriction to the kind of value we can return.

So what happens if we return this?

Let's try it! Let's change our setMyInt() method to return this, which is a reference to the object which is calling the method (in this case, the setMyInt() method):

  public MyClassName5 setMyInt (int newInt) {
    this.instanceInt = newInt;
    return this;
  }
Enter fullscreen mode Exit fullscreen mode

I removed the rest of the class for brevity, but it should be the same as MyClassName4 (with 4 changed to 5 everywhere). So how does this method work? Well, it returns the object which called the method, which in this case is a MyClassName5 object, so we need to make that the return value. Let's try this method and see what happens:

jshell> MyClassName5 j5 = new MyClassName5(0, 0.0, "zero")
j5 ==> MyClassName5@5d76b067

jshell> j5.setMyInt(-1)
$21 ==> MyClassName5@5d76b067
Enter fullscreen mode Exit fullscreen mode

Look at the return values from these two statements in the jshell -- they're the same! They both return the object MyClassName5@5d76b067. If that's true, shouldn't we be able to call another method on the value returned from setMyInt()? Let's try it!

jshell> j5.getMyInt()
$22 ==> -1

jshell> j5.setMyInt(2).setMyDouble(2.2)
$23 ==> true

jshell> j5.getMyInt(); j5.getMyDouble()
$24 ==> 2
$25 ==> 2.2
Enter fullscreen mode Exit fullscreen mode

Look what happened there! Chaining the methods worked! But the return value (in $23) was true because setMyDouble() still returns a boolean. If we change all of our setters to return a MyClassName5 object, we should be able to chain them together in any order!

Let's make a MyClassName6 with setters that return MyClassName6 objects, similar to what we did above. Again, I'll only write the setters here to save space:

  public MyClassName6 setMyInt (int newInt) {
    this.instanceInt = newInt;
    return this;
  }

  public MyClassName6 setMyDouble (double newDouble) {
    this.instanceDouble = newDouble;
    return this;
  }

  public MyClassName6 setMyString (String newString) {
    this.instanceString = newString;
    return this;
  }
Enter fullscreen mode Exit fullscreen mode

Let's try it!

jshell> MyClassName6 j6 = new MyClassName6(6, 6.6, "six")
j6 ==> MyClassName6@131276c2

jshell> j6.setMyString("seven").setMyDouble(77.77).setMyInt(777)
$28 ==> MyClassName6@131276c2

jshell> j6.getMyString(); j6.getMyDouble(); j6.getMyInt()
$29 ==> "seven"
$30 ==> 77.77
$31 ==> 777
Enter fullscreen mode Exit fullscreen mode

That's it! All we had to do to be able to chain methods together was to return this. Returning this lets us call method after method in series, and all of those methods will be applied to the same (original) object!

An idea which is quite similar to fluent interfaces is the Builder pattern, which works almost the same as the setters above, but that is a topic for another article.

Be sure to let me know in the comments if you have any questions or critiques!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .