0% found this document useful (0 votes)
9 views38 pages

Object Oriented Programming

Chapter 2 covers the fundamentals of object-oriented programming (OOP), focusing on key concepts such as abstraction, encapsulation, inheritance, and polymorphism, which are essential for software engineering interviews. It explains the differences between abstract classes and interfaces, the role of constructors in abstract classes, and the advantages of abstraction in reducing complexity and improving code reusability. Additionally, it discusses inheritance, composition, and their respective benefits and drawbacks, along with multilevel inheritance and the appropriate use of encapsulation and abstraction in project development.

Uploaded by

Abdellah LAKEHAL
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
9 views38 pages

Object Oriented Programming

Chapter 2 covers the fundamentals of object-oriented programming (OOP), focusing on key concepts such as abstraction, encapsulation, inheritance, and polymorphism, which are essential for software engineering interviews. It explains the differences between abstract classes and interfaces, the role of constructors in abstract classes, and the advantages of abstraction in reducing complexity and improving code reusability. Additionally, it discusses inheritance, composition, and their respective benefits and drawbacks, along with multilevel inheritance and the appropriate use of encapsulation and abstraction in project development.

Uploaded by

Abdellah LAKEHAL
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 38

CHAPTER 2: OBJECT ORIENTED

PROGRAMMING
In this chapter, we'll dive into the world of object-oriented
programming (OOP). If you're new to programming, just starting
your career, or have less than five years of experience, it's quite
common for your interviews to focus on the fundamentals of OOP.
The interviewer's goal is to assess your grasp of this topic,
evaluating your strength in designing code based on these
principles, especially since Java is an object-oriented language. It's
essential to be well-versed in key OOP concepts like abstraction,
encapsulation, inheritance, and polymorphism. Real-life examples
can help you explain these concepts better, and it's also important to
understand the SOLID principles.
Failing to provide satisfactory answers to these questions can lead to
rejection because no one wants to hire a software engineer who
lacks proficiency in these fundamental aspects.
Let’s dive into the interview questions,
What are the four principles of OOP?
The four principles of Object-Oriented Programming (OOP) are:
Encapsulation: This refers to the practice of hiding the internal
workings of an object and exposing only the necessary functionality.
The data and behaviour of an object are encapsulated within the
object, and can only be accessed through well-defined interfaces.
Inheritance: Inheritance allows objects to inherit properties and
behaviours from other objects. Inheritance allows for the creation of
hierarchical relationships between classes, with parent classes
passing down their characteristics to their child classes.
Polymorphism: Polymorphism refers to the ability of objects to
take on many forms, and is achieved through the use of inheritance,
overloading and overriding methods, and interfaces. Polymorphism
allows for greater flexibility and reuse of code.
Abstraction: Abstraction refers to the process of identifying
common patterns and extracting essential features of objects,
creating classes from these patterns. Abstraction allows for the
creation of higher-level concepts that can be used in multiple
contexts, and can simplify complex systems.
What is the difference between an abstract class and an
interface?
Feature Abstract Class Interface
Partial abstraction,
Purpose shared implementation Complete abstraction, contract
Traditionally only abstract methods
Can have abstract and (can have default and static
Methods non-abstract methods methods in Java 8+)
Supports single
Inheritance inheritance Supports multiple inheritance
Can have static, non-
Variables static, final, non-final Only static final variables
Subclass must
Implementatio implement all abstract Implementing class must provide
n methods code for all methods
Cannot be instantiated
Instantiation directly Cannot be instantiated directly
Constructors Can have constructors Cannot have constructors
Can have a main
Main method method Cannot have a main method

When to Use:
Abstract classes:

When you have some common implementation to share


among subclasses.
When you want to enforce a hierarchy and prevent direct
instantiation of the base class.
When you need to control access to members using
access modifiers.
Interfaces:

When you want to define a contract that multiple


unrelated classes can implement.
When you want to achieve loose coupling between
classes.
When you need to support multiple inheritance.

What is the use of constructor in an abstract class?


While abstract classes cannot be instantiated directly, their
constructors play a crucial role in object initialization within
inheritance hierarchies. Here are the key purposes of constructors in
abstract classes:
Initializing Member Variables:

Abstract classes often have member variables that need to


be initialized for proper object state. Constructors perform
this initialization, ensuring consistent setup for all
subclasses.
Example: An abstract Shape class might have a color
property initialized in its constructor.
Enforcing Invariants and Constraints:

Constructors can enforce rules and constraints that must


hold true for all objects in the hierarchy. This ensures data
integrity and validity.
Example: A BankAccount abstract class might require a
non-negative initial balance in its constructor.
Shared Initialization Logic:

Common initialization steps for all subclasses can be


consolidated in the abstract class constructor, reducing
code duplication.
Example: An Employee abstract class might initialize a
hireDate property in its constructor, shared by all
employee types.
Controlling Instantiation:

Constructors can be made private or protected to control


