Before we talk about Object-Oriented Programming we need to talk about the object itself in java.
Object in Java
Object-oriented programming is a programming paradigm where objects play the main role. An object is an entity with properties and methods. Very often methods of the object will manipulate with object’s properties. Another important thing to remember that objects work with references or we can also say pointers. In Java, an object gets created from its class. The class will give us a template and it will determine what kind of properties and methods object from this class should have. Class is a blueprint for an object and an object is an implementation of its class. Let’s see an example
In the above example, we created two objects from the Employee class. The arrows show that emp
and emp2
are references to their objects. Also, we can see that two objects are not related to each other and their properties have different values. Two objects have been created differently. The first one got created with an empty constructor and we can see that object properties will have default values initially(for objects is null, for numeric is 0, and for boolean is false). The second object is created by constructor with two arguments and we are assigning values to object properties while creating.
Employee emp = new Employee();
The above line will create an object. Java will go and allocate space in the heap memory for the object and emp
will point to it. emp
itself does not hold any value or properties of an object. It will hold address(reference) to an object and using this reference we can work with the object.
This example clearly shows that a class is a blueprint for its objects and an object is an implementation of its class.
Why do we need objects in our programs?
Because we always abstract things out so we can focus on business logic and kind of thinking from a higher view. For example, we all know that a computer understands only 0 and 1. Do we write code in 0 and 1 or do we communicate with computers with 0 and 1? Of course not, we have many abstractions on top of the core binary system. Same thing with objects. In Java, we have core 8 primitive data types:
byte for whole numbers
short for whole numbers
int for whole numbers
long for whole numbers
float for floating numbers
double for floating numbers
char for single character(ACII table)
boolean logical - can be true or false
Everything in Java based on them. You can see that using primitives we can represent numbers, single characters, and boolean. It’s already good but not enough and here in the picture comes objects. We can abstract out primitives and create custom data types we need.
Let’s say we are writing software to manage employees. So we will need to represent employees in our program. How we would do so? Yes, we can create an Employee class that will represent employees and each object will represent each employee we will work with. Ok, let’s take one step back and answer this question — what we would use to hold words and sentences in our program? Of course, String. The String is an object which is based on char of array and it’s already provided to use with core java libraries. We definitely need objects.
The last thing I want to mention is static properties and methods in the class. The static properties belong to the class itself and not to a specific object/instance(even though a specific object has access to static properties and methods). The correct way of using the static members is by class name. The classes which consist of static methods are usually helper classes and they do not serve as data type and we do not create an object from them(for example java.util.Arrays and java.util.Math). There are many examples of hybrid classes that have static and instance members together.
Object-Oriented Programming in Java
There are 4 main concepts of OOP in Java:
- Encapsulation
- Inheritance
- Abstraction
- Polymorphism
Encapsulation is a data hiding or data protection mechanism. The way we achieve an encapsulation by removing direct access from properties by making them private. We provide public methods(usually getters and setters) to read and set values for object properties.
public class Person {
private String name;
private int age; public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
} public int getAge() {
return age;
}
public void setAge(int age) {
if (age < 0) {
throw new IllegalArgumentException("age cannot be negative");
}
this.age = age;
}}
- Why do we need to encapsulate? If the property has public access, the client code can have direct access and can assign any value. By encapsulating we have one layer where we can control what comes to our property in our setter method. In the above example, we can see how we are restricting negative age in the setAge method. Another example — let’s say we are creating a custom List data structure based on an array. The underlining array data structure should be private if we will make it public, it will be accessible to client code. There are possibilities that client code will create a big mess by manipulating directly with the array.
- From whom we are protecting our data? Seems a very simple question however understanding this question is the main part to start developing using the encapsulation concept. We are removing direct access from object properties so we are protecting it from client code that will use this class(object). We are hiding properties from ourselves if we will use this class in other parts of the project.
- If the property type of object is a mutable object. We cannot return the original address because the client will direct access using the returned reference. We always need to take a copy and return a reference to it.
- Even though you can create your setters and getters methods with any name or if the requirement is not required to have them, it is totally fine to avoid them. But keep in mind that if you want to use your objects with external libraries so they might assume you have all setters and getters with correct names.
Inheritance is a process where one class can inherit visible properties and methods from another class — the parent-child relationship between two classes (or superclass and subclass).
// in Person.java file
public class Person {
public String name;
public String address;
public int age;
public void walk() {
System.out.println(name + " is walking.");
}
}// in Student.java file
public class Student extends Person {
public static void main(String[] args){
Student student = new Student();
student.name = "John Doe";
student.address = "101 Main St";
student.age = 22;
student.walk();
}
}
In the above example, the Student class extends the Person class so our Student class is a child class of Person class. Student class will extend all visible (depends on access modifiers of variables and methods) variables and methods.
Inheritance is useful for code reusability for example we can have one generic class that will have common properties and methods with default behaviors and the child classes can just extend it and reuse a lot of code. If the child class wants to have its own implementation for methods defined in the parent class, we can always override these methods in the child class.
One good example is java.lang.Object class. Object class is the parent class for all classes in java. Java automatically will inject extends Object syntax after every class declaration. Why every class needs to extend from super java.lang.Object class? So from every class in java, we can potentially create an object. It can be Person or it can be Student or it can be Car and so on and if we think about these classes they are all objects. Java wants to give generic behaviors for every object that ever will be created in java. The java.lang.Object has 11 methods(Java 8) so every class will inherit these methods.
equals(Object obj)
This is one of the methods that will come from the Object class. We need the equals method to compare two objects of the same class on equality. So our superclass is giving as equals method to do so. By default, it will not compare two object properties, it will compare if two references are pointing to the same object or not(same as ==). We need to override the equals method and write the logic of how exactly we want to compare our objects.
It’s good to have some common methods for all objects because other libraries can assume that in order to compare your objects they can use the equals method. The same logic for other methods as well.
We are saying that every class extends Object but in this example, our Student class extends our Person class not the Object class. Yes, the Student class will extend the Object class via the Person class.
Java allows only a single inheritance type. Multiple classes can inherit from a single class but one class cannot inherit multiple classes at the same time.
Abstraction allows us to focus on what an object does instead of how it does. Abstraction is achieved by abstract methods. In java, abstract methods can be created in the abstract classes and interfaces. Let’s discuss both options and see when we need to use abstraction.
An abstract class is a class in java that can have abstract methods. We cannot create objects from an abstract class directly. The abstract class will become useful only when it has implementation (concrete) classes. The concrete class is a non-abstract class that extends an abstract class and implements all its abstract methods.
// In FileService.java file
public abstract class FileService {
public abstract void saveFile(String source, String target);
public abstract String getFileContent(String source);
public abstract void copyFile(String source, String target);
public abstract boolean deleteFile(String path);
}// In FileServiceLocalImpl.java
public class FileServiceLocalImpl extends FileService {
@Override
public void saveFile(String source, String target){
// code that will save file into file system
}
@Override
public String getFileContent(String source){
// code that will get file content
} @Override
public void copyFile(String source, String target){
// code that will copy file
} @Override
public boolean deleteFile(String path){
// code that will delete file
}
}
In the above example, we have an abstract class FileService which has abstract methods. The abstract methods do not have bodies because they are abstract. Indeed, it will not compile if you add a body for the abstract method. We have FileServiceLocalImpl non-abstract class which extends the abstract class. When a non-abstract class extends an abstract class, the non-abstract class kind of signing up the contract with the abstract class — non-abstract that extends abstract class must implement all its abstract methods. We implement abstract methods by overriding them and providing the body.
public class Main{
public static void main(String[] args) {
// FileService fService = new FileService();
// It will not compile because we cannot create object
// from abstract class directly
FileService fService = new FileServiceLocalImpl();
// ...
// code that use fService do resolve some problem }
}
So we create an object for FileService like
FileService fService = new FileServiceLocalImpl();
This is a polymorphic way of creating an object. The left part will dictate what methods and properties are available and the right side is an actual object. If the right side is overridden some methods while running the program will execute the overridden methods from FileServiceLocalImpl in our case.
So why do we need the extra work and create all these abstract methods and then create another class to implement them?
We need to design our software that relatively easy to change. For example, let’s say to store files we use a local file system in our server where our software runs, and then we decided to switch to use an S3 bucket to store and manipulate all our files. So we always need to be ready for this kind of change and if we design our software from the first place using the abstraction concept it will save us a lot of work. We can have an abstract class or interface that will predefine all manipulations with files via abstract methods and we can have one implementation class for the local file system. We will be using this abstract class to do all manipulation around the project. And when the time will come to change the local file system to an S3 bucket, we can create one concreate class for S3 bucket manipulations. And we will just need to replace the implementation part while creating the object for FileService. The business logic code that uses FileSystem no need to change. This is the real power of abstraction.
The code with business logic should work with component via Interface. In the future when decided to replace the component we don’t need to worry about the business logic part code we can just create another implementation based on Interface.
The interface is a data type in Java that can have abstract methods. I don’t want to go deep into syntax differences between abstract class and interface. The interface is also a mechanism to achieve abstraction, the main difference that the abstract class can also have non-abstract methods and interface only abstract methods(except static and default). We can implement multiple interfaces at the same time but we can extend only one abstract class.
Polymorphism is the ability of an object to take many forms. Polymorphism works together with inheritance and abstraction.
FileService fService = new FileServiceLocalImpl();
and for example, if we have another concrete class of FileService we could do
FileService fService = new FileServiceS3Impl();
FileService can be FileServiceLocalImpl and FileServiceS3Impl so that’s why polymorphism is an ability of an object to take many forms.
That’s it for OOP in java. Thank you!
Please take my Java Course for video lectures.This article is part of the series of articles to learn Java programming language from Tech Lead Academy:Introduction to programming
OS, File, and File System
Working with terminal
Welcome to Java Programming Language
Variables and Primitives in Java
Convert String to numeric data type
Input from the terminal in Java
Methods with Java
Java Math Operators and special operators
Conditional branching in Java
Switch statement in Java
Ternary operator in Java
Enum in Java
String class and its methods in Java
Loops in Java
Access modifiers in Java
Static keyword in Java
The final keyword in Java
Class and Object in Java
Object-Oriented Programming in Java
OOP: Encapsulation in Java
OOP: Inheritance in Java
OOP: Abstraction in Java
OOP: Polymorphism in Java
The method Overriding vs Overloading in Java
Array in Java
Data Structures with Java
Collection framework in Java
ArrayList in Java
Set in Java
Map in Java
Date and Time in Java
Exception in Java
How to work with files in Java
Design Patterns
Generics in Java
Multithreading in java
Annotations in Java
Reflection in Java
Reflection & Annotations - The Powerful Combination
Run terminal commands from Java
Lambda in Java
Unit Testing in Java
Big O Notation for coding interviews
Top Java coding interview questions for SDET