OOPS in Java: Diving deep into 'static' and 'abstract'
Table of contents
- This will be the most detailed guide you will ever read on static and abstract.
- static
- Any Class can't be made static
- Only inner classes can be declared static
- Variables Can Be Declared as Static or Non-Static Regardless of Whether the Class is Declared Static or Not
- Functions Can Also Be Declared as Static or Non-Static Regardless of Whether the Class is Declared Static or Not
- Constructor of a static class
- static Block Of Code
- Inheritance and static
- Polymorphism and static
- abstract
- Variables can't be declared abstract
- You can't create objects for an abstract class
- Both abstract and non-abstract functions are allowed in an abstract class
- Constructor and abstract
- static and abstract class
- abstract class can also have static functions and variables
- static block of code inside abstract class
- Interface
I have seen a lot of people getting confused with OOPS concepts, especially those related to static
and abstract
keyword. In this blog, I will be covering up all the possible combinations of code using these 2 keywords, so that whenever someone asks you a question regarding them, you never get confused again in future.
This will be the most detailed guide you will ever read on static
and abstract
.
In a typical code structure, there are three main entities:
class
variable
function
Most of the explanations I make will revolve around these three. So, let's start.
Note: Don't look at the logic behind the codes that I have written. Just focus on the concepts. What I mean to say is that I will create a Car class and try to create static function that would print the car's information but logically this is not correct as we should be able to create different cars and each car would have different brand name and other information, so it should not be allowed to access this info using the class name.
static
The concept behind the static
keyword is straightforward. It means that a member belongs to the class itself, rather than to instances of the class. This implies that if you declare a member as static
, you can access it without creating an object of the class.
The idea is that anything declared as static
gets resolved at compile time. And an instance of a class is assigned heap memory at run time, so we can access variables or functions of a class declared as static using the class name itself.
However, it's important to note that only inner classes can be declared as static
in Java.
Any Class can't be made static
Consider the following code:
static class Car {
}
class Main {
public static void main(String[] args){
System.out.println("Hello");
}
}
This code will simply throw error coz you can't declare any class static
as such. It's because you need a class to access something declared as static
, but there's nothing outside this class, so how can you even access it?
Only inner classes can be declared static
That means, we can declare only an internal class of a class as static. Consider the following code:
class Car {
String brand = "Lexus";
/*
This CarEngine class is the inner class of class Car, so it can be
declared as static and this code will run without throwing
any error
*/
static class CarEngine {
}
}
class Main {
public static void main(String[] args){
System.out.println("Hello");
}
}
Variables Can Be Declared as Static or Non-Static Regardless of Whether the Class is Declared Static or Not
- Consider the following code:
class Car {
String brand = "Lexus";
static class CarEngine {
// this is a default variable
String engineType = "V6 Turbocharged";
// this variable is static
static String manufacturer = "Toyota";
}
}
class Main {
public static void main(String[] args){
/*
Since engineType wasn't declared static, therefore, we need
to declare an object of the class to access it
*/
Car.CarEngine engine = new Car.CarEngine();
System.out.println(engine.engineType);
/*
since manufacturer was a static variable it can be
accessed using the class name itself
*/
System.out.println(Car.CarEngine.manufacturer);
}
}
Also note one thing that in Java, we can create objects of static class, i.e., we can access the members of a static class either through an object or through the class name directly.
- We can also declare variables as
static
in a class that is not declared asstatic
.
class Car {
String brand = "Lexus";
static String model = "Lexus LS";
}
class Main {
public static void main(String[] args){
/*
Notice how class Car was non-static but it can still have
static variables under it
*/
System.out.println(Car.model);
}
}
Functions Can Also Be Declared as Static or Non-Static Regardless of Whether the Class is Declared Static or Not
- Consider the following code:
class Car {
String brand = "Lexus";
static class CarEngine {
String engineType = "V6 Turbocharged";
static String manufacturer = "Toyota";
// a non-static function
void startEngine() {
System.out.println("Engine is up and running...");
}
// a static function
static void stopEngine() {
System.out.println("Engine has stopped working!");
}
}
}
class Main {
public static void main(String[] args){
Car.CarEngine engine = new Car.CarEngine();
// we need to create an object to access the function startEngine
engine.startEngine();
// we don't need an object to access the function stopEngine
Car.CarEngine.stopEngine();
}
}
- We can also declare functions as
static
in a class that is not declared asstatic
.
class Car {
static String brand = "Lexus";
static String model = "Lexus LS";
static void printCarInfo(){
System.out.println("Brand: " + brand);
System.out.println("Model: " + model);
}
}
class Main {
public static void main(String[] args){
/*
Notice how class Car was non-static but it can still have
static functions and variables under it
*/
Car.printCarInfo();
}
}
Constructor of a static
class
class Car {
String brand = "Lexus";
static class CarEngine {
static String engineType;
static String manufacturer;
public CarEngine(String engineType, String manufacturer){
this.engineType = engineType;
this.manufacturer = manufacturer;
}
}
}
class Main {
public static void main(String[] args){
System.out.println(Car.CarEngine.engineType); // null
System.out.println(Car.CarEngine.manufacturer); // null
Car.CarEngine engine = new Car.CarEngine("V6 Turbocharged", "Toyota");
System.out.println(engine.engineType); // V6 Turbocharged
System.out.println(engine.manufacturer); // Toyota
System.out.println(Car.CarEngine.engineType); // V6 Turbocharged
System.out.println(Car.CarEngine.manufacturer); // Toyota
}
}
Ok, so now let's understand the above code. We declared a class Car
and an inner class of it called CarEngine
. CarEngine
has 2 variables: engineType
and manufacturer
, and a constructor
.
A constructor
is a special kind of function that gets called as when we create an instance
of a class
.
Now, come to main
and consider each section of the code:
System.out.println(Car.CarEngine.engineType); // null System.out.println(Car.CarEngine.manufacturer); // null /* This section of the code gives null as output because the default value of both of these variables is null, and at this point we haven't created any object of this class, thus the constructor doesn't get called and hence the values for both the variables don't get initialised */
Car.CarEngine engine = new Car.CarEngine("V6 Turbocharged", "Toyota"); System.out.println(engine.engineType); // V6 Turbocharged System.out.println(engine.manufacturer); // Toyota /* Now, as I have created a new object for the class, if we try to access the variables using this object the already intialised values will be printed */
System.out.println(Car.CarEngine.engineType); // V6 Turbocharged System.out.println(Car.CarEngine.manufacturer); // Toyota /* Since, we already declared an object for the class, that means the constructor was already called and the variables are initialised and thus the values will be printed even if we try to access them using the classname */
Note that you can't declare a constructor as
static
, since a constructor is related to initialisation of an object, which occurs at run time, declaring a constructor asstatic
doesn't make any sense.
static
Block Of Code
Since, static
gets resolved at run time, so any code inside a static
block of code will be executed first.
Consider the following code:
class Car {
static String brand = "Lexus";
static {
System.out.println("Inside Car class");
}
static class CarEngine {
static String engineType = "V6 Turbocharged";
static String manufacturer = "Toyota";
static {
System.out.println("Inside CarEngine class");
}
}
}
class Main {
public static void main(String[] args){
//1
Car car;
Car.CarEngine engine;
// 2
Car car_2 = new Car();
Car.CarEngine engine_2 = new Car.CarEngine();
// 3
Car.CarEngine engine_3 = new Car.CarEngine();
Car car_3 = new Car();
// 4
System.out.println(Car.brand);
System.out.println(Car.CarEngine.engineType);
// 5
System.out.println(Car.CarEngine.engineType);
System.out.println(Car.brand);
}
}
Let's get into each part of code in the main
one by one:
//1 Car car; Car.CarEngine engine; /* This won't print anything, because in Java, static initialization blocks and static variable assignments are executed during class loading, not during object instantiation. When you declare variables without initializing them or accessing their static members, there's no need for the JVM to load those classes into memory, and hence, the static blocks are not executed. */
// 2 Car car_2 = new Car(); Car.CarEngine engine_2 = new Car.CarEngine(); /* OUTPUT: Inside Car class Inside CarEngine class The output is as such because we created the objects of both the classes Car and CarEngine, so the static blocks get executed and hence the required result. */
// 3 Car.CarEngine engine_3 = new Car.CarEngine(); Car car_3 = new Car(); /* OUTPUT: Inside CarEngine class Inside Car class The output is as such because we created object for CarEngine class first. */
// 4 System.out.println(Car.brand); System.out.println(Car.CarEngine.engineType); /* OUTPUT: Inside Car class Lexus Inside CarEngine class V6 Turbocharged Since, we are trying to access a member of Car's class, it gets loaded into the memory, thus resulting in execution of the static block first, then the car's brand gets printed, then CarEngine class' static blocks gets executed and then the engineType gets printed. */
// 5 System.out.println(Car.CarEngine.engineType); System.out.println(Car.brand); /* OUTPUT: Inside CarEngine class V6 Turbocharged Inside Car class Lexus Reason for this output is same as example //4. It's just that Car.CarEngine class' member was tried to access first, hence the order of result. */
/* Wait but what would be the output, when I execute the entire code at once? It would be something like: Inside Car class Inside CarEngine class Lexus V6 Turbocharged V6 Turbocharged Lexus But why? It's because the static block of code is dependent on class and as soon as we create an object of the class, it gets loaded into the memory and the static block of code gets executed. Now, since static block is not dependent on objects, no matter how many more objects of the class you create, it will never be executed again. */
Inheritance and static
Functions declared as static
can't be overridden, and it makes sense, because inheritance depends on the objects. Imagine a parent class Vehicle
and it has a static function called run()
, which upon being executed logs out Vehicle is running.
on the screen. Then we have a child class of this class Vehicle
, called Car
, which has the same function (it's trying to override it's parent's function). This code will simply throw error.
class Vehicle {
public static void run(){
System.out.println("Vehicle is running!");
}
}
class Car extends Vehicle {
public void run(){
System.out.println("Vehicle is running!");
}
}
class Main {
public static void main(String[] args){
// Car.run();
}
}
But, wait! If you make the run()
function inside Car class as static
, it will work without any error.
class Vehicle {
public static void run(){
System.out.println("Vehicle is running!");
}
}
class Car extends Vehicle {
public static void run(){
System.out.println("Car is running!");
}
}
class Main {
public static void main(String[] args){
Vehicle.run();
Car.run();
Vehicle vehicle_1 = new Vehicle();
vehicle_1.run();
Car car_1 = new Car();
car_1.run();
Vehicle vehicle_2 = new Car();
vehicle_2.run();
// Not allowed. Learn about upcasting to understand in more depth
// Car car_2 = new Vehicle();
// car_2.run();
}
}
You will be surprised to see the output of this code. It will be:
/*
Vehicle is running!
Car is running!
Vehicle is running!
Car is running!
Vehicle is running!
*/
But why? It's because when you access the function using run function using Car
class or it's object the run function associated to it gets executed. Same for the Vehicle
class. But, if you look at the last example, the output is Vehicle is running!
and it's because what function to resolve depends on the reference type not the object resolved at runtime.
Polymorphism and static
class Car {
public static void run(){
System.out.println("Car is running!");
}
public static void run(String carBrand) {
System.out.println(carBrand + " is running!");
}
}
class Main {
public static void main(String[] args){
Car.run();
Car.run("Lexus");
Car car_1 = new Car();
car_1.run();
car_1.run("Lexus");
}
}
The output for the above code is pretty obvious. It will be:
/*
Car is running!
Lexus is running!
Car is running!
Lexus is running!
*/
It's because based on the parameters you pass, the required function is matched and is executed, no matter if the class is itself used to execute it or an object of the class.
abstract
I have covered up every possible situation in which static
keyword could be used. If I missed something, sure let me know in the comments.
Then, now let's start with abstract
. The idea behind abstract
is also simple. Something declared as abstract
is like a prototype, which means it can be inherited or overridden and the actual implementation can be provided by the inheritor or overrider.
Let's look into examples to have a better insight.
Variables can't be declared abstract
abstract class Car {
abstract String brand;
}
class CarImplementation extends Car {
}
class Main {
public static void main(String[] args){
CarImplementation car = new CarImplementation();
System.out.println(car.brand);
}
}
The above code will simply throw error and won't work.
You can't create objects for an abstract
class
abstract class Car {
String brand;
abstract void printCarInfo();
}
class CarImplementation extends Car {
void printCarInfo() {
System.out.println(brand);
}
}
class Main {
public static void main(String[] args){
Car car = new Car();
}
}
The above code won't work.
Both abstract
and non-abstract
functions are allowed in an abstract
class
abstract class Car {
String brand = "Lexus";
abstract void printCarInfo();
void nonAbstractfunction() {
System.out.println("Inside non-abstract function!");
}
}
class CarImplementation extends Car {
void printCarInfo() {
System.out.println(brand);
}
}
class Main {
public static void main(String[] args){
CarImplementation car = new CarImplementation();
car.printCarInfo();
car.nonAbstractfunction();
}
}
/*
OUTPUT:
Lexus
Inside non-abstract function!
*/
The output is pretty much expected. It's because we have an abstract
class called Car
and it's child class called CarImplementation
. We have an abstract
function and a non-abstract
function inside the Car
class. We implemented the abstract
function inside it's child class and then simply created object for it and called both the functions to get the desired output.
But, note that you can also override thenon-abstract
functions in the child class.
abstract class Car {
String brand = "Lexus";
abstract void printCarInfo();
void nonAbstractfunction() {
System.out.println("Inside non-abstract function!");
}
}
class CarImplementation extends Car {
void printCarInfo() {
System.out.println(brand);
}
void nonAbstractfunction() {
System.out.println("Inside non-abstract function in child class!");
}
}
class Main {
public static void main(String[] args){
CarImplementation car = new CarImplementation();
car.printCarInfo();
car.nonAbstractfunction();
}
}
/*
OUTPUT:
Lexus
Inside non-abstract function in child class!
*/
Constructor and abstract
Abstract classes can have constructors. I know it doesn't make much sense because you can't really create an object for an abstract
class but it's fine. The constructor
can be overridden by it's child class.
Look at the following code for a better idea.
abstract class Car {
String brand;
public Car() {
this.brand = "Lexus";
}
public Car(String brand) {
this.brand = brand;
}
abstract void printCarInfo();
}
class CarImplementation extends Car {
public CarImplementation() {
super();
}
public CarImplementation(String brand) {
super(brand);
}
void printCarInfo() {
System.out.println(brand);
}
}
class Main {
public static void main(String[] args){
CarImplementation car = new CarImplementation();
car.printCarInfo();
CarImplementation car_2 = new CarImplementation("Toyota");
car_2.printCarInfo();
}
}
/*
OUTPUT:
Lexus
Toyota
*/
So, in the above code, we have an abstract
class Car and it's child CarImplementation
. Then, we have two constructors. The required constructor gets called accordingly.
static
and abstract
class
An inner class can be declared as both static
and abstract
. Look at the following example:
abstract class Car {
String brand = "Lexus";
static abstract class CarEngine {
static String engineType = "V6 Turbocharged";
static String manufacturer = "Toyota";
abstract void printCarEngineInfo();
}
static class CarEngineImplementation extends CarEngine {
void printCarEngineInfo() {
System.out.println("Engine Type: " + engineType);
System.out.println("Manufacturer: " + manufacturer);
}
}
}
public class Main {
public static void main(String[] args){
Car.CarEngineImplementation engine = new Car.CarEngineImplementation();
engine.printCarEngineInfo();
}
}
/*
OUTPUT:
Engine Type: V6 Turbocharged
Manufacturer: Toyota
*/
abstract
class can also have static
functions and variables
These static
functions can be directly accessed using the class name itself.
abstract class Car {
static String brand = "Lexus";
static void printCarInfo() {
System.out.println(brand);
}
}
public class Main {
public static void main(String[] args){
System.out.println(Car.brand);
Car.printCarInfo();
}
}
/*
OUTPUT:
Lexus
Lexus
*/
static
block of code inside abstract
class
Now, for this part let's go with a complex example.
class Car {
static String car = "";
public Car(){
car = "Lexus";
}
static {
System. out.println("Inside static block of Car class!");
}
abstract static class CarEngine{
static String engineType;
static String manufacturer;
static {
System.out.println("Inside static block of CarEngine class!");
}
public CarEngine(){
engineType = "V6 Turbocharged";
manufacturer = "Toyota";
}
}
static class CarEngineImplementation extends CarEngine{
}
}
public class Main {
public static void main(String[] args) {
// 1
Car car = new Car();
Car.CarEngineImplementation engine = new Car.CarEngineImplementation();
/*
OUTPUT:
Inside static block of Car class!
Inside static block of CarEngine class!
*/
// 2
Car.CarEngineImplementation engine_2 = new Car.CarEngineImplementation();
Car car_2 = new Car();
/*
OUTPUT:
Inside static block of CarEngine class!
Inside static block of Car class!
*/
}
}
Let's understand the above code. First I declared a class called Car
. Then, inside it I declared a static
and abstract
class called CarEngine
. Now, since this class is abstract
, we need a child class for its implementation. Let's called CarEngineImplementation
. The remaining concept is same as what we did when understanding static block of code
in a static
class before. Whichever class gets a place in memory first, it's static block of code
gets executed.
Interface
Interface in Java is just like an abstract class. The main difference is that variables inside an Interface are public
, static
and final
by default, and you can't change this.
Check out: https://www.javatpoint.com/difference-between-abstract-class-and-interface, for more detailed difference between an abstract class
and an interface
.
Check out the following example code:
interface Car {
String brand = "Lexus";
void printCarInfo();
}
class CarImplementation implements Car {
public void printCarInfo() {
System.out.println(brand);
}
}
public class Main {
public static void main(String[] args) {
CarImplementation car = new CarImplementation();
car.printCarInfo();
}
}
/*
OUTPUT:
Lexus
*/
static
functions inside Interface
interface Car {
String brand = "Lexus";
static void printCarInfo() {
System.out.println("Car: " + brand);
}
}
class CarImplementation implements Car {
public void printCarInfo() {
System.out.println("CarImplementation: " + brand);
}
}
public class Main {
public static void main(String[] args) {
Car.printCarInfo();
CarImplementation car = new CarImplementation();
car.printCarInfo();
}
}
/*
OUTPUT:
Car: Lexus
CarImplementation: Lexus
The output will still be same even if you make the printCarInfo()
function inside `CarImplementation` class `static`
*/
Consider another code:
interface Car {
String brand = "Lexus";
static void printCarInfo() {
System.out.println("Car: " + brand);
}
}
class CarImplementation implements Car {
}
public class Main {
public static void main(String[] args) {
Car.printCarInfo();
CarImplementation car = new CarImplementation();
car.printCarInfo();
}
}
/*
This code will throw error because static functions don't get
inherited
*/
That's all for today. I hope you learned something new. You can reach out to me on:
Subscribe to my newsletter
Read articles from Swapnil Pant directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Swapnil Pant
Swapnil Pant
I'm a web developer who loves challenges and is good at decision making. Skilled in HTML, CSS, JavaScript, Typescript, Node.JS, Express.JS, and React, I'm an open source enthusiast who's always seeking to learn and contribute to the community. With a strong attention to detail and problem-solving abilities, I'm passionate about staying ahead of industry trends and taking on new challenges.