how subclasses are created, ensuring they are instantiated
through specific mechanisms or helper methods.
Example: A Singleton abstract class might have a private
constructor to enforce a single instance.
Calling Superclass Constructors:

Subclasses must call their superclass constructor (implicitly


or explicitly) during their own construction. This ensures
proper initialization of inherited state.
Example: A SavingsAccount subclass must call the
BankAccount constructor to initialize shared account
properties.
Key Points:

Abstract class constructors are not used for object creation


directly, but they are invoked when a subclass object is
created.
They ensure consistent initialization and enforce class
invariants, promoting code reusability and maintainability.
Understanding constructor behaviour in abstract classes is
essential for effective object-oriented design.

What is abstraction, and what are its advantages?


(Concrete class doing the same what is the advantage over concrete
class?)
Abstraction is a fundamental concept in programming and many
other fields. In simplest terms, it's the act of focusing on the
essential details of something while hiding away the unnecessary
complexity. Here's a breakdown of abstraction in programming:
What is it?

Abstraction allows you to break down complex systems


into smaller, easier-to-understand pieces.
You define interfaces or classes that expose only the
relevant functionalities, hiding the inner workings.
This lets you work with the system at a higher level,
without getting bogged down in the low-level details.
Think of it this way:

Imagine a car. You don't need to understand the intricate


mechanics of the engine to drive it. You just need to know
how to steer, accelerate, and brake.
Similarly, when using an abstraction in programming, you
don't need to know how it works internally. You simply call
its functions or methods and interact with it at a higher
level.
Advantages of abstraction:

Reduced complexity: It makes your code easier to


understand, write, and maintain by breaking down large
problems into smaller, more manageable chunks.
Increased productivity: You can focus on the logic and
functionality of your program without getting bogged
down in implementation details.
Improved reusability: Abstracted components can be used
in multiple parts of your program, reducing code
duplication and promoting modularity.
Enhanced readability: Code becomes more concise and
less cluttered, making it easier for others to understand
and collaborate on.
Flexibility and adaptability: Abstractions allow you to
change the underlying implementation without affecting
the code that uses them.
Here are some specific examples of abstraction in programming:
Functions: They hide the implementation details of a
specific task and provide a simple interface for other parts
of your program to interact with.
Classes: They bundle related data and functionality,
making it easier to manage and reuse complex data
structures.
Libraries and frameworks: They provide pre-built
abstractions for common tasks, saving you time and effort.

What is the difference between abstraction and


encapsulation?
Abstraction:

Focus: What the object does, hiding implementation


details.
Goal: Simplifying complex systems by exposing only
essential features.
Mechanisms: Abstract classes, interfaces, functions.
Encapsulation:

Focus: How the object's data and behavior are bundled


together.
Goal: Protecting data integrity and controlling access.
Mechanisms: Access modifiers (public, private, protected),
getters and setters.
Key Differences:

Scope: Abstraction operates at a higher level, focusing on


the overall design and interface. Encapsulation works at
the object level, managing internal data and
implementation.
Purpose: Abstraction aims to simplify complexity and
promote reusability. Encapsulation aims to protect data
and manage dependencies.
Implementation: Abstraction is often achieved through
abstract classes or interfaces. Encapsulation is typically
implemented using access modifiers and methods to
control access to data.

What is the difference between Abstraction and


polymorphism?
Abstraction

Focus: Hides the internal complexity of an object,


exposing only the essential features and functionalities
that users need to interact with.
Think of it as: A map that shows the important landmarks
of a city without getting bogged down in the details of
every street and alleyway.
Benefits:

Simplifies code by reducing cognitive load and making it


easier to understand.
Promotes code reusability by focusing on general
functionalities that can be applied in different contexts.
Improves maintainability by making it easier to change the
implementation details without affecting the code that
uses the abstraction.
Mechanisms:

Abstract classes: Define a blueprint for subclasses with


shared functionality and abstract methods that must be
implemented.
Interfaces: Specify contracts that classes must adhere to,
defining methods without implementation.
Functions: Hide the internal logic of a specific task,
providing a simple interface for other parts of the program
to interact with.
Polymorphism
Focus: Enables an object to exhibit different behaviors
depending on its actual type at runtime.
Think of it as: A chameleon that can change its color to
blend in with its surroundings.
Benefits:

Makes code more flexible and adaptable by allowing


different objects to respond differently to the same
message.
Promotes code reusability by enabling generic functions
and methods that can work with different types of objects.
Improves maintainability by making it easier to add new
types of objects without modifying existing code.
Mechanisms:

Method overloading: Allows a class to define multiple


