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!
}
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
...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(
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;
}
}
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();
| ^----------------^
...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
...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;
}
}
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!"
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;
}
}
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
, withtrue
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"
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;
}
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
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
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;
}
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
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!