In this chapter I’ll focus on discussing how you can program behavior in Java. Classes have attributes (e.g. color, number of doors) and exhibit behavior (e.g. eat, sleep, or dive). There is a special group of programming languages that are called functional (e.g. JavaScript, Scala et al). In these languages you can simply write functions like eat
, sleep
, or dive
, and these functions can live their independent lives and don’t have to be declared in classes. In object-oriented languages behavior is implemented in methods defined in classes.
The phrase to implement a method simply means to write code in a method. Starting from Java 8 you can also program behavior in so called lambda expressions covered later in this chapter.
So far I was declaring and implementing behavior in methods located inside Java classes. But there is a way and a reason to separate the method declaration from its implementation. Such separation can be done using interfaces.
While you can just create a Java class and implement all its methods there, a more formal way of declaring the behavior of the class in a separate interface
. Then a class declaration would include the keyword implements
followed by the name of the interface. This is a way to declare an application program interface (API) - what the class can do - so the programmer can quickly look at the short declaration of the interface rather than going through a longer code of the class.
For years, Java interfaces were used mainly for holding method declarations and final
static
variables. For example, the interface Talkable
contains the declaration of just one method talk
:
interface Talkable{
public void talk();
}
Since the method talk
is not implemented here, it’s called abstract.
Note
|
To create a new interface in IDEA, select the menu File | New | Java class and select Interface in the Kind box. |
You should be thinking, "What’s the point of simply declaring a method without implementing it?" The thing is that more than one class can exhibit the same behavior. For example, a dog, a cat, and a parrot can "talk". But they "implement it" differently. A parrot can repeat words, while dogs bark. Cats talk by meowing. Accordingly you can declare three classes that will implement Talkative
interface differently, as shown below:
public class Parrot implements Talkative {
public void talk(){
System.out.println("My name is Kesha.");
}
}
public class Dog implements Talkative {
public void talk(){
System.out.println("Bark! Bark-bark!");
}
}
public class Cat implements Talkative {
public void talk(){
System.out.println("Meow! Meow!");
}
}
These three classes use the Java keyword implements
, which means they promise to implement all abstract methods in the interface(s) listed after the keyword implements
otherwise the Java compiler will complain: "You promised to implement all abstract methods from Talkable
, where are they? Keep your promise!"
What if we need to create the class Fish
that uses interfaces? Fish don’t talk. But they swim. Dogs can talk (bark) and swim, right? Why won’t we declare yet another interface Swimmable
with the methods swim
and dive
?
public interface Swimmable {
public void swim(int howFar);
public void dive(int howDeep);
}
Now let’s leave the parrots alone. Let’s create a new class Fish
and change the class Dog
just a little bit. The Fish
will implement Swimmable
and the Dog
will implement two interfaces:
public class Fish implements Swimmable {
public void swim(int howFar){
System.out.println("OK, will swim " + howFar + " feet");
}
public void dive(int howDeep){
System.out.println("OK, will dive " + howDeep + " feet");
}
}
public class Dog implements Talkative, Swimmable {
public void talk(){
System.out.println("Bark! Bark-bark!");
}
public void swim(int howFar){
System.out.println("Will swim about a half of this distance: " + howFar/2 + " feet");
}
public void dive(int howDeep){
System.out.println("C'mon, don't ask a dog to dive, please!");
}
}
When a class implements several interfaces, make sure to implement each and every abstract method declared in these interfaces. A class can extend another class and implement interfaces at the same time, for example:
class Fish extends Pet implements Swimmable{
// your code implementing swim(), dive()
// and any other methods goes here
}
Note
|
Java includes several interfaces that don’t declare any methods (e.g. Serializable ). They are called marker interfaces. You don’t need to implement any methods in classes that implement marker interfaces. They are used by the Java compiler internally to generate the byte code in a special way.
|
To tell you the truth, I don’t like the fact that I had to implement the method dive
in the class Dog
. The dogs I’ve seen can’t dive. Can the interface Swimmable
be changed so I’m not forced to write the implementation of the method dive
in the class Dog
? Yes, it can. Java 8 introduced a new keyword default
. Now you’re allowed to write a default implementation of a method in the interface. Such a method won’t be considered abstract anymore, and you won’t be forced to implement it in your class. Let’s add a default implementation of the method dive
to Swimmable
.
public interface Swimmable {
public void swim(int howFar);
public default void dive(int howDeep){
System.out.println("Can't dive, sorry");
};
}
Now the class Dog
doesn’t have to implement the method dive
- the compiler will see a default implementation and won’t complain. The next version of the class Dog
implements only the method swim
from Swimmable
.
public class Dog implements Talkative, Swimmable {
public void talk(){
System.out.println("Bark! Bark-bark!");
}
public void swim(int howFar){
System.out.println("Will swim about a half of this distance: " + howFar/2 + " feet");
}
}
There is no need to change the class Fish
. It also implements Swimmable
, but has its own version of the method dive
, which will override the default implementation of the dive
from Swimmable
. You can still invoke the method dive
on the instance of the Dog
class - the default implementation will be invoked. The next class PetMaster
will demonstrate this.
public class PetMaster {
public static void main(String[] args) {
Dog myDog = new Dog();
myDog.talk();
myDog.swim(7);
myDog.dive(2); // will use default method
Fish myFish = new Fish();
myFish.swim(50);
myFish.dive(20);
}
}
Run this program and you’ll see the following output on the console:
_Bark! Bark-bark!
Will swim about a half of this distance: 3 feet
Can't dive, sorry
OK, will swim 50 feet
OK, will dive 20 feet
The message "Can’t dive, sorry" was printed by the default method dive
from the interface Swimmable
.
Starting from Java 8, interfaces are also allowed to include static methods, which are not specific to any instance and can be used only internally by other methods of the interface. The following example illustrates the use of a static method in the interface. Now the default implementation of the method dive
won’t just reject an offer to swim, but will check the current month: if it’s June, July, or August then diving is allowed because the water should be warm.
The modified version of the Swimmable
interface includes a static method isSummer
that checks the current month and returns true
if it’s June, July, or August. I’m using the Java Date and Time API here. The default method dive
calls the static method isSummer
and either agrees or disagrees to dive depending on the time of the year.
import java.time.LocalDate;
import java.time.Month;
public interface Swimmable {
public void swim(int howFar);
public default void dive(int howDeep){
if (isSummer()){
System.out.println("OK, will dive. The water should be warm.");
} else {
System.out.println("Can't dive, sorry. The water's cold for diving.");
}
};
// Check if it's summer now
static boolean isSummer(){
Month month = LocalDate.now().getMonth();
if (month == Month.JUNE || month == Month.JULY || month == Month.AUGUST){
return true;
} else{
return false;
}
}
}
The method isSummer
uses the class LocalTime
to get Month
, which has a data type enum
that I haven’t used so far. It’s a special data type to represent a fixed number of some values, like months in this case. There are only 12 months, and Java Date and Time API listed them by name in the enum
called Month
. The only values that are allowed here are Month.JANUARY
to Month.DECEMBER
. Using enums makes the programs more readable - it’s easier to deal with months by names than by numbers.
Java comes with many useful classes that are organized in packages. Some packages include classes responsible for drawing, while other packages have classes to work with the Internet, and so on. For example the class LocalDate
is located in the package called java.time
, and the full name of the class LocalDate
is java.time.LocalDate
.
To let the compiler know where the class LocalDate
is located you could specify the full class name, for example:
java.time.LocalDate todaysDate = java.time.LocalDate.now();
But this syntax is difficult to read so we use the import
statements above the class declaration to let the compiler know the location of the class, interface, or enumeration. For example:
import java.time.LocalDate;
Now you can use just the class name without the need to specify the package name:
LocalDate todaysDate = LocalDate.now();
The packages are stored in directories and subdirectories on the disk. If you see a full class name java.time.LocalDate
it means that this class was originally created in the subdirectory time of the directory java.
From now on we’ll use packages and import statement in every chapter of this book. If you want to place your class into a package, just create a subdirectory (e.g. pets) and add a package
statement on the top of your class definition, for example:
package pets;
class Dog{
// your code goes here
}
One last thing: Lazy kids use the wild cards in import statements. Instead of writing one import statement per class, they would use an asterisk:
import java.time.*;
This means that definitions needed for my program are located in the package java.time
. Of course, writing one import statement instead of several ones looks appealing, but the readability of the program suffers. Packages can have dozens of classes and it’s better to explicitly state which classes your program uses.
Beside method declarations, default and static methods you can add static final
variables to the interface declaration. Such variables can be used by the code inside the interface or in the classes that implements it. For example, the interface Swimmable
can define the maximum depth allowed for diving. Here’s yet another version of the interface Swimmable
:
public interface Swimmable {
static final int MAX_DEPTH = 10; // in feet
public void swim(int howFar);
public default void dive(int howDeep){
if (howDeep > MAX_DEPTH){
System.out.println("Can't dive, sorry");
}
};
}
A class that implements Swimmable
can use the value of MAX_DEPTH
as well.
If an interface has only one abstract method declared (default and static methods don’t count) it’s called functional interface. Both Talkative
and Swimmable
are examples of a functional interface - each has only one abstract method. Java 8 introduced a special way of implementing functional interfaces using lambda expressions, which we’ll discuss later in this lesson.
If a method is not implemented we call it abstract, but classes can be declared abstract too, and Java has the keyword abstract
for this. An abstract class is called abstract if it was declared with the keyword abstract
, for example:
public abstract class Animal{
// some code goes here
}
If a class is declared as abstract, you can’t create an instance of it. Typically, abstract classes have some non-implemented methods that are also declared with the abstract
keyword:
public abstract class Animal {
String animalClass;
String name;
public void setName(String name){
this.name = name;
}
public void sleep(){
System.out.println("The " + name +
" is tired and goes to sleep.");
}
abstract public void talk();
}
The class Animal
has two implemented methods: setName
and sleep
and one abstract method talk
. Since the abstract class can not be instantiated, the programmer has to create a descendant class and implement the method talk
there if he or she wants to create an instance of such a class, for example:
public class Dog extends Animal{
public void talk(){
System.out.println("Bark! Bark-bark!");
}
}
Strictly speaking, it’s not a must to implement the method talk
in the class Dog
, but in this case Dog
remains abstract and can’t be instantiated. Maybe the programmer wants to create a class Puppy
that extends Dog
and implements the method talk
there?
And again you might be wondering, "Why complicate a programmer’s life by declaring non-implemented methods?" If you want to build a hierarchy of classes that extend from Animal
you might want to declare an unified method signature, so each class (Dog
, Parrot
, Fish
et al.) will have the same way of initiating the talk.
Abstract classes and interfaces allow to create classes that implement polymorphism, which is an advanced topic and won’t be covered in this book. You can read about it in Oracle’s tutorial if interested. Abstract classes allow you to implement some common static or instance-specific behavior, e.g. setName
and sleep
in the vertical class hierarchy.
Interfaces can’t have instance methods, but they don’t enforce any class hierarchy. So you can have a class Dog
that extends any class, while implementing an interface Talkative
that declares the method talk
.
If you need to use an object of certain type only once, you can kill two birds with one stone: declare an anonymous class and create an instance of it. In this case you don’t even need to give a class a name, hence why it’s anonymous. I’ll show you an example of a program that’s first written without and then written with them.
Let’s say I want to write a simple calculator in Java. In chapters 8 and 9 you’ll create a real calculator with the graphical Users Interface (GUI), but for now I’ll show you how to program operations such as addition and subtraction. The multiplication and division operations can be programmed similarly, so I won’t be implementing these operations.
I want to keep these examples in the package called calc
. You can create a subdirectory named calc in your project and save your classes there. The other option is to right-click on the folder src in your IDEA project and select the menu New | Package and enter the package name there:
First comes the version that doesn’t use anonymous classes - I’ll just use one method for each operation. The code of the class Calculator
is pretty simple. Note the first line that declares the package where the class Calculator
belongs.
package calc;
public class Calculator {
static double addTwoNumbers (double first, double second){
double result = first + second;
System.out.println("" + first + " + " +
second + " = " + result );
return result;
}
static double subtractTwoNumbers (double first, double second){
double result = first - second;
System.out.println("" + first + " - " +
second + " = " + result );
return result;
}
}
public static void main(String[] args) {
addTwoNumbers(3.55, 50.00);
subtractTwoNumbers(3.55, 50.00);
}
}
This class has two methods that manipulate numbers (one per operation), and its main
method invokes each method passing the same values as arguments. After performing the arithmetic operation each method prints the result. Running this program produces the following output:
3.55 + 50.0 = 53.55
3.55 - 50.0 = -46.45
Now let’s redesign this application. Instead of writing a separate method for each operation, I want to write a generic method that can receive the code of the operation and two numbers to be operated upon. This method will have three arguments: the arithmetic operation, the first, and the second number.
The operation is a behavior, so let’s declare an interface to represent it using a functional interface with a single abstract method:
package calc;
public interface ArithmeticOperation {
public double performOperation(double a, double b);
}
I’ll write two implementations of the ArithmeticOperation
interface using anonymous classes - one for addition and one for subtraction. I will also write a method calculate
that will take three arguments: the object that implements ArithmeticOperation
and two numbers.
package calc;
public class CalculatorWithAnonymousClasses {
// The first anonymous class for addition
static ArithmeticOperation addition = new ArithmeticOperation() {
public double performOperation(double first, double second) {
double result = first + second;
System.out.println("" + first + " * " +
second + " = " + result );
return result;
}
};
// The second anonymous class for subtraction
static ArithmeticOperation subtraction = new ArithmeticOperation() {
public double performOperation(double first, double second) {
double result = first - second;
System.out.println("" + first + " - " +
second + " = " + result );
return result;
}
};
public static double calculate(ArithmeticOperation whatToDo, double a, double b ){
return whatToDo.performOperation(a,b);
}
public static void main(String[] args) {
calculate(addition, 3.55, 50.00);
calculate(subtraction, 3.55, 50.00);
}
}
The output of the CalculatorWithAnonymousClasses
program will be the same as from Calculator
. What did we achieve by re-writing calculator this way? We separated the declaration of the behavior and its implementation. The behavior is declared in the interface, and if more than one class needs to implement ArithmeticOperation
, we’ll reuse this interface. The anonymous classes allowed me to create a wrapper object around the method performOperation
, so this object could be passed as an argument to the method calculate
. In the next section I’ll rewrite this calculator again in a more elegant way with lambda expressions.
In many code samples I’ve been using the keyword public
in declaration of member variables and methods. This means that such a variable or a method can be accessed by any other code from the project. In the next chapter I’ll explain how to control access levels in Java.
Up till now, to invoke a method we’ve been creating named or anonymous classes and instantiating objects, but lambda expressions allow to define and invoke a piece of code even without the need to declare classes or instantiate objects.
First, let me introduce a new term - a function. So far to implement any behavior you’d write a method that could be invoked by specifying the class name and a method name (static methods) or by specifying the object variable and a method name (non-static methods). This is a main idea of any object-oriented programming language - the classes and objects are the first-class citizens. You can’t write a program without them.
But there are functional programming languages that don’t need to wrap behavior inside classes. They allow you to implement behavior by writing functions, which are similar to methods in that they can have names, take arguments, and return results. But functions don’t have to be placed inside classes and can live independently.
A lambda expression is a function without a name or anonymous function that you can assign to a variable, pass as an argument to a method or return from a method. In earlier versions of Java you could pass a value to a method only if this value was an object or a primitive. But now a function (a piece of code) becomes a value that can be passed around. Now let’s see it in action.
In the new version of calculator I’ll declare each arithmetic operation as a lambda expression. I will reuse the same functional interface with the three-argument method calcuate
, but will pass the lambda expression that implements ArithmeticOperation
as the first argument.
public class CalculatorWithLambdas {
// Implementing addition as a lambda expression
static ArithmeticOperation addition=(first, second) -> {
double result = first + second;
System.out.println("" + first + " + " +
second + " = " + result );
return result;
};
// Implementing addition as a lambda expression
static ArithmeticOperation subtraction = (first, second) -> {
double result = first - second;
System.out.println("" + first + " - " +
second + " = " + result );
return result;
};
public static double calculate(ArithmeticOperation whatToDo, double a, double b ){
return whatToDo.performOperation(a,b);
}
public static void main(String[] args) {
calculate(addition, 3.55, 50.00);
calculate(subtraction, 3.55, 50.00);
}
}
The difference between CalculatorWithAnonymousClasses
from the previous section and CalculatorWithLambdas
is that the former implements the functional interface as anonymous classes and the latter as lambdas. Lambda expressions offer a concise way of implementation of functional interfaces. To write a lambda expression you need the play by the rules:
-
Declare an the interface that has only one abstract method.
-
Make sure that the arguments of your lambda expression match the arguments of the abstract method.
-
Make sure that the return value of your lambda expression matches the return value of the abstract method.
Review the code of the CalculatorWithLambdas
. Both lambdas addition
and subtraction
abide by these rules.
You may wonder, "Why are there no data types specified for the lambda parameters first and second?" The reason being that Java compiler is smart enough to guess their data types because it knows which abstract method this lambda implements. An educated guess such as this is called type inference.
Note
|
IntelliJ IDEA can automatically convert anonymous classes that implement functional interfaces into lambda expressions. Just make sure that your project language level is 8.0. You can do this by selecting the menu File | Project Structure. |
Let’s return to our examples with pets. I’d like to show you how lambda expressions can simplify your code by reducing the number of required classes. Earlier in this chapter we’ve created the classes Dog
, Parrot
and the only difference between them was the implementation of the interface Talkative
. But we can declare just one class with a method that can take the implementation of the Talkative
in a form of lambda expression.
Let’s start with defining the functional interface Talkative
. This time I’ll slightly change the signature of the method talk
comparing to the version shown earlier in this chapter. I do this to show you how to write a lambda expression that implements a method that has an argument and returns some value.
public interface Talkative {
public String talk(String petName);
}
Now let’s write the class Pet
with a method speakup
that will take implementation of the Talkative
interface as the first argument and the pet’s name as a second one. The class Pet
may have many other useful methods, but I’d like to focus on the speakup
that can receive a piece of code defining the rules of talking and apply this code.
public class Pet {
// Some other code can go here
public String speakup(Talkative talkRules, String name){
return talkRules.talk(name);
}
}
The only other class we need to create is PetMasterLambda
that will create instances of Pet
representing both dogs and parrots, but passing different implementation of the Talkative
interface. Here it comes:
public class PetMasterLambda {
public static void main(String[] args) {
// dogs
Pet myDog = new Pet();
Talkative dogTalkRules = (name) -> {
return "I'm a dog. My name is " + name;
};
System.out.println(myDog.speakup(dogTalkRules, "Sammy"));
// parrots
Pet myParrot = new Pet();
Talkative parrotTalkRules = (name) -> {
return "I'm a parrot. Don't call me " + name;
};
System.out.println(myDog.speakup(parrotTalkRules, "Charlie"));
}
}
We’ve defined different rules for talking parrots and dogs as lambdas in variables parrotTalkRules
and dogTalkRules
accordingly. Not that I have not specified the data type of the variable name
. It’s yet another example of the inferred typing - the Talkative
interface has a single abstract method with the argument of type String
. The second argument of the method speakup
will be passed to the method talk
. Run this program and it’ll print the following:
I'm a dog. My name is Sammy
I'm a parrot. Don't call me Charlie
By using lambda expressions I was able to eliminate the need for creating a separate class for each animal. Of course, this is possible only if the only difference between classes Dog
and Parrot
was implementation of the talking behavior.
I’d like to draw your attention to the fact, that my class Pet has no state - it doesn’t define any class variables. This means that we don’t even need to create instances of the class Pet
, but could simply define its method speakup
as static
and call it just like this:
Pet.saySomething(dogTalkRules, "Mary")
In Chapter 9 I’ll show you more lambda expressions while explaining how to process GUI events such as click on a button or a mouse move. In the Part 2 of the assignment that you’re about to work on, I’ll challenge you to re-write the class Pet
so it’ll keep the rules of talking in its class variable.
Part 1. In this part you’ll do an exercise to prove that you understand Java interfaces.
-
Create a new IDEA project named chapter5.
-
Create a package named pets.
-
In the package pets recreate the final versions of classes
Dog
,Fish
and interfacesSwimmable
andTalkable
from the section Interfaces. -
In the package pets create a new class
Pet
with a constructor that will take the name of the pet (of typeString
) as an argument. -
Change the declarations of the classes
Dog
andFish
so each of them extendPet
while implementingTalkable
andSwimable
interfaces. -
Create the class
PetMaster
from the section "Interfaces", but give pets names while instantiating classesDog
andFish
. -
Modify the implementations of the methods
talk
andswim
so they would print the pet’s name as a part of the output inSystem.out.println
. For example, "My name is Sammy. Bark-Bark-Bark!" -
Run and test the
PetMaster
program.
Part 2. In this part you’ll do an exercise to prove that you understand the basics of lambda expressions.
-
In the project chapter5 create a new package called lambdapets.
-
In the package lambdapets declare the following
Talkative
interface:public interface Talkative { public String talk(String petName); }
-
Create a new version of the class
Pet
with the constructor that takes the rules of talking and pet’s name as arguments. Its methodspeakup
will not have arguments. This is how the code of the newPet
should look like:public class Pet { String name; Talkative myTalkingRules; Pet(Talkative talkRules, String name){ this.name = name; myTalkingRules = talkRules; } public String speakup(){ return myTalkingRules.talk(name); } }
-
Write a new version of the class
PetMasterLambda
that will define talking rules for dogs and parrots. It should create two instances ofPet
(one for a dog, and one for a parrot) and invoke the methodspeakup
on each instance.