methods with the same name but different parameter
types or numbers.
Method overriding: Allows subclasses to provide their own
implementation of a method inherited from a superclass.
Interfaces: Can define abstract methods with common
behavior that different classes can implement in their own
way.
Feature Abstraction Polymorphism
Focus What an object does How an object behaves
Simplify complexity, hide internal Provide flexibility, adapt
Goal details behavior based on type
Mechanism Abstract classes, interfaces, Method overloading,
s functions overriding, interfaces
Reduced complexity, improved Increased flexibility,
Benefits reusability, maintainability adaptability, reusability
What is the difference between Inheritance and
Composition?
Inheritance allows a class (called a subclass) to inherit properties
and behaviors from another class (called a superclass). The subclass
can then add or modify these properties and behaviors as needed.
It's useful for creating hierarchies of related classes and sharing
code and functionality.
For example, if we have an Animal class, a Mammal class, and a Cat
class, the Cat class can inherit properties and behaviors from both
Animal and Mammal classes while adding its own specific methods.
Benefits:

Promotes code reuse by sharing common functionalities


among related classes.
Provides code organization by structuring classes in a
hierarchy.
Enables specialization by adding specific features to
subclasses.
Composition allows a class to be composed of other objects. This
means that a class can have references to other objects as its
properties and use them to delegate tasks or behaviors. It's useful
for creating complex objects from simpler ones and enabling
dynamic composition at runtime.
For instance, a Car class can be composed of objects such as an
Engine, Wheels, Seats, etc. The Car class can then utilize these
objects to perform various tasks.
Benefits:

Loose coupling between classes – changes in one class


usually don't affect the other.
Greater flexibility – allows using functionalities from any
class, not just parent-child hierarchy.
Promotes modularity and code clarity.
Feature Inheritance Composition
Relationship "is-a" "has-a"
Implementatio Subclasses inherit from Member variables hold other
n superclass objects
Code reuse, organization, Loose coupling, flexibility,
Benefits specialization modularity
Tight coupling, limited flexibility, Complexity, lifecycle
Drawbacks duplication management

What are Composition and Aggregation with examples?


Composition and aggregation are two types of object-oriented
programming concepts that describe the relationship between
objects.
Composition is a strong type of association where an object is made
up of one or more objects of other classes. For example, a car is
composed of various parts such as wheels, engine, transmission, etc.
The car class has an object of the wheel class, engine class, and
transmission class as its member variables.
Aggregation is a weak type of association where an object
contains a reference to one or more objects of other classes. For
example, a university class has a collection of student classes as its
member variable. The student class has an object of the university
class as its member variable.
What is aggregation, composition, and inheritance?
To check if the current code contains examples of aggregation,
composition, and inheritance, you need to look for the relevant
syntax and usage patterns in the code.
Here are some pointers for identifying these concepts in code:
Inheritance: Look for classes that extend or inherit from other
classes. This is typically indicated by the extends keyword in Java,
for example: public class Car extends Vehicle {...}. Inheritance is
used to create a hierarchy of classes where subclasses inherit
properties and methods from their parent classes.
Composition: Look for objects that contain other objects as instance
variables. This is typically indicated by object instantiation within
another object's constructor, for example:
public class Person {
private Job job;
public Person(Job job) {
this.job = job;
}
}

Composition is used to build complex objects by combining simpler


objects.
Aggregation: Look for objects that have references to other
objects as instance variables, but do not own or create them. This is
typically indicated by a "has-a" relationship between objects,
for example:
public class University {
private List<Student> students;
public University(List<Student> students) {
this.students = students;
}
}

Aggregation is used to represent relationships between objects


