The Programming in Java course is designed to introduce the basic tokens of Java language and its object-oriented features through a problem-based approach. The course also covers important APIs like collections, date-time, concurrency, along with logging and testing.
Outcome
Write a program using Java to solve a given problem with proper syntax and semantics
Debug code using Eclipse debugger
Design and implement classes for a given business scenario
Apply Object Oriented concepts for a given business scenario
Perform static code analysis using PMD
Develop code that makes use of exception handling mechanisms provided by Java
Write unit test cases to perform automated unit testing using JUnit, and check code coverage using EclEmma
Perform logging of different events using Log4j API
Implement the business logic for a given problem by choosing appropriate collection classes
Write code using the Collections API. In particular, the Set, List, and Map interfaces and their implementation classes
Identify and use utility classes like LocalDate, LocalTime, etc. for a given business scenario
Write code using lambda expressions and the Stream API
Computer applications are playing a crucial role across various business domains like banking, insurance, education, entertainment, etc.
These applications are built using programming languages along with the help of several other hardware and software resources.
In this course you will learn Java, one of the most popular programming languages used to build such applications.
Java has been evolving since 1991 with different editions
-
As of 2018, it is being used by 10 million developers worldwide
More than 15 billion devices are powered by Java technology
More than 125 million Java-based TV devices have been deployed
97% of enterprise desktops run Java
The VOIP at your desk and the editor that you use to write your programs are powered by Java
Have a glance at what is going on in the
Java world.
Learning through problems
We will come across 6 different problem scenarios through which we will learn programming in Java.
j1
Java Development Kit
JDK consists of development tools required for developing a Java application.
It also consists of Java runtime environment for executing a Java application.
Java Editions
Java comes in various editions to serve the needs of different applications.
Two key editions of Java are:
Java standard edition (J2SE): provides a platform for writing programs which can run on desktops and servers.
Example applications: popular editors for programming like eclipse and netbeans.
Java enterprise edition (J2EE): provides a platform for building and deploying software for an enterprise.
Example applications: sparsh, icicibank.com.
In this course we will be focusing on creating Java applications using J2SE.
The binaries of these editions to support various platforms are provided as Java Development Kit (JDK).
j2
Project Structure
Note:
A single .java file can have multiple classes, but they are compiled into their own separate .class files.
It is a good practice to have a single Java class in a .java file.
The class containing the main method must be public.
The .java file should have the same name as that of the public class.
There can be only one public class in a .java file.
Platform Independence
Usually larger applications are created by a team of developers. While most of them could be working on the same operating system such as Windows, others might be using different operating systems like Mac or Linux. In this scenario, we might have a situation where a program written on Windows needs to be executed on Mac OS also.
If a program written on a particular platform can run on other platforms without any recompilation, it is known as platform independence.
Since Java is platform independent, this is not a problem. A program written using Java on Windows will execute without any recompilation on any other platform.
To see what platform independence actually is and how it matters, let us compare Java to a platform dependent language like C.
j3/
XPro Sellers
XPro Sellers is one of the leading companies in the city. The sales data of the company in the year 2016-17 is given below.
Sales in millions of dollars:
Jan
|
Feb
|
Mar
|
Apr
|
May
|
Jun
|
Jul
|
Aug
|
Sept
|
Oct
|
Nov
|
Dec
|
6
|
9
|
7
|
10
|
11
|
9
|
7
|
12
|
14
|
15
|
13
|
11
|
The finance officer of the company wants to know the monthly average sales and the number of months for which the sales are above the average sales.
Here is the Java code to solve this problem:
class AverageSales {
public static void main(String[] args) {
int sum = 0, count = 0;
int[] sales = { 6, 9, 7, 10, 11, 9, 7, 12, 14, 15, 13, 11 };
for (int index = 0; index < sales.length; index++) {
sum += sales[index];
}
float average = (float) sum / sales.length;
for (int sale : sales) {
if (sale > average) count++;
}
System.out.println("Average sale: " + average);
System.out.println("Sales above average: " + count);
}
}
Now let us go through the above code to learn the features of Java language...
Note:
Creating Variables
Where there is functionality, there is data. And for implementing the functionality, we need to store the data.
Let us go through the code and see how data is specified:
// Problem 1: Find the monthly average sales and the number of months which have sales more than the average
class AverageSales {
public static void main(String[] args) {
int sum = 0, count = 0;
int[] sales = { 6, 9, 7, 10, 11, 9, 7, 12, 14, 15, 13, 11 };
for (int index = 0; index < sales.length; index++) {
sum += sales[index];
}
float average = (float) sum / sales.length;
for (int sale : sales) {
if (sale > average) count++;
}
System.out.println("Average sale: " + average);
System.out.println("Sales above average: " + count);
}
}
In programs, data values are stored in memory locations identified by names (identifiers). Such named memory locations are called variables.
Notice how variables are declared using types, identifiers and assigned values:
j5
Keywords and Identifiers
As you have seen, a program is composed of several components, blocks, and words.
// Problem 1: Find the monthly average sales and the number of months which have sales more than the average
class AverageSales {
public static void main(String[] args) {
int sum = 0, count = 0;
int[] sales = { 6, 9, 7, 10, 11, 9, 7, 12, 14, 15, 13, 11 };
for (int index = 0; index < sales.length; index++) {
sum += sales[index];
}
float average = (float) sum / sales.length;
for (int sale : sales) {
if (sale > average) count++;
}
System.out.println("Average sale: " + average);
System.out.println("Sales above average: " + count);
}
}
Some of these words are reserved and have a special meaning in Java, e.g. class, public, void, for, int, static. These words are called keywords. There are 50 keywords in Java 8.
Apart from keywords, there are other words which are used as names to identify components in a program, e.g. HelloWorld, AverageSales, main, sum, index. These words are called Identifiers.
Identifiers are names used for identifying components of a program like classes, methods, interfaces, enums and variables. Once declared, these names can be used to identify those components later in the program
Data types in Java
While declaring variables, we have to specify the kind of data they will hold.
int sum = 0;
A data type defines the type of data a variable can hold, its memory need, and the operations that can be performed on it.
A variable can hold values only of the type specified at the time of its declaration.
Syntax: = ;
Data types are primarily of two kinds:
Note: Non-primitive types such as String, user-defined types, enum will be discussed later in the course.
For a comparison of Java and Python syntax check out the appendix.
Operators
To manipulate and to do some operations on the data, we require operators.
Highlighted below are some operators used in our code:
// Problem 1: Find the monthly average sales and the number of months which have sales more than the average
class AverageSales {
public static void main(String[] args) {
int sum = 0, count = 0;
int[] sales = { 6, 9, 7, 10, 11, 9, 7, 12, 14, 15, 13, 11 };
for (int index = 0; index < sales.length; index++) {
sum += sales[index];
}
float average = (float) sum / sales.length;
for (int sale : sales) {
if (sale > average) count++;
}
System.out.println("Average sale: " + average);
System.out.println("Sales above average: " + count);
}
}
Operators are symbols which perform specific operations on values and return a result. Java is loaded with a huge set of operators:
Arithmetic Operators (+, -, *, /, %, ++, --)
Bitwise Logical Operators (&, |, ^, ~, <<, >>, >>>)
Relational Operators (==, !=, <, <=, >, >=)
Assignment Operators (=, +=, -=, *=, /=, %=)
Short-Circuit Logical Operators (&&, ||, !)
Ternary Operator: It is a short form of if-then-else statement
Syntax: ? : ;
See more about the operators in Java
here.
The Need for Conversion
Whenever a mathematical operation is performed, the resultant type is the same as the type of the largest accommodating operand. In the below-highlighted case, if the sum which is of integer datatype is not explicitly converted to float datatype, then the result stores in average will be of integer type and the fractional part of the result will be discarded.
// Problem 1: Find the monthly average sales and the number of months which have sales more than the average
class AverageSales {
public static void main(String[] args) {
int sum = 0, count = 0;
int[] sales = { 6, 9, 7, 10, 11, 9, 7, 12, 14, 15, 13, 11 };
for (int index = 0; index < sales.length; index++) {
sum += sales[index];
}
float average = (float) sum / sales.length;
for (int sale : sales) {
if (sale > average) count++;
}
System.out.println("Average sale: " + average);
System.out.println("Sales above average: " + count);
}
}
To prevent the loss of information, any of the operand's value can be converted to a higher accommodating type. The statement above converts the value of sum to be used as a float, thereby preserving the fractional part in the result. Such a conversion is called explicit type conversions.
Explicit Conversion
Explicit conversions are generally used to prevent data loss in mathematical operations.
Also, when a value of larger data type needs to be stored as a value of smaller data type, despite the possibility of data loss, we should explicitly specify the conversion. In such a case, it is called narrowing conversion.
Example: While assigning a long variable to that of int type, we need to explicitly type cast:
long regId = 4567L;
int iRegId = (int)regId; // Explicit Type Casting
And while performing mathematical operations, we might need to explicitly type cast:
float percentage = ((float)totalMarks/800) * 100; // Explicit Type Casting
Note:
In narrowing conversions, char data type can be converted to byte or short
boolean cannot be type casted to any other data type
Implicit Conversion
Implicit conversion happens when a value of smaller data type needs to be used as a value of larger compatible data type. Also called widening conversion, it is done automatically by Java.
Example: When we assign an int variable to that of a long type:
int intValue = 1002;
long longVariable = intValue;
The conversion happens automatically without the intervention of the programmer.
Control Structures
In cases where we need to perform decision making, branching and repetition, we require control structures. control structures helps in controlling the flow of our program, usually in the form of conditional or repetitive execution.
// Problem 1: Find the monthly average sales and the number of months which have sales more than the average
class AverageSales {
public static void main(String[] args) {
int sum = 0, count = 0;
int[] sales = { 6, 9, 7, 10, 11, 9, 7, 12, 14, 15, 13, 11 };
for (int index = 0; index < sales.length; index++) {
sum += sales[index];
}
float average = (float) sum / sales.length;
for (int sale : sales) {
if (sale > average) count++;
}
System.out.println("Average sale: " + average);
System.out.println("Sales above average: " + count);
}
}
Making Decisions - if and else
if-else statements help in decision making by changing the flow of execution according to a condition.
In our solution for problem 1, if(sale > average) { } checks whether a sale is greater than the average value or not.
It follows this syntax:
if () { // Curly braces are not required if there is only one statement inside the block
;
}
The statements inside the if block execute only when the condition evaluates to true.
Here are some variations of if-else statements:
if-else | if-else ladder | Nested if |
if () {
;
}
else {
;
}
| if () {
;
}
else if () {
;
}
else {
;
}
| if () {
if () {
;
}
else {
;
}
}
|
Making Decisions - switch
A more concise way of decision making can be done using a switch block. It allows the flow of execution to be switched according to a value.
It is generally used as a replacement for if-else ladder as it improves readability
Value can be a byte, short, char, int or String
Its syntax is:
switch (variable) { // The program flow jumps to the case which matches this variable's value
case value1: ;
break; // breaks out of the switch block. Placed at the end of a case
case value2: ;
break;
default: ; // Executes when none of the cases match
}
Arrays
Arrays help us to hold more than one value at a time.
Notice the syntax for creating arrays in Java:
// Problem 1: Find the monthly average sales and the number of months which have sales more than the average
class AverageSales {
public static void main(String[] args) {
int sum = 0, count = 0;
int[] sales = { 6, 9, 7, 10, 11, 9, 7, 12, 14, 15, 13, 11 };
for (int index = 0; index < sales.length; index++) {
sum += sales[index];
}
float average = (float) sum / sales.length;
for (int sale : sales) {
if (sale > average) count++;
}
System.out.println("Average sale: " + average);
System.out.println("Sales above average: " + count);
}
}
Arrays allow us to perform operations on a set of values, e.g. marks of students, prices of products, etc. where having a variable for each value is not feasible.
An array is a collection of similar data in contiguous memory locations referred by the same name.
Can be used to store data of primitive as well as reference data type
Holds a fixed number of data, determined at the time of array declaration
It is an object in Java and is created dynamically
Working with Arrays
Here's another way of creating and initializing an array:
int[] marks = new int[3]; // Creating an array of size 3
marks[0] = 98; // Setting element at index 0
marks[1] = 86;
marks[2] = 92;
This can be visualized as:
0 1 2
| |
Note:
Array index always starts from zero
The length attribute of an array can be used to get its size
Once initialized, the size of an array cannot be changed
Multi-dimensional Arrays
We can also have multi-dimensional arrays. For example, a 2-D array where each element of the array is itself an array.
It can be used to represent tabular data.
int[][] dayWiseTemperature = new int[7][3]; // The row size is 7, and the column size (optional) is 3
// Another way of creating a 2-D array
int[][] dayWiseTemperature = new int[][] {
{30, 35, 28},
{29, 32, 0},
.
.
.
};
// To set or change individual values
dayWiseTemperature[1][2] = 28;
|
|
temp
|
|
|
0
|
1
|
2
|
d
|
0
|
98
|
86
|
92
|
a
|
1
|
29
|
32
|
28
|
y
|
..
|
|
|
|
|
6
|
|
|
|
Accessing Array Content
Traversing an array to access its elements is a frequent operation.
For this we can use loops, as in our problem 1 solution:
for (int index = 0; index < sales.length; index++) {
sum += sales[index]; // Accessing element at position index
}
Java has also introduced the enhanced for loop (for-each loop) to iterate over collections, eliminating the use of indexes:
For example, the second for loop in our problem 1 solution:
for (int sale : sales) { // For each int value sale in the sales array
if (sale > average) count++;
}
Command Line Arguments
Now that you know arrays, you will be able to notice something familiar in the code we have already seen…
public static void main(String args[]) {
// Code
}
Yes, String args[] is an array of strings. But where does it come from? …and how can we use it?
It is an array of command line arguments. Such arguments are passed as inputs to your program either through the command prompt of your system, or through the run configuration of your IDE.
Fixing Bugs
Following the requirement in Problem 1, the finance officer of the company now wants to know the average sale of the first half of 2016-17.
The development team has written the following code for this:
class AverageSales {
public static void main(String[] args) {
int sum = 0;
int[] sales = { 6, 9, 7, 10, 11, 9, 7, 12, 14, 15, 13, 11 };
for (int index = 0; index <= 6; index++) {
sum += sales[index];
}
float average = (float) sum / 6;
System.out.println("Average sale for first half of the year: " + average);
}
}
It was found that the code is not giving the expected result, which should be 8.66
Let us see how we can fix the code to get the correct result...
Debugging a Program
Whenever we don't get the desired outcome from our code, we end up spending a lot of time going through the lines of code or using print statements to find the problem.
As we all know, the process of finding and fixing bugs in a program is called debugging. And most of us have been doing it manually.
Eclipse provides a debugger to control the execution of a Java program and to view the states of the variables throughout the execution.
It has its own perspective to help in debugging
Allows using breakpoints and watchpoints, and updating variable values at runtime
Provides modes like Step Into and Step Over to have greater control over the execution
Let us see how to use the debugging tool to do more in less time.
The Debugging Toolbar
j6
It removes all terminated programs from the debug view
It resumes the execution of the program until the next breakpoint
It suspends the execution
It terminates the current program
Step Into - It executes the current line and goes to the next line in the program. If the current line is a method call, then the debugger will take you to the called method
Step Over - If the current line is a method call, then the debugger will execute the method without stepping into the called method
Use Step Filters - If selected, then debug will skip from stepping into any of the packages/ classes mentioned in the Java Debug Step Filtering preferences
j7
The E-Mart Scenario
E-Mart, a popular retail chain, has decided to build an online portal and host its products for sale.
The online system has to handle products and customers. Here is an overview of the required data and functionalities:
Products should have details like id, name, price and stock, along with functionalities to display details, calculate discounted price, etc.
Customers should have details like id, password, name, phone, and address. They should also have functionalities to display details, order products, etc.
There are two types of customers: Regular and Premium. Regular customers get some discount, while the premium customers get discount as well as membership benefits. Membership benefits currently include a standard membership card which can be used for acquiring and redeeming points while shopping at E-Mart
Customers should be informed if they try to buy a product which is not in stock
E-Mart has hired a web development team to meet their growing requirements.
Going forward we will see how the team approaches the problem, and understand their solutions to learn object oriented programming.
Note: Use Problem-02-EMart-Part-1 as the reference demo project for the upcoming topics.
Real World Objects
For the E-Mart application, we have to represent real world objects/entities such as products and customers programmatically. This will help us in interacting with them the way we do in real life.
Since Java is an object oriented language, Java programs can incorporate object oriented concepts (which we will see soon), making it very easy to represent and interact with real world objects in programs.
Also, since real world objects have properties (characteristics and behaviors), they cannot be represented using merely the primitive types. Java allows programmers to define the representations of such real world objects (user-defined types). Instances of these representations can contain all the specified properties.
A representation specifying the characteristics and behaviors of an object is called a class.
The Product Class
E Mart sells multiple products, which have details like id, name, price and stock. They might also exhibit certain behaviors to display their details, calculate discounted price, etc.
These details and behaviors can be stored together for each product, and can be represented using a class:
class Product {
private int productId;
private String name;
private float price;
private int stock;
public float getPriceAfterDiscount(int discountPercentage) {
float discountedPrice = price - (price * discountPercentage / 100);
return discountedPrice;
}
public void displayDetails() {
System.out.println("Product Id: " + productId);
System.out.println("Product Name: " + name);
System.out.println("Product Price: " + price);
System.out.println("Stock: " + stock);
}
public void displayDetails(int discountPercentage) {
System.out.println("Product Name: " + name);
System.out.println("Discounted price: " + getPriceAfterDiscount(discountPercentage));
if(stock > 0) System.out.println("In stock");
else System.out.println("Out of stock!");
}
public int getProductId() {
return productId;
}
public void setProductId(int productId) {
this.productId = productId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
public int getStock() {
return stock;
}
public void setStock(int stock) {
this.stock = stock;
}
}
Inside a Class
A class is a design or blueprint that describes the characteristics and behaviors of a real-time entity.
It starts with the keyword "class" followed by a name
It specifies attributes (characteristics) and methods (behaviors)
Attributes are the elements (instance variables) which hold the values of a particular entity.
Here, productId, name and price are the attributes
Methods are the set of instructions which define the behaviors of the entity.
Here, display() is a method
Access specifiers help in limiting access to the members of a class.
private members are accessible only inside their class, but public members are accessible in other classes as well.
More about access specifiers will be discussed later
Defining Behaviors
A method is a set of statements which depicts the behavior of a class.
A method definition looks like this: j8
The first line of the method contains the method signature, which includes the name of the method and the arguments (parameters) it takes
Input arguments of a method are optional (but not the parenthesis). The arguments specified in the method definition are called formal arguments or parameters. Here, discountPercentage is the formal argument
Variables declared inside method blocks (including arguments) are called local variables.
Here, discountPercentage and discountedPrice are local variables
Working with Methods
Methods can be called or invoked to execute its statements. Methods may also return results after execution.
The return keyword is used to return a value
It also sends back the flow of execution from a method to the calling method
If a method does not return anything, the return type should be specified as void
To execute a method, it needs to be called. A method call (invocation) statement in a program looks like this:
// Calling getPriceAfterDiscount() method in the same class
float finalPrice = getPriceAfterDiscount(12); // Here 12 is the actual argument
Here the value returned by the method will be stored in the variable finalPrice.
The arguments passed in the method call statement are called actual arguments or parameters.
Remember the main() method! ...It is the starting point for the execution of all the programs.
Working with Objects
Now that we have seen a class, let us see how we can create objects of the class and use them.
In Java, an object is created using the new keyword. An E-Mart product can be created as follows:
Product prodObj = new Product(); // Creating a product object
The new keyword is responsible for calling constructors (will be discussed soon) and having memory allocated for the object.
Once created, the object's methods can be invoked using the "." operator:
prodObj.setProductId(101); // Setting attribute values
prodObj.setName("T-Shirt");
prodObj.setPrice(799f);
String name = prodObj.getName(); // Getting attribute value
float finalPrice = prodObj.getPriceAfterDiscount(12);
An object is an instance of a class.
It allows us to use the attributes and methods specified in the class
A class can have any number of objects
An object holds data related to an instance of a class
Note:
Instance variables are automatically initialized to their default values during object creation.
For example, before setting values to the product object, productId will be 0, name will be null, price will be 0.0f
Local variables (declared in methods) have to be initialized before they can be used
Variables referring to objects are called reference variables.
In the above code, prodObj is a reference variable
Method Overloading
E-Mart wants to display product details to its employees and customers in different ways. For employees, they want to display all the details of the product including the product id, stock, name and the market price. And for customers, they want to display the product name, the discounted price of the product and the product availability.
We know a class can have multiple methods (behavior) to implement different functionalities. But what if we need the same behavior with some variation in functionality? As in displaying product details to employees and customers.
This has been achieved by having two displayDetails() methods in the Product class. How do you think this works!
class Product {
// Attributes and methods
public void displayDetails() {
System.out.println("Product Id: " + productId);
System.out.println("Product Name: " + name);
System.out.println("Product Price: " + price);
System.out.println("Stock: " + stock);
}
public void displayDetails(int discountPercentage) {
System.out.println("Product Name: " + name);
System.out.println("Product discounted price: " + getPriceAfterDiscount(discountPercentage));
if(stock > 0) System.out.println("In stock");
else System.out.println("Out of stock!");
}
}
The displayDetails() method actually has two versions. One which shows all the details for E-Mart employees, and the other which shows the name, discounted price and stock availability for the customers.
This is called method overloading. It allows having different implementations of the same behavior depending upon the arguments.
Working with Overloaded Methods
Java allows a class to have more than one method with the same name. Such methods accept parameters differing in their data types, the number of parameters, or their order.
Overloaded methods are called just the usual way, where the version which matches with the arguments is invoked:
product.displayDetails(); // Displaying all the product details for the employees
product.displayDetails(5); // Displaying the discounted product details for customers
Which displayDetails() method do you think will be invoked?
It is possible to resolve calls to overloaded methods based on the method signature. Hence, this is done during compile time.
Creating Products with Values
We have already seen how an E-Mart product object is created with default values. And after its creation, values have to be set to each of its attributes.
Product prodObj = new Product(); // Creating product object
prodObj.setName("Jeans"); // Setting value to attribute
What if we need to set the attributes of a product at the time of its creation!
Did you notice Product() in the above code? Doesn't it look like a method?
It is a special method which is responsible for creating and initializing objects. It is called a constructor.
The constructor above is a default constructor provided by the compiler, and initializes all the attributes to their default values.
It is important to know that constructors can be customized to initialize the attributes of an object with specific values. They can also be used to perform activities when an object is created.
Here's our Product class with a user-defined constructor:
class Product {
private int productId;
private String name;
private float price;
private int stock;
public Product(int id, String name, float price, int stock) {
this.productId = id;
this.name = name;
this.price = price;
this.stock = stock;
System.out.println("Product created!");
}
// Other methods
}
Now a product object can be created by passing its values to the constructor:
Product prodObj = new Product(101, "T-Shirt", 799, 25);
Constructors
A constructor is a special method which has the same name as the class and no return type. It is used for creating objects and initializing them with values.
There are two types of constructors:
A class can have multiple constructors to initialize different number of fields. This is called constructor overloading.
Product {
// Attributes and methods
Product() { }
Product(int id, String name) { }
Product(int id, String name, float price, int stock) { }
}
Current Object
When we create objects, we use reference variables to access the public methods of the objects. And these public methods provide access to the private members of the object.
Product p1 = new Product();
p1.setProductId(101);
In the above code we are using the setProductId() method to access the private attribute productId of product p1.
If we look into the class, setProductId() itself accesses the instance variable productId to set its value.
public void setProductId(int pId) {
productId = pId; // Assigning argument to instance variable
}
Obviously for this, setProductId() needs to refer to the object whose productId has to be set.
If there are multiple objects (p1, p2, p3, ...), which object do you think will the above method set the productId for?
(We know objects of a class have their own memory and are independent of each other)
Methods such as above, and even constructors, automatically refer to the object which invokes them. Such an object becomes the current object in the class for that invocation.
In other words, the object which causes the invocation of the constructors and methods of its class, becomes the current object.
So if p1 invokes setProductId(), p1 becomes the current object in the class. If p2 invokes setProductId(), p2 becomes the current object in the class, and so on.
Now what if the parameter has the name as the instance variable?
public void setProductId(int productId) {
productId = productId; // What happens here?
}
The this Keyword
When a method has a local variable with the same name as an instance variable, the local variable gets higher preference inside that method. This is called shadowing of a field. To prevent this, we need to explicitly specify the object the instance variable belongs to.
...But which object?
The current object!
And how do we get the reference of the current object?
The this keyword!
public void setProductId(int productId) {
this.productId = productId; // Assigning argument to instance variable
}
The this keyword is a reference to the current object, the object which invokes the method.
It helps in explicitly accessing the member of an object inside its class.
this keyword is also used for invoking one constructor from another constructor. This helps in reusing constructor logic.
class Product {
// Fields and methods
public Product(int productId, String name) {
this.productId = productId;
this.name = name;
}
public Product(int productId, String name, float price, int stock) {
this(productId, name); // Invoking overloaded constructor
this.price = price;
this.stock = stock;
}
}
Note: Constructor invocation using this keyword should be the first statement inside the constructor.
Interface of an Object
We know that every object has attributes and methods. The values of attributes determine the state of an object at any point in time.
In real life, state of an object cannot be accessed or modified directly, but only through a behavior. Such a behavior forms the interface of the object with the outside world. For example, speed of a car (constitutes state) can be increased by pressing the accelerator (behavior).
Having such an interface not only hides the inner complexities, but can also put constraints to the permitted values (speed of a car cannot be increased indefinitely).
Here's an example of a programmatic representation of such an interface:
class Car {
private float speed; // private prevents direct access
public void accelerate(float speedToAdd) { // Method to increase speed
this.speed += speedToAdd;
if(this.speed > 120) this.speed = 120; // Limiting speed to 120 kmph
}
// Other fields and methods
}
According to the above code, the only way to increase the speed (attribute) is by using the accelerate() method (behavior).
Hence, accelerate() contributes as an interface of a car with the outside world.
This brings us to two important and related object oriented concepts - Encapsulation and Abstraction
Object Oriented Concept - Encapsulation
We now understand that state (determined by attributes) should not be modified directly. Instead, a class should provide methods as an interface to access its attributes.
Notice that this is followed in the Product class as well:
class Product {
private int productId;
private String name;
private float price;
private int stock;
public int getProductId() { // Getter method for productId
return productId;
}
public void setProductId(int productId) { // Setter method for productId
this.productId = productId;
}
// Other methods
}
This is called encapsulation, the bundling of data and related methods into a single unit.
This allows restricting access to the members.
This allows hiding internal details to prevent direct access. They can be accessible only through public methods.
Public methods used to read and write values to private variables are called getters (accessors) and setters (mutators) respectively.
Encapsulation helps in representing components together as a single unit, while restricting access to the complexities inside.
e.g. All the internal body parts are encapsulated inside the human body.
Object Oriented Concept - Abstraction
The interface of an object which hides the complexities, also shows a layer of communication using which a user interacts with the object.
Notice how methods are invoked and used without knowing their details:
public class ProductTester {
public static void main(String[] args) {
Product prodObj = new Product("T-Shirt", 799);
float finalPrice = prodObj.getPriceAfterDiscount(15);
System.out.println("Final price of " + prodObj.getName() + " is " + finalPrice);
}
}
This is called abstraction, the process of exposing the relevant details.
Behaviors can be invoked without knowing how they work
What to invoke is relevant, but how it works is not
This helps in knowing how to interact with things with minimum information about how they work.
For example, driving a car doesn't require knowing how a car works internally.
Here the abstraction the car provides is through its dashboard.
If the car runs on autopilot, you don't even need to know how to drive.
More abstraction!
Higher the abstraction, easier it is to operate.
Generating Ids
We have already seen that product ids of all the products are manually set after their creation.
Product p1 = new Product();
p1.setProductId(1001);
As E-Mart has a large number of products, it would be difficult to manually provide a product id to every product object created.
To overcome this, they want the ids to be generated automatically when a product is created.
For this, the autogeneration logic has to be placed inside the constructor of the Product class itself.
Since ids have to be unique for every product, we need to maintain a variable which can be shared and manipulated across all the Product objects.
The static keyword is used to create such a shared member of a class.
Here is a modified Product class which can automatically generate product ids:
class Product {
private int productId;
private String name;
private float price;
private int stock;
private static int idCounter = 100;
public Product(String name, float price, int stock) {
this.productId = ++idCounter;
this.name = name;
this.price = price;
this.stock = stock;
}
// Other methods
}
The above code increments the static idCounter variable to generate a unique id for every product. Being static, idCounter maintains a common value across all the Product objects.
The static Keyword
he static keyword is used to make a member belong to a class, and not to any of its individual objects. Only one copy of the member is maintained across all the instances.
Static variables are class level variables which are used to keep a value across all the instances of a class. They are initialized when the class gets loaded (e.g. idCounter).
Here's a visualization of how having a static variable helps in maintaining a value across all the objects of a class: j9
Apart from static variables, we can also have static methods and blocks.
Static Methods and Blocks
Static methods are class level methods. They can be used without any instance of the class, and are invoked using the class name. They can be called using a reference of the class as well.
Assuming there is a static method to get the current value of idCounter, it can be called like this:
Product.getLastId(); // Calling a static method
Product p1;
p1.getLastId(); // Calling static method using reference
Note: 'main()' is a static method so that JVM can call it without creating an object.
Static blocks are used to initialize static variables when it cannot be done in a single line. They can also be used to add preprocessing if required.
Static blocks get executed once when a class gets loaded. If there are multiple static blocks, they will be executed in the order of their occurrence.
Our product id counter can be initialized like this as well:
private static int idCounter;
static { // Static block
int randomNumber = 31;
idCounter = (int) Math.pow(randomNumber, 3); // Initializing counter
}
Note: Whenever a class is used for the first time, it is loaded into memory, along with all its static members.
The static Context
We know objects can access the static members of their class. But do you think a static method can access the members of an object?
public class Product {
private int productId;
private String name;
private float price;
private int stock;
private static int idCounter = 1000;
public static void generateProductId() {
this.productId = ++idCounter; // Which object's productId!?
}
}
The static method doesn't belong to any object, so it cannot know which object's member we are trying to access. Trying to do so will result in a compilation error.
Hence, a static context, i.e. static blocks and static methods, cannot access non-static (instance) members directly.
However, non-static methods can access static members.
- Static variables and methods can be accessed without creating an object of a class
- Static blocks execute only once
Product Class - A summary of OOP Basics
We have covered the basics of OOP while discussing E-Mart's Product class. Before we dive into the advanced OOP concepts, here is the complete Product class for a quick look:
class Product {
private int productId;
private String name;
private float price;
private int stock;
private static int idCounter = 100;
public Product(String name, float price, int stock) {
this.productId = ++idCounter;
this.name = name;
this.price = price;
this.stock = stock;
}
public float getPriceAfterDiscount(int discountPercentage) {
float discountedPrice = price - (price * discountPercentage / 100);
return discountedPrice;
}
public void displayDetails() {
System.out.println("Product Id: " + productId);
System.out.println("Product Name: " + name);
System.out.println("Product Price: " + price);
System.out.println("Stock: " + stock);
}
public void displayDetails(int discountPercentage) {
System.out.println("Product Name: " + name);
System.out.println("Discounted price: " + getPriceAfterDiscount(discountPercentage));
if(stock > 0) System.out.println("In stock");
else System.out.println("Out of stock!");
}
public int getProductId() {
return productId;
}
public void setProductId(int productId) {
this.productId = productId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
public int getStock() {
return stock;
}
public void setStock(int stock) {
this.stock = stock;
}
}
Representing Customers
We now know how to represent real world objects in our program, and through the Product class we have seen how such a representation helps in interacting with the objects. Yet, there are more concepts that can not only help us in representing the relationships between objects, but also in creating a better interface between objects and the outside world.
We will learn these by representing the customers of E-Mart.
E-Mart has a large customer base. As mentioned earlier, there are only two types of customers: Regular and Premium.
Regular customers should get a discount of 5%, and premium customers should get a discount of 10% in the final bill. Moreover, premium customers should be given a standard membership card which can be used for acquiring and redeeming points during shopping at E-Mart.
Both types of customers should have details like customer id, password, name, phone, and address. They should also have methods to display relevant details, order products, etc.
The above requirements have been fulfilled through various classes which are as follows:
- Both the customer types have some common details, and these can be represented by a common Customer class:
abstract class Customer {
private int customerId;
private String password;
private String name;
private String phone;
private Address address;
public float orderProducts(Product[] products) {
BillCalculator billCalc = new BillCalculator();
billCalc.processOrder(products);
return billCalc.getAmount();
}
public abstract void displayDetails();
}
Representing Customers - Address, Bill and Membership
class Address {
private String street;
private String city;
private int zipCode;
public Address(String street, String city, int zipCode) {
this.street = street;
this.city = city;
this.zipCode = zipCode;
}
}
public class BillCalculator {
private int billId;
private float amount;
public void processOrder(Product[] products) {
for (int i = 0; i < products.length; i++) {
if (products[i].getStock() <= 0) {
System.out.println(products[i].getName() + " out of stock"); break;
}
else {
this.amount += products[i].getPrice();
products[i].setStock(products[i].getStock() - 1);
}
}
}
}
interface MembershipCardHolder {
int DEFAULT_POINTS = 100;
float redeemPoints(int pointsToRedeem);
void addPoints(float money);
}
Representing Customers - Regular and Privileged
final class RegularCustomer extends Customer {
public RegularCustomer(int id, String name, String password, String phone, Address address) {
this.setCustomerId(id);
this.setName(name);
this.setPassword(password);
this.setPhone(phone);
this.setAddress(address);
}
@Override
public void displayDetails() {
System.out.println("Customer Id: " + this.getCustomerId());
System.out.println("Customer Name: " + this.getName());
System.out.println("Customer phone: " + this.getPhone());
}
@Override
public float orderProducts(Product[] products) {
float billAmount = super.orderProducts(products);
return billAmount * 0.95f;
}
}
final class PremiumCustomer extends Customer implements MembershipCardHolder {
private int points = DEFAULT_POINTS;
public PremiumCustomer(int id, String name, String password, String phone, Address address) {
this.setCustomerId(id);
this.setName(name);
this.setPassword(password);
this.setPhone(phone);
this.setAddress(address);
}
@Override
public void displayDetails() {
System.out.println("Customer Id: " + this.getCustomerId());
System.out.println("Customer Name: " + this.getName());
System.out.println("Customer phone: " + this.getPhone());
System.out.println("Points remaining: " + this.points);
}
@Override
public float redeemPoints(int pointsToRedeem) {
pointsToRedeem = pointsToRedeem > this.points ? this.points : pointsToRedeem;
float amountToRedeem = pointsToRedeem * 0.5f;
this.points = this.points - pointsToRedeem;
return amountToRedeem;
}
@Override
public void addPoints(float money) {
this.points += (int) money / 100;
}
@Override
public float orderProducts(Product[] products) {
float billAmount = super.orderProducts(products);
float finalAmount = billAmount * 0.90f;
addPoints(finalAmount);
return finalAmount;
}
public float orderProducts(Product[] products, int pointsToRedeem) {
float billAmount = super.orderProducts(products);
float finalAmount = billAmount * 0.90f;
if(finalAmount != 0)
finalAmount -= redeemPoints(pointsToRedeem);
addPoints(finalAmount);
return finalAmount;
}
}
Relationship - Inheritance
You have just seen 6 different classes to implement the requirements of E-Mart. Now we will see how these classes are related to each other.
Have you noticed that although regular and premium customers are different, both of them are based on some common properties of customers?
For example, customerId, password, name, phone, address, orderProducts(), displayDetails(), etc.
Hence a common Customer class has been defined, and the RegularCustomer and PremiumCustomer classes are special cases of it.
When an object is based on another object, i.e. acquires characteristics and behaviors from another object, the relationship is that of inheritance.
Inheritance or "is-a" relationship exhibits the concept of reusability through a valid relationship between any two real world objects.
Example: A premium customer is a customer, a project manager is an employee, a car is an automobile
In Java, a class inherits from another class using the extends keyword:
class Customer {
// Parent/Super/Base class
}
class RegularCustomer extends Customer { // RegularCustomer is a Customer
// Child/Sub/Derived class
}
class PremiumCustomer extends Customer { // PremiumCustomer is a Customer
// Child/Sub/Derived class
}
Generalization (is-a) is denoted by a link with an arrow head:
By extending the Customer class, the specialized classes RegularCustomer and PremiumCustomer can inherit the common features
Constructor Call in Inheritance - Super Constructors
The parent constructors in an inheritance hierarchy are automatically invoked because of the implicit usage of the super keyword. The super keyword is used to refer to the parent class.
This is what happens (can be done explicitly as well):
class RegularCustomer extends Customer {
public RegularCustomer() {
super(); // Invoking the parent class constructor
System.out.println("It is a regular customer!");
}
}
If we want to invoke a parameterized constructor of the parent class, we need to pass the required parameters in the super keyword.
Here's an example involving the employees of E-Mart:
class EMartEmployee {
// Instance variables and methods
public EMartEmployee(String name, float salary) { // Parent class constructor
this.name = name;
this.salary = salary;
}
}
class EMartManager extends EMartEmployee {
private float bonus;
public EMartManager(String name, float salary, float bonus) { // Child class constructor
super(name, salary);
this.bonus = bonus;
}
}
If the parent class has only a parameterized constructor, the child must call it using super.
Also, the call to a super constructor has to be the first statement inside a constructor.
We will look at more uses of the super keyword later.
Relationship - Aggregation
We know, Customer class has several attributes including address.
class Customer {
private int customerId;
private String password;
private String name;
private String phone;
private Address address;
// Methods
}
We can see that address is an object of Address class.
When an object contains another object as its attribute, we have an aggregation between them. It is termed as "has-a" relationship.
Example: A car has an engine, smartphone has a processor
Aggregation (has-a) is denoted by a link with a diamond head:
Relationship - Association
We know that the E-Mart Customer class has an orderProducts() method. It allows customers to order products, and processes them.
How does the orderProduct() method achieve this?
class Customer {
// Instance variables
// Methods
public float orderProducts(Product[] products) {
BillCalculator billCalc = new BillCalculator();
billCalc.processOrder(products);
return billCalc.getAmount();
}
}
Notice that, products and bill calculator are used in the orderProducts() method to process the orders.
When an object utilizes another object in order to perform its activities, a relationship of association is established between them. This is termed as "uses-a" relationship.
Example: A customer uses products and bill calculator to process orders, a driver uses a car to travel
Association (uses-a) is denoted by a line:
Since regular and premium customers have commonalities as well as differences in their requirements for ordering products, they need to re-implement the orderProducts() methods according to their requirements.
For this, we have different implementations of orderProducts() in the Customer, RegularCustomer and PremiumCustomer classes.
class Customer {
// Methods and fields
public float orderProducts(Product[] products) {
BillCalculator billCalc = new BillCalculator();
billCalc.processOrder(products);
return billCalc.getAmount();
}
}
class RegularCustomer extends Customer {
// Methods
@Override
public float orderProducts(Product[] products) {
float billAmount = super.orderProducts(products);
return billAmount * 0.95f; // 5% discount
}
}
| class PremiumCustomer extends Customer {
// Methods
@Override
public float orderProducts(Product[] products) {
float billAmount = super.orderProducts(products);
float finalAmount = billAmount * 0.90f; // 10% discount
addPoints(finalAmount);
return finalAmount;
}
}
|
We can see that although RegularCustomer and PremiumCustomer inherit the orderProducts() method from Customer, they define it again with a modified implementation.
This is called method overriding.
Method Overriding
Method overriding lets you redefine a method in the child class which is already present in the parent class.
A few things to note while overriding methods:
When we override a method in the child class, it should have the same signature as that of the parent class
It should not have a weaker access privilege (will be discussed soon)
Private methods are not inherited, hence not overridden
Here's what happens when we call overridden methods:
Customer customer = new Customer();
customer.orderProducts(products); // calls orderProducts() of the parent class
RegularCustomer customer = new RegularCustomer();
customer.orderProducts(products); // calls orderProducts() of the child class
But what happens here?
Customer customer = new RegularCustomer();
customer.orderProducts(products);
Note: When a base class is specialized into child classes, instantiating the base class is not a good practice.
Dynamic Binding
A parent class reference can refer to a child class object, i.e. child objects can substitute parent objects (Liskov substitution principle).
Customer customer = new RegularCustomer();
customer.orderProducts(products);
The version of the method that will be called is determined by the object.
This allows making generic methods as follows:
public static void showCustomerDetails(Customer customer) {
customer.displayDetails();
}
The method accepting Customer type allows working with objects of Customer as well as its children:
showCustomerDetails(new RegularCustomer(101, "Tony", "tony123", "9876543210", new Address("1 Hebbal", "Mysore", 570027));
showCustomerDetails(new PremiumCustomer(102, "Banner", "banner123", "9876543211", new Address("2 Hebbal", "Mysore", 570027));
It's easy to understand that the details displayed will depend on the argument passed, and this decision can be taken only at runtime. This is called Dynamic binding.
Note:
Besides the inherited methods, only the overridden methods can be called using the parent class reference. Any new method created in the child class will not be accessible
Static methods are not overridden. They will be called based on the type of reference used
Polymorphism
We have just seen that we can have methods as follows:
public static void showCustomerDetails(Customer customer) {
customer.displayDetails();
}
And dynamic binding allows this method to work with any object of type Customer or its child.
This is required because until runtime, we cannot know what kind of customer will come.
If the customer is a regular customer, the customer in our method behaves like a RegularCustomer. If the customer is a premium customer, it behaves like a PremiumCustomer. Since customer can behave like multiple objects, it is said to exhibit polymorphism.
And since this can happen only at runtime, it is called dynamic polymorphism.
Remember overloading?
It allows multiple methods to have the same name. It appears as if the same method behaves differently in different situations. This is also polymorphism.
Since calls to overloaded methods can be resolved based on the method signatures, the binding happens at compile time itself. And hence called static polymorphism.
Annotations
Did you notice something over the overridden methods?
class RegularCustomer extends Customer {
// Methods
@Override
public float orderProducts(Product[] products) {
// Implementation
}
}
| class PremiumCustomer extends Customer {
// Methods
@Override
public float orderProducts(Product[] products) {
// Implementation
}
}
|
This is called an annotation. It is like a instruction for the compiler or JVM, and provides additional meaning to the code.
The @Override annotation is used with methods, and ensures that the method is actually overriding a parent method. If it is used on a method which does not override any parent method, a compilation error occurs.
An annotation is a meta-data that provides data about the program and is not part of the program itself.
Its purpose is to give additional information to the compiler, or for some processing during compile-time or runtime
It can be added to classes, methods, variables, parameters and packages
A few other built-in annotations:
@Deprecated denotes a method as obsolete. A warning occurs when such a method is used
@SuppressWarnings(value="unused") prevents warnings related to unused local variables and unused private methods. Here value is an element
Restriction on Access Privileges
We know that there is an access restriction while overriding methods - An overridden method cannot have an access privilege weaker than that of the parent method.
What kinds of access privileges are there?
To understand this, we need to understand the purpose of packages first.
As you would have noticed, all the classes in our projects are in some package/s.
Packages help in organizing our Java files. In order to follow a modular approach, a set of related classes and interfaces can be grouped together inside a package. This also helps in controlling access and avoiding name conflicts among them.
The package keyword is used to specify the package of a class. It should be the first line of a java file. To use a class from another package, the import keyword is used.
package com.emart;
abstract class Customer {
// Methods and fields
}
| package com.emart.tester;com.emart.Customer;
class Tester {
public void showCustomer(Customer customer) {
customer.displayDetails();
}
}
|
Now let us see at what levels can the access be limited...
Note:
Access Specifiers - Restricting Access
So far we have been using "public" and "private" members in our code to provide different levels of access.
These keywords are called access specifiers. They are used to specify access levels to control the visibility of a class and its members. This facilitates encapsulation. There are 4 such access levels in Java:
Note: A class can have only public or default access.
Using common functionalities in child classes
We know RegularCustomer and PremiumCustomer classes override the orderProducts() method of the Customer class because they have their own additional requirements. But what about the common functionalities in the parent method?
The functionalities of the parent method can be reused using the super keyword. (Remember using super with constructors?)
Have a look at the highlighted code where the child classes use super to call the parent's method:
class Customer {
// Instance variables
// Methods
public float orderProducts(Product[] products) {
BillCalculator billCalc = new BillCalculator();
billCalc.processOrder(products);
return billCalc.getAmount();
}
}
class RegularCustomer extends Customer {
// Methods
@Override
public float orderProducts(Product[] products) {
float billAmount = super.orderProducts(products);
return billAmount * 0.95f; // 5% discount
}
}
| class PremiumCustomer extends Customer {
// Methods
@Override
public float orderProducts(Product[] products) {
float billAmount = super.orderProducts(products);
float finalAmount = billAmount * 0.90f; // 10% discount
addPoints(finalAmount);
return finalAmount;
}
}
|
The orderProducts() method in the parent class calculates and returns the total bill amount. Since the overridden methods in the child classes need to get the total bill amount before performing their own operations, they first call the parent's method.
More uses of super
Actually, the super keyword can be used to refer to any non-private members of a parent in a hierarchy. This facilitates reusability of existing functionalities.
Apart from accessing the parent class constructors and methods which have been overridden in the child class, super keyword can also be used to access instance variables (non-private) of the parent class when both the parent and child classes have variables with the same name.
Here's an example:
class EMartEmployee {
protected float salary;
// Other fields and methods
}
class EMartManager extends EMartEmployee {
private float salary;
private float bonus;
public void calculateSalary() {
this.salary = super.salary + bonus;
}
}
Restricting Inheritance
E-Mart has confirmed that the RegularCustomer and PremiumCustomer classes are complete in their implementation, and no further specialization is required.
To enforce this, their extension can be programmatically restricted using the final keyword:
final class RegularCustomer extends Customer {
// Methods and fields
}
| final class PremiumCustomer extends Customer {
// Methods and fields
}
|
The above classes are now final, i.e. they cannot be specialized anymore.
This tells us that RegularCustomer and PremiumCustomer classes are complete and cannot be extended further.
The final keyword is generally used with components which should never change, i.e. remain constant.
It is used to restrict modification of variables, overriding methods and inheriting classes.
Some examples of final classes from the Java library are String and Wrapper classes. We will look into their details later.
Uses of final
The final keyword can be used with variables, classes and methods:j10
Enforcing Specialization
As mentioned earlier, the two E-Mart customer types should have a method to display their relevant details. Since the implementation of such method depends on the type of customer, the method cannot be implemented in the parent Customer class. It has to be defined in the child classes.
What we want is to make sure that the child classes implement such method.
This can be enforced using the abstract keyword.
Look at the code below:
abstract class Customer {
// Methods and fields
public abstract void displayDetails();
}
The method displayDetails() declared above does not have any implementation. This is because the implementation depends on whether the customer is regular or premium. Hence, we need the child classes to override and define the method. To enforce this, the abstract keyword is used with the method.
Since the above method is abstract, the class itself becomes incomplete. And there is a need for extending this class to complete it. To enforce this, the abstract keyword is used with the class.
Any class extending the abstract class must provide the missing implementation to complete it:
final class RegularCustomer extends Customer {
@Override
public void displayDetails() { // Overriding the abstract method of parent class
// Implementation
}
// Other methods
}
Abstract Classes and Methods
The abstract keyword signifies that something is not complete. It can be used with classes and methods.
We use the abstract keyword when:
An abstract class is a class which is incomplete and cannot be instantiated. To be used, it has to be made complete by extending it.
It is generally used when we want to have some behavior but are not sure how exactly it should be implemented
An abstract class encapsulates the common behaviors of all its subclasses with the help of abstract methods
Concrete (non-abstract) subclasses which extend an abstract class must implement all the abstract methods. Otherwise, they should be made abstract as well.
abstract class Customer { }
An abstract method is a method without any definition, i.e. the body.
The signature of an abstract method must be preceded by the abstract keyword
If a class contains at least one abstract method, the class should be abstract
A class can be abstract even without any abstract methods.
abstract class Customer {
public abstract void displayDetails(); // Notice the semicolon in the method declaration
}
Abstract and Dynamic Binding
We know a parent class reference can refer to a child class object. So if a reference belongs to an abstract class, we can be sure that the object it refers to will always be of its child type.
Here's a familiar code showing how objects of child classes are being passed to a method accepting a parent type argument:
public class Tester {
public static void main(String[] args) {
RegularCustomer regCust = new RegularCustomer(101, "Tony", "tony123", "9876543210", new Address("1 Hebbal", "Mysore", 570027));
PremiumCustomer premCust = new PremiumCustomer(102, "Banner", "banner123", "9876543211", new Address("2 Hebbal", "Mysore", 570027));
showCustomerDetails(regCust);
showCustomerDetails(premCust);
}
public static void showCustomerDetails(Customer customer) { // Customer is abstract
customer.displayDetails();
}
}
Abstract classes enforce inheritance
and
Abstract methods enforce overriding
And we get another flavour of dynamic binding
Enforcing Contracts
As mentioned before, E-Mart premium customers are given a membership card using which they can redeem points.
Such membership cards can be provided by several businesses to their customers, and not just E-Mart. This calls for setting a contract which can be followed by all the businesses who want to provide membership cards.
Have a look at the code below:
interface MembershipCardHolder {
int DEFAULT_POINTS = 100;
float redeemPoints(int pointsToRedeem);
void addPoints(float money);
}
This is an interface. As you can see, it specifies the behaviors by declaring methods, but doesn't provide the implementations.
Using interfaces we specify what is to be done, without telling how.
The how is then implemented by the classes which follow the specifications:
final class PremiumCustomer extends Customer implements MembershipCardHolder {
private int points = DEFAULT_POINTS;
// Overriding methods in the interface
@Override
public float redeemPoints(int pointsToRedeem) {
pointsToRedeem = pointsToRedeem > this.points ? this.points : pointsToRedeem;
float amountToRedeem = pointsToRedeem * 0.5f; // Point value is Rs. 0.5
this.points = this.points - pointsToRedeem;
return amountToRedeem;
}
@Override
public void addPoints(float money) {
this.points += (int) money / 100; // Point cost is Rs. 100
}
// Other methods
}
The PremiumCustomer class which implements the MembershipCardHolder interface has to override and provide the implementations of the methods from the interface.
Thus, interfaces are used to enforce standard rules or contracts.
Working with Interfaces
An interface is used to define a generic template which can be implemented by various classes.
It contains method signatures and constant declarations. Since Java 8, it can also have default and static methods
The methods declared in an interface are implicitly public and abstract, and the data fields are implicitly public, static and final, i.e. constants
An interface can extend more than one interface, and a class can implement more than one interface. This can be used to simulate multiple inheritance in Java
A class can extend from another class and at the same time implement any number of interfaces
The implements keyword is used to implement an interface. The classes implementing an interface must implement all the specified methods. Otherwise, they should be made abstract
An interface creates a type. Hence, its reference can be used to refer to the objects of the classes which implement that interface. This leads to dynamic binding.
public class Tester {
public static void main(String[] args) {
MembershipCardHolder cardHolder = new PremiumCustomer(102, "Banner", "banner123", "9876543211", new Address("2 Hebbal", "Mysore", 570027));
System.out.println("Discount from 100 points = Rs. " + cardHolder.redeemPoints(100));
}
}
Utility Methods in Interfaces
Our PremiumCustomer implementation of the MembershipCardHolder interface includes some point-money conversions:
@Override
public float redeemPoints(int pointsToRedeem) {
pointsToRedeem = pointsToRedeem > this.points ? this.points : pointsToRedeem;
float amountToRedeem = pointsToRedeem * 0.5f; // Converting points to money, where each point value is 0.5 rupees
this.points = this.points - pointsToRedeem;
return amountToRedeem;
}
@Override
public void addPoints(float money) {
this.points += (int) money / 100; // Converting money to points, where 100 rupees equals 1 point
}
These conversions are universal for all such membership cards, and would be the same for all the implementations of the interface. Hence, these can be put separately as static utility methods.
Since Java 8, interfaces can have static methods. This allows to easily organize the interface related helper and utility methods in the interface itself.
So moving our point-money conversion formula as static methods would make our interface look like this:
interface MembershipCardHolder {
int DEFAULT_POINTS = 100;
float redeemPoints(int pointsToRedeem);
void addPoints(float money);
static float getMoneyFromPoints(int points) {
return points * 0.5f; // Converting points to money
}
static int getPointsFromMoney(float money) {
return (int) money / 100; // Converting money to points
}
}
Now all the implementing classes can use these methods. They can be invoked using the interface name.
Also, if any change is required in the formula, only the interface needs to be modified.
A New Specification in Interfaces
It has been announced that membership card holders of all business organizations are to avail points when they purchase fuel for their vehicles. It has been specified that 50% of the expenditure at any fuel station is eligible to generate points.
This is a new feature specification that needs to be added to the interface:
interface MembershipCardHolder {
// Method declarations, static methods and constants
void addPointsFromFuelPurchase(float fuelPrice);
}
Since this is an abstract method declaration, all the implementing classes will now have to override and define this behavior. Hence, adding such a declaration in the interface breaks the code of the classes.
Since Java 8, interfaces can have methods with default implementations. This can be used to introduce new functionalities into interfaces without breaking the existing classes which implement those interfaces. The default methods provide their own definitions, which the implementing classes need not override.
So we can make addPointsFromFuelPurchase() a default method with an implementation that all classes can use, or override if they need to:
interface MembershipCardHolder {
// Method declarations, static methods and constants
default void addPointsFromFuelPurchase(float fuelPrice) {
this.addPoints(fuelPrice * 0.5f);
}
}
Now this will provide the new feature without breaking any implementing classes.
Note: If a class implements multiple interfaces having the same default methods, it has to override them.
From the overridden methods, the default methods of a specified interface can be called using the super keyword.
E.g. MembershipCardHolder.super.addPointsFromFuelPurchase(500);
An Alternative - Variable Arguments
We know the orderProducts() method in the Customer class accepts an array of Product objects:
abstract class Customer {
// Fields and methods
public float orderProducts(Product[] products) {
// Statements
}
}
This allows customers to buy multiple products (arguments) at a time.
In such cases, where variable number of arguments need to be passed as an array, Java provides an alternate syntax:
public float orderProducts(Product... products) {
// Statements
}
This is called varargs. It is a construct that allows methods to accept zero or more arguments.
It internally uses arrays to process the variable number of arguments
It is better than overloading as it can take zero or 'n' number of arguments
It is convenient to use as we don't have to create arrays of arguments to pass to the methods
public class VarargDemo {
public static void main(String[] args) {
Product halfPant = new Product("Half Pant", 299, 3);
Product trousers = new Product("Trousers", 599, 2);
Customer regCust = new RegularCustomer(101, "Tony", "tony123", "9876543210", new Address("1 Hebbal", "Mysore", 570027));
float totalAmount = regCust.orderProducts(halfPant, trousers); // Passing variable arguments; Creating arrays to pass is not required
}
}
Although varargs makes things easier, there are some limitations on its usage:
Object Oriented Concepts - A Revisit
Java being an object oriented language incorporates object oriented paradigms.
Throughout problem 3 we have come across the following 4 primary object oriented concepts:
Abstraction - the process of exposing the relevant details
e.g. The dashboard of a car is exposed to the drive
Encapsulation - the process of restricting access to the members
e.g. The internals of a switchboard are hidden from the user
Inheritance - a technique to create specialized classifications from a general one
e.g. Homo sapians are animals
Polymorphism - an object's ability to behave differently depending upon the context
e.g. A phone can behave like a camera, video player, gaming console, etc.
Java Objects - Under the hood
Having learnt the OOP concepts, we now understand how they bring power and beauty to the Java language.
But have you wondered how Java manages object creation in the memory?
For simplicity, consider this Customer class:
public class Customer {
private int customerId;
private long phoneNumber;
public Customer (int customerId, long phoneNumber){
this.customerId = customerId;
this.phoneNumber = phoneNumber;
}
// Methods
}
What do you think will happen when the following statement gets executed:
Customer cust1 = new Customer(31, 9663472291L);
Yes, a Customer object referenced by 'cust1' is created in the memory.
But how does it happen?
Stack and Heap
Java has its memory logically divided into two primary sections - Stack and Heap.
Let us understand the procedure for allocating memory when a new object is created as below:
Customer cust1 = new Customer(31, 9663472291L);
Step 1: The reference variable is created in the stack
Customer cust1 = new Customer(31, 9663472291L);
Step 2: The object is created in the heap
Customer cust1 = new Customer(31, 9663472291L);
Step 3: The reference variable in the stack refers to the object in the heap
Customer cust1 = new Customer(31, 9663472291L);
Garbage Collection
Whenever there is an allocation, there must be a mechanism for deallocation too.
Sometimes, even though a resource in a program is unreachable or not in use, the memory used by that resource is not freed. This is called Memory leak and is undesirable.
Earlier languages like C/C++ made the programmer responsible for freeing the memory occupied by such resources. Java, on the other hand, has a garbage collector which automatically deallocates the memory used by these resources. This prevents memory leak.
When an object does not have any reference, it becomes eligible for garbage collection.
Garbage Collection - Scenario 1
When the reference is set to null
public class Tester {
public static void main(String[] args) {
Customer cust1 = new Customer(31, 9663472291L);
cust1 = null;
}
}
Garbage Collection - Scenario 2
When the reference is set to a new object such that there is no reference to the previous object
public class Tester {
public static void main(String[] args) {
Customer cust1 = new Customer(31, 9663472291L);
cust1 = new Customer(32, 8978384739L);
}
}
Garbage Collection - Scenario 3
When a reference is local to some method then it will be removed from the stack as soon as the method has finished executing. If this reference was referring to an object, that object will become eligible for garbage collection.
public class Tester {
public static void main(String[] args) {
Customer cust1 = new Customer(31, 9663472291L);
}
}
Code Analysis
We know E-Mart application has customers and various products. And we have learnt how their requirements have been implemented.
Now E-Mart has put requirements for its employees.
One of the developers has created the following class to represent an employee:
public class employee {
private String Name;
public int EmpId;
public String city;
employee() {
this.city="Mysore";
}
employee(int empId) {
this.EmpId=empId;
}
public String getname() {
System.out.println("Name: " + this.Name);
return Name;
}
public void setname(String name) {
this.Name = name;
}
}
The solution was sent for review and was rejected since some coding standards were not followed.
Introducing PMD
Following coding conventions helps in writing efficient and maintainable code.
The given code violates some coding standards. Finding violations in code manually is a lot of work. Source code analyzing tools like PMD help in locating these violations.
PMD is a source code analyzer, which checks code against a predefined set of rules.
It detects inefficient code like unused variables, empty switch/if/while, unnecessary object creation, etc.
The rules are defined in XML format (.exsd file)
PMD needs to be configured with the required rules before its use.
The rules we are going to follow are:
Unused imports should not be present
System.out.println statements should not be present
Variable naming convention should be followed
- variable names should be in camel case
Method naming convention should be followed
- method names should be in camel case
Class naming convention should be followed
- class names should be in pascal case
Package naming convention should be followed
- package names should be in lowercase
printStackTrace() should not be present
Violations in our code
package EMartEmployee;java.util.*;
public class employee {
private String Name;
public int EmpId;
public String city;
employee() {
this.city = "Mysore";
}
employee(int empId) {
this.EmpId = empId;
}
public String getname() {
System.out.println("Name: " + this.Name);
return Name;
}
public void setname(String name) {
this.Name = name;
}
}
Here's the code after fixing the violations:
package emartemployee;
public class Employee {
private String name;
public int empId;
public String city;
Employee() {
this.city = "Mysore";
}
Employee(int empId) {
this.empId = empId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Testing a Validation
While the E-Mart application is under development, the developers keep performing unit tests on their code. But as the code is increasing in size, testing is becoming difficult. This calls for automating the tests to make testing more reliable and to save time.
To start with automation of tests, the team has chosen the following module which validates the age of a person for employing. It checks that an employee's age should be from 18 to 40:
public class AgeValidation {
public boolean isAgeValid(int age) {
if(age >= 18 && age <= 40) return true;
else return false;
}
}
Now the developers need to create automated unit tests for the isAgeValid() method for different inputs.
They have put the following solution forward:
public class AgeValidationTest {
@Test
public void ageValidationTestValid() {
Assert.assertTrue(new AgeValidation().isAgeValid(25));
}
@Test
public void ageValidationTestInvalid() {
Assert.assertFalse(new AgeValidation().isAgeValid(16));
}
}
Given above are test cases written using the JUnit framework
Unit Testing - Manual and Automated
Testing of single small units of code such as a method or a class and asserting certain behavior is called unit testing. It is done by developers.
Unit testing can be:
Manual
Time consuming
Error prone
There are several frameworks available for automating unit tests. JUnit and TestNG are among the popular ones for Java.
JUnit Testing Framework
In this course, we will be using JUnit framework for testing our code.
JUnit is an open-source unit testing framework for Java. It provides classes to write and run automated tests. It provides
annotations to create and customize tests
test fixtures (to fix state) for setting up each test
assertions to test for expected results
test suites for grouping tests
Required JARs for JUnit: junit-4.12.jar, hamcrest-core-1.3.jar
JUnit - Test Case
Working with JUnit starts with creating test methods. JUnit provides annotations for marking and configuring such methods.
The org.junit.Test annotation turns a public method into a test method.
@Test
public void ageValidationTestValid() {
Assert.assertTrue(new AgeValidation().isAgeValid(25));
}
Once a class has a test method, it is called a test class or a test case, and can be run as a JUnit test:
Right click on the class ---> Run As ---> JUnit Test
The JUnit Runner is responsible for constructing the instances of the test classes before running the tests. It is also responsible for making them available for garbage collection after the tests.
JUnit - Assertions
A test method can either make assertions or expect exceptions.
Assertions are statements which claim a certain output from a module to be tested. They compare expected results with actual results.
If they match, the claim turns out to be true, and the test will pass.
Otherwise, the claim turns out false, and the test will fail.
The org.junit.Assert class is used for making assertions.
Example:
@Test
public void ageValidationTestInvalid() {
Assert.assertFalse(new AgeValidation().isAgeValid(16));
}
The above test asserts that the isValidAge() method will return true for the given input. If the method has been properly implemented, it will return true, and the test will pass.
Assert Class
The Assert class provides static methods for testing a variety of conditions. These methods accept actual and expected results for comparison and determine whether a test method will pass or fail.
When a test fails, the test method throws an AssertionException. Optional error messages can be specified for Assertion exceptions. These exceptions are caught by JUnit and their messages are displayed.
Here are some useful methods in the Assert class: J17
Test Suites
What if we can create multiple test classes as a group to execute them together?
JUnit provides test suits to create groups of test classes so that the code maintainability is improved.
The following annotations are used for creating test suites:J18
Here is an example,
@RunWith(Suite.class)
@Suite.SuiteClasses({UserTest.class, ProductTest.class, OrderTest.class})
public class TestSuiteDemo { ... }
Code Coverage
Code coverage aims at determining the extent to which code is tested during unit testing.
With code coverage, you can determine the parts of the code which were not executed by test cases. You can then modify your unit tests to ensure those parts of the code are executed.
The larger the code coverage, better is the chance of having a bug-free code.
We make use of coverage tools to measure code coverage.
Some commonly used coverage tools are as follows:
JCov
JaCoCo
Cobertura
EclEmma
Emma
Products Running Out of Stock!
Imagine a customer ordering a product which is not in stock.
E-Mart wants to prevent customers from buying products not in stock, and instead, notify the customers about the unavailability of such products.
Products running out of stock is an exceptional situation, i.e. a deviation from the normal condition. Such situations need to be handled by giving appropriate messages to the customers.
Exceptions