OOPS in Java: Diving deep into 'static' and 'abstract'

Swapnil PantSwapnil Pant
15 min read

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:

  1. class

  2. variable

  3. 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

  1. 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.

  1. We can also declare variables as static in a class that is not declared as static.
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

  1. 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();
    }
}
  1. We can also declare functions as static in a class that is not declared as static.
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:

  1.    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
       */
    
  2.    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
       */
    
  3.    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 as static 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.   //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.     // 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.   // 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.   // 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.   // 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.
      */
    
  6.   /*
          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-abstractfunctions 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:

  1. Twitter

  2. Showwcase

10
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.