without tightly coupling them together.
To get a better understanding of how these concepts are used in
code, you may want to read through the codebase and look for
patterns that indicate their usage. Additionally, you may want to
search for specific keywords and syntax related to inheritance,
composition, and aggregation, such as extends, implements, and
new. Finally, you may also want to talk to other developers or review
documentation to understand how these concepts are being used in
the codebase.
Can you explain multilevel inheritance in Java?
Multilevel inheritance is a type of inheritance in object-oriented
programming (OOP) where a derived class (subclass) is created from
another derived class, which itself was derived from a base class
(superclass).
In multilevel inheritance, each derived class inherits the
characteristics of the class above it in the hierarchy. This means that
a subclass not only has all the features of its immediate superclass,
but also those of all its ancestors up the hierarchy chain.
Here's an example to illustrate multilevel inheritance:
class Animal {
void eat() {
System.out.println("Eating...");

}
class Dog extends Animal {

void bark() {
System.out.println("Barking...");

}
}
class Bulldog extends Dog {
void guard() {
System.out.println("Guarding...");
}
}
In this example, Animal is the base class, Dog is a derived class from
Animal, and Bulldog is a derived class from Dog.
Animal has a single method eat(). Dog inherits eat() from Animal
and adds a new method bark(). Bulldog inherits both eat() and
bark() from Dog and adds a new method guard().
Now, an instance of Bulldog can access all the methods of its
immediate superclass (Dog), as well as all the methods of its
ancestor superclass (Animal). For example:
Bulldog bulldog = new Bulldog();
bulldog.eat(); // output: Eating...
bulldog.bark(); // output: Barking...
bulldog.guard(); // output: Guarding...
This example demonstrates how multilevel inheritance can be used
to create a hierarchy of classes that inherit and extend behavior
from each other. However, it is important to use inheritance
judiciously to avoid creating overly complex and tightly-coupled class
hierarchies.
When do you use encapsulation and abstraction in your
project?
Encapsulation and abstraction are two important concepts in object-
oriented programming, and they are used in different ways in
different parts of a project.
Encapsulation is used to protect the internal state of an object and
to control how other objects can access or modify that state. It is
typically used in data modelling, where we define classes that
represent real-world entities and their properties.
For example, if we were building a system to manage a library, we
might define a Book class that has properties like title, author, and
isbn. We would use encapsulation to ensure that these properties
are not accessible or modifiable from outside the Book class, except
through carefully designed methods like getTitle() and setAuthor().
Abstraction, on the other hand, is used to hide the implementation
details of a class or component and to present a simpler, higher-level
interface to other parts of the system. It is typically used in system
design and architecture, where we define components and their
interfaces.
For example, if we were building a web application, we might define
a UserService component that provides methods for creating,
updating, and retrieving user accounts. We would use abstraction to
ensure that other components in the system do not need to know
how the UserService is implemented, but can simply use its interface
to perform the necessary actions.
In general, encapsulation and abstraction are used together in
object-oriented programming to create robust, maintainable, and
scalable systems. Encapsulation is used to protect the internal state
of objects and to control how other objects can access or modify
that state, while abstraction is used to hide the implementation
details of components and to present a simpler, higher-level interface
to other parts of the system.
How do you achieve encapsulation?
Encapsulation is achieved in Java through the use of access
modifiers and getter and setter methods.
Access modifiers control the visibility of variables and
methods in a class. There are three access modifiers in Java:
public, private, and protected.
Public: Public variables and methods can be accessed from
anywhere in the program.
Private: Private variables and methods can only be accessed within
the same class.
Protected: Protected variables and methods can be accessed within
the same class, and by subclasses and classes in the same package.
By default, if you don't specify an access modifier, the variable or
method is considered to have "package" or "default" access, which
means it can be accessed within the same package.
Here's an example of how to use access modifiers to achieve
encapsulation:
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;
}
}
In this example, the Person class has two private variables, name
and age. These variables are not directly accessible from outside the
class, which means that other classes cannot modify or access them
directly.
To allow other classes to access these variables, we provide public
getter and setter methods for name and age. The getter methods
allow other classes to retrieve the values of these variables, while
the setter methods allow other classes to modify their values.
Note that we can also add validation logic to the setter methods to
ensure that the values being set are valid. In this example, the
setAge method throws an exception if the age is negative.
By using access modifiers and getter and setter methods, we can
achieve encapsulation in Java. This allows us to protect the data and
behavior of our objects and prevent other objects from accessing or
modifying them directly, which makes our code more robust and
maintainable.
What is polymorphism, and how can it be achieved?
Polymorphism is the ability of objects of different classes to be
treated as if they are of the same type. It allows us to write code
that can work with objects of different types in a uniform way,
without needing to know the specific class of each object.
In Java, polymorphism is achieved through two mechanisms:
method overloading and method overriding.
Method overloading is when a class has two or more methods with
the same name, but different parameters. When a method is called,
the compiler determines which method to call based on the number
and types of the arguments passed to it.
public class Calculator {
public int add(int x, int y) {
return x + y;
}
public double add(double x, double y) {
return x + y;
}
}
In this example, the Calculator class has two methods named add,
one that takes two integers and one that takes two doubles. When
the add method is called, the compiler determines which version of
the method to call based on the types of the arguments passed to it.
Method overriding is when a subclass provides its own
implementation of a method that is already defined in its superclass.
The subclass method must have the same name, return type, and
parameter list as the superclass method.
public class Animal {
public void speak() {
System.out.println("Animal speaks");
}
}
public class Dog extends Animal {
public void speak() {
System.out.println("Dog barks");
}
}
In this example, the Animal class has a method named speak. The
Dog class extends the Animal class and provides its own
implementation of the speak method. When we call the speak
method on a Dog object, the Dog version of the method is called
instead of the Animal version.
Polymorphism allows us to write code that can work with objects of
different types in a uniform way, without needing to know the
specific class of each object. It makes our code more flexible and
easier to maintain, and is a key feature of object-oriented
programming.
What are Method Overloading and Overriding?
Method overloading and overriding are two concepts in object-
oriented programming that describe how methods in a class can be
used.
Method overloading is the ability of a class to have multiple methods
with the same name but with different parameters. This is also
known as "compile-time polymorphism" or "function overloading" in
some languages. For example, a class Calculator might have multiple
methods with the name add, but with different parameters such as
add (int a, int b) and add (double a, double b).
Method overriding is the ability of a subclass to provide a different
implementation of a method that is already provided by its
superclass. This is also known as "runtime polymorphism" or
"function overriding". The method in the subclass has the same
name, return type and parameters as the method in the superclass.
The purpose of method overriding is to change the behaviour of the
method in the subclass.
Here's an example of Overloading:
public class Calculator {
public int add(int x, int y) {
return x + y;
}
public int add(int x, int y, int z) {
return x + y + z;
}
public double add(double x, double y) {
return x + y;
}
}
In this example, Calculator defines three different add() methods
with the same name but different parameters. The first method
takes two int arguments, the second takes three int arguments, and
the third takes two double arguments. The compiler decides which
method to call based on the number and type of arguments passed
to it.
Method overriding, on the other hand, is the ability to define a
method in a subclass that has the same name and parameters as a
method in its superclass. When the method is called on an object of
the subclass, the method in the subclass is executed instead of the
method in the superclass.
Here's an example overriding:
public class Animal {
public void makeSound() {
System.out.println("The animal makes a sound");
}
}
public class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("The dog barks");
}
}
In this example, Animal defines a makeSound() method that prints a
message. Dog overrides this method with its own implementation
that prints a different message. When makeSound() is called on a
Dog object, the overridden method in Dog is executed, producing
the output "The dog barks".
What is Method overriding with an example?
In Java, method overriding is when a subclass provides its own
implementation of a method that is already defined in its superclass.
The subclass method must have the same name, return type, and
parameter list as the superclass method.
Access specifiers determine the visibility of a method, and they can
also be used when overriding methods. When overriding a method,
the access specifier of the overriding method cannot be more
restrictive than the access specifier of the overridden method. In
other words, if the overridden method is public, the overriding
method must also be public or less restrictive.
Here is an example of method overriding with access specifiers:
public class Animal {
public void speak() {
System.out.println("Animal speaks");
}
protected void eat() {
System.out.println("Animal eats");
}
}
public class Dog extends Animal {
@Override
public void speak() {
System.out.println("Dog barks");
}
@Override
protected void eat() {
System.out.println("Dog eats");
}
}
In this example, the Animal class has a method named speak that is
public, and a method named eat that is protected. The Dog class
extends the Animal class and provides its own implementations of
the speak and eat methods.
The speak method in the Dog class overrides the speak method in
the Animal class and is also public. The eat method in the Dog class
overrides the eat method in the Animal class and is also protected.
Since the eat method in the Animal class is also protected, the
access specifier of the eat method in the Dog class can be the same
or less restrictive, but not more restrictive.
In this way, we can use method overriding to provide our
implementations of methods that are defined in a superclass, while
also adhering to the access specifiers of the original methods. This
allows us to customize the behavior of our subclasses while
maintaining the structure and visibility of the superclass.
What is Method overriding in terms of exception handling,
with an example?
In Java, when overriding a method, the overridden method can
throw exceptions, and the overriding method can choose to throw
the same exceptions or a subset of them. Here's an example of
method overriding with exception handling:
public class Animal {
public void speak() throws Exception {
System.out.println("Animal speaks");
}
public void eat() throws Exception {
System.out.println("Animal eats");
}
}
public class Dog extends Animal {
@Override
public void speak() throws IOException {
System.out.println("Dog barks");
throw new IOException("Exception from Dog.speak");
}
@Override
public void eat() {
System.out.println("Dog eats");
}
}
In this example, the Animal class has two methods: speak and eat.
Both methods are declared to throw an Exception.
The Dog class extends the Animal class and overrides both the
speak and eat methods.
The speak method in the Dog class overrides the speak method in
the Animal class and throws an IOException. The IOException is a
subclass of Exception, so this is allowed.
The eat method in the Dog class overrides the eat method in the
Animal class but does not throw any exceptions.
When calling these methods, we can catch the exceptions that are
thrown. For example:
public static void main(String[] args) {
Animal animal = new Dog();
try {
animal.speak();
} catch (IOException e) {
System.out.println("Caught IOException: " + e.getMessage());
} catch (Exception e) {
System.out.println("Caught Exception: " + e.getMessage());
}
try {
animal.eat();
} catch (Exception e) {
System.out.println("Caught Exception: " + e.getMessage());
}
}
In this example, we create an instance of the Dog class and assign it
to a variable of type Animal. We then call the speak and eat
methods on this object.
Since the speak method in the Dog class throws an IOException, we
catch that exception specifically and print out its message. If the
speak method in the Dog class threw a different type of exception,
such as RuntimeException, it would not be caught by this catch
block.
The eat method in the Dog class does not throw any exceptions, so
the catch block for Exception will not be executed. If the eat method
in the Dog class did throw an exception, it would be caught by this
catch block.
Can we have overloaded methods with different return
types?
No, we cannot have overloaded methods with only different return
types. Overloaded methods must have the same method name and
parameters, but they can have different return types only if the
method parameters are also different. The reason for this is that the
return type alone is not enough information for the compiler to
determine which method to call at compile time. For example,
consider the following code:
public class Calculator {
public int add(int x, int y) {
return x + y;
}
public double add(int x, int y) {
return (double) (x + y);
}
}
In this example, we have two add() methods with the same
parameters, but different return types (int and double). This will
cause a compilation error because the compiler cannot determine
which method to call based on the return type alone.
Can we override the static method? Why Can't we do that?
No, it is not possible to override a static method in Java. A static
method is associated with the class and not with an object, and it
can be called directly on the class, without the need of creating an
instance of the class.
When a subclass defines a static method with the same signature as
a static method in the superclass, the subclass method is said to
hide the superclass method. This is known as method hiding. The
subclass method is not considered an override of the superclass
method, it is considered a new method and it hides the superclass
method, but it doesn't override it.
In summary, since a static method is associated with the class, not
an object and it is directly callable on the class, it is not possible to
override a static method in Java, instead, it is hidden by subclass
method with the same signature.
What are SOLID principles, with example?
SOLID is an acronym that represents five principles of object-
oriented design that aim to make software more maintainable,
flexible, and easy to understand. Here are the explanations of each
of the five SOLID principles, along with examples:
Single Responsibility Principle (SRP): This principle states that
a class should have only one reason to change. In other words, a
class should have only one responsibility or job. For example, if we
have a class named User, it should only be responsible for handling
user-related functionalities such as authentication, user data
management, etc. It should not be responsible for other unrelated
functionalities like sending emails or managing payment
transactions.
Open/Closed Principle (OCP): This principle states that a class
should be open for extension but closed for modification. This means
that we should be able to add new functionalities or behaviors to a
class without changing its existing code. For example, instead of
modifying an existing Payment class to add support for a new
payment method, we can create a new PaymentMethod class that
implements a Payment interface and inject it into the Payment class.
Liskov Substitution Principle (LSP): This principle states that
derived classes should be able to replace their base classes without
affecting the correctness of the program. In other words, if we have
a base class Animal and a derived class Dog, we should be able to
use the Dog class wherever we use the Animal class. For example, if
we have a method that takes an Animal parameter and performs
some action, we should be able to pass a Dog object to that method
without any issues.
Interface Segregation Principle (ISP): This principle states that
a class should not be forced to implement interfaces it does not use.
In other words, we should separate interfaces that are too large or
general into smaller and more specific interfaces. For example,
instead of having a single Payment interface that includes all
payment methods, we can have separate interfaces like
CreditCardPayment, PayPalPayment, etc.
Dependency Inversion Principle (DIP): This principle states
that high-level modules should not depend on low-level modules.
Instead, both should depend on abstractions. This means that we
should rely on abstractions, rather than concrete implementations.
For example, instead of depending on a specific database
implementation in a class, we should depend on a database
interface, which can be implemented by different databases. This
allows for easier testing, maintenance, and scalability.
What is Cohesion and Coupling?
Cohesion refers to the degree to which the elements within a
module or component work together to achieve a single, well-
defined purpose. High cohesion means that the elements within a
module are strongly related and work together towards a common
goal, while low cohesion means that the elements are loosely related
and may not have a clear purpose.
Coupling, on the other hand, refers to the degree of
interdependence between modules or components. High coupling
means that a change in one module or component will likely affect
other modules or components, while low coupling means that
changes in one module or component will have minimal impact on
other modules or components.
In general, software design principles strive for high cohesion and
low coupling, as this leads to code that is more modular,
maintainable, and easier to understand and change.
What does Static keyword signify?
In Java, the static keyword is used to create variables, methods, and
blocks that belong to the class, rather than to an instance of the
class. When a variable or method is declared as static, it is
associated with the class and not with individual objects of the class.
Here are some common uses of the static keyword:
Static variables: A static variable is a variable that belongs to the
class and is shared by all instances of the class. Static variables are
declared using the static keyword and are often used for constants
or for variables that need to be shared across instances of the class.
public class Example {
public static int count = 0;
}
In this example, the count variable is declared as static, so it belongs
to the Example class and is shared by all instances of the class.
Static methods: A static method is a method that belongs to the
class and can be called without creating an instance of the class.
Static methods are declared using the static keyword and are often
used for utility methods that do not depend on the state of an
instance.
public class Example {
public static void printMessage(String message) {
System.out.println(message);
}
}
In this example, the printMessage() method is declared as static, so
it belongs to the Example class and can be called without creating
an instance of the class.
Static blocks: A static block is a block of code that is executed when
the class is loaded. Static blocks are used to initialize static variables
or to perform other one-time initialization tasks.
public class Example {
static {
System.out.println("Initializing Example class");
}
}
In this example, the static block is executed when the Example class
is loaded and prints a message to the console.
In summary, the static keyword is used to create variables, methods,
and blocks that belong to the class, rather than to an instance of the
class. This allows these elements to be shared by all instances of the
class and to be accessed without creating an instance of the class.
What is the difference between static variable and an
instance variables?
Static variables and instance variables are both types of variables
used in programming, but they differ in their scope and lifetime.
Static variables are declared using the "static" keyword and are
shared across all instances of a class. They are initialized only once,
when the class is loaded, and retain their value throughout the
execution of the program. Static variables are typically used to store
data that is common to all instances of a class, such as a constant or
a count of objects created.
Instance variables, on the other hand, are declared without the
"static" keyword and are unique to each instance of a class. They
are initialized when an object is created and are destroyed when the
object is destroyed. Instance variables are typically used to store
data that is specific to each instance of a class, such as the name or
age of a person.
In summary, the main differences between static variables and
instance variables are:
Scope: Static variables have class scope, while instance variables
have object scope.
Lifetime: Static variables are initialized once and retain their value
throughout the execution of the program, while instance variables
are created and destroyed with the objects they belong to.
Usage: Static variables are used to store data that is common to all
instances of a class, while instance variables are used to store data
that is specific to each instance of a class.
What is Covariant type?
Covariant type refers to the ability to use a subclass type in place of
its superclass type. In other words, it allows a subclass to be used in
place of its superclass. This feature is supported by some
programming languages such as Java and C#.
For example, if class B is a subclass of class A, then an object of
class B can be used wherever an object of class A is expected.
Here is an example in Java:
class A { }
class B extends A { }
A a = new A();
B b = new B();
a = b; // valid
In the above example, the variable "a" is of type A, and the variable
"b" is of type B. However, the assignment "a = b" is valid, because B
is a subclass of A.
Covariant return types allow a method to return a subclass type in
place of its superclass type.
class A { }
class B extends A { }
class C {
A getA() { return new A(); }
B getB() { return new B(); }
}
In the above example, the method getA() returns an object of type
A, and the method getB() returns an object of type B. Because B is a
subclass of A, the method getB() can be overridden to return B
instead of A.
In summary, Covariant type refers to the ability to use a subclass
type in place of its superclass type, this feature is supported by
some programming languages such as Java and C#. It allows a
subclass to be used in place of its superclass and covariant return
types allow a method to return a subclass type in place of its
superclass type.
Can Java interface have no method in it?
An interface without any methods is called a "marker interface". It is
used to mark a class as having some specific behavior or property,
without specifying any methods for that behavior or property.
For example, the Serializable interface in Java has no methods:
public interface Serializable {
}
However, classes that implement the Serializable interface gain the
ability to be serialized and deserialized, which is the behavior that
the Serializable marker interface indicates.
Here's an example of using a marker interface:
In this example, we define a marker interface MyMarkerInterface
with no methods. We then define a class MyClass that implements
the MyMarkerInterface interface. This indicates that MyClass has
some specific behavior or property that is indicated by the
MyMarkerInterface marker interface. However, since
MyMarkerInterface has no methods, MyClass does not need to
implement any methods as a result of implementing the
MyMarkerInterface interface.
What are the exception rules for overriding?
When overriding a method in Java, there are some rules that must
be followed regarding exceptions:
The overriding method can throw the same exceptions as the
overridden method, or any subset of those exceptions.
The overriding method can also throw unchecked exceptions, even if
the overridden method does not.
The overriding method cannot throw checked exceptions that are
not in the same class hierarchy as the exceptions thrown by the
overridden method. This means that the overriding method cannot
throw checked exceptions that are more general than those thrown
by the overridden method. However, it can throw more specific
checked exceptions or unchecked exceptions.
If the overridden method does not throw any exceptions, the
overriding method cannot throw checked exceptions.
Here's an example to illustrate these rules:
class Parent {
void foo() throws IOException {
// implementation goes here
}
}
class Child extends Parent {
// this is a valid override
void foo() throws FileNotFoundException {
// implementation goes here
}
// this is not a valid override
void foo() throws Exception {
// implementation goes here
}
// this is a valid override
void foo() throws RuntimeException {
// implementation goes here
}
// this is not a valid override
void foo() throws SQLException {
// implementation goes here
}
// this is a valid override, since it does not throw any exceptions
void foo() {
// implementation goes here
}
}
In this example, Parent has a method foo() that throws an
IOException. Child overrides this method and follows the rules for
exception handling:
The first override is valid, since FileNotFoundException is a subclass
of IOException.
The second override is not valid, since Exception is a more general
exception than IOException.
The third override is valid, since RuntimeException is an unchecked
exception and can be thrown even if the overridden method does
not throw any exceptions.
The fourth override is not valid, since SQLException is not in the
same class hierarchy as IOException.
The fifth override is valid, since it does not throw any exceptions.
What are the different types of access modifiers?
There are four types of access modifiers in Java:
public: The public access modifier is the most permissive access
level, and it allows access to a class, method, or variable from any
other class, regardless of whether they are in the same package or
not.
protected: The protected access modifier allows access to a class,
method, or variable from within the same package, as well as from
any subclass, even if they are in a different package.
default (no modifier): If no access modifier is specified, then the
class, method, or variable has package-level access. This means that
it can be accessed from within the same package, but not from
outside the package.
private: The private access modifier is the most restrictive access
level, and it allows access to a class, method, or variable only from
within the same class. It cannot be accessed from any other class,
even if they are in the same package.
Here is an example of how access modifiers can be used:
public class MyClass {
public int publicVar;
protected int protectedVar;
int defaultVar;
private int privateVar;
public void publicMethod() {
}
protected void protectedMethod() {
}
void defaultMethod() {
}
private void privateMethod() {
}
}
In this example, the MyClass class has four instance variables and
four instance methods, each with a different access modifier. The
publicVar and publicMethod() can be accessed from any other class,
while protectedVar and protectedMethod() can be accessed from any
subclass and from within the same package. defaultVar and
defaultMethod() can be accessed from within the same package
only, while privateVar and privateMethod() can be accessed only
from within the same class.
What is the difference between private and protected access
modifiers?
The main difference between private and protected access modifiers
in Java is that private members are only accessible within the same
class, while protected members are accessible within the same class
and its subclasses, as well as within the same package.
Here are some more differences between private and protected:
Visibility: Private members are only visible within the same class,
while protected members are visible within the same class and its
subclasses, as well as within the same package.
Access: Private members cannot be accessed outside the class, while
protected members can be accessed by subclasses and other classes
in the same package.
Inheritance: Private members are not inherited by subclasses, while
protected members are inherited by subclasses.
Overriding: Private members cannot be overridden in subclasses,
while protected members can be overridden in subclasses.
Here is an example to illustrate the difference between private and
protected access modifiers:
public class MyClass {
private int privateVar;
protected int protectedVar;
public void myMethod() {
privateVar = 1; // OK, can be accessed within the same class
protectedVar = 2; // OK, can be accessed within the same
class
}
}
public class MySubclass extends MyClass {
public void mySubMethod() {
// privateVar = 3; // Error, cannot be accessed in subclass
protectedVar = 4; // OK, can be accessed in subclass
}
}
public class MyOtherClass {
public void myOtherMethod() {
MyClass obj = new MyClass();
// obj.privateVar = 5; // Error, cannot be accessed outside
//the class
// obj.protectedVar = 6; // Error, cannot be accessed //outside
the package or subclass
}
}
In this example, we have a class MyClass with a private variable
privateVar and a protected variable protectedVar. The myMethod()
method of MyClass can access both variables. We also have a
subclass MySubclass of MyClass, which can access the protectedVar
variable, but not the privateVar variable. Finally, we have another
class MyOtherClass, which cannot access either variable, because
they are not visible outside the class or its package.
What is the use of protected members?
Protected members in Java are used to provide access to class
members within the same package and to subclasses of the class,
even if they are in a different package. The protected access
modifier is more restrictive than public, but less restrictive than
private.
The protected access modifier is useful when you want to expose
certain methods or variables to subclasses, while still hiding them
from other classes in the same package or outside the package. For
example, you might have a superclass with some variables and
methods that are not intended to be used outside the class or
package, but should be accessible to subclasses. In this case, you
can declare those variables and methods as protected.
Here is an example of how protected members can be used:
package com.example.package1;
public class Superclass {
protected int protectedVar;

protected void protectedMethod() {


// ...
}
}
package com.example.package2;
import com.example.package1.Superclass;
public class Subclass extends Superclass {
public void someMethod() {
protectedVar = 42;
protectedMethod();
}
}
In this example, we have two packages, com.example.package1 and
com.example.package2. Superclass is defined in package1, and has
a protected variable protectedVar and a protected method
protectedMethod(). Subclass is defined in package2 and extends
Superclass. In the someMethod() method of Subclass, we can access
protectedVar and protectedMethod() from Superclass, even though
they are protected, because Subclass is a subclass of Superclass.
CHAPTER 3: CORE JAVA
This chapter deals with Core Java Interview question, All the
questions that I am writing here throughout the books are really
important. These are the question those are appeared in an
interview.
Even with questions you will get the idea like which topic is
important and which is not. As an interviewee we should be
focussing on the hot topics to prepare better.
In Core Java, these topics are most important.

String
Collection framework (HashMap, Concurrent HashMap)
Concepts of immutability
Exception
Serialization
Garbage collectors
Multithreading
Executor Framework
Lambdas
Stream
Java 8 all new features
Optional
Functional interface

What is JIT in java?


JIT stands for "Just-In-Time" compiler in Java. It is a feature of the
Java Virtual Machine (JVM) that improves the performance of Java
applications by compiling bytecode into native machine code at
runtime. The JIT compiler examines the bytecode of a method and
compiles it into machine code if it determines that the method is
executed frequently. This allows for faster execution of the compiled
code, rather than interpreting the bytecode each time it is executed.

You might also like