Java OO Interview Questions and Answers

If you asked me to pick a section that is most popular with the interviewers, this is it. If you don't perform well in Object Oriented (i.e. OO) programming , your success rate in interviews will be very low. Good interviewers will be getting you to analyze or code for a particular scenario. They will be observing your decisions with interfaces and classes, and question your decisions to ascertain your technical skills, analytical skills, and communication skills. You can't memorize your answers. This section requires some level of experience to fully understand.

In this blog, I cover some OO interview questions and answers. If you are interested in more questions and answers, the "Core Java Career Essentials" book has a whole chapter dedicated for OO questions and answers with enough examples to get through your OO interview questions with flying colors.


Q. How do you know that your classes are badly designed? 
A. 

  • If your application is fragile – when making a change, unexpected parts of the application can break.
  • If your application is rigid – it is hard to change one part of the application without affecting too many other parts.
  • If your application is immobile – it is hard to reuse the code in another application because it cannot be separated.

Overly complex design is as bad as no design at all. Get the granularity of your classes and objects right without overly complicating them. Don't apply too many patterns and principles to a simple problem. Apply them only when they are adequate. Don't anticipate changes in requirements ahead of time. Preparing for future changes can easily lead to overly complex designs. Focus on writing code that is not only easy to understand, but also flexible enough so that it is easy to change if the requirements change. 


Q. Can you explain if the following classes are badly designed? 
The following snippets design the classes & interfaces for the following scenario. Bob, and Jane work for a restaurant. Bob works as manager and a waiter. Jane works as a waitress. A waiter's behavior is to take customer orders and a manager's behavior is to manage employees.


?
1
2
3
package badrestaurant;
 
public interface Person {}

?
1
2
3
4
5
package badrestaurant;
 
public interface Manager extends Person {
    public void managePeople( );
}

?
1
2
3
4
5
package badrestaurant;
 
public interface Waiter extends Person {
    public void takeOrders( );
}


?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package badrestaurant;
 
public class Bob implements Manager, Waiter {
 
    @Override
    public void managePeople( ) {
        //implementation goes here
    }
 
    @Override
    public void takeOrders( ) {
        //implementation goes here
    }
}

?
1
2
3
4
5
6
7
8
9
package badrestaurant;
 
public class Jane implements Waiter {
 
    @Override
    public List<string> takeOrders( ) {
        //implementation goes here
    }
}

The Restaurant class uses the above classes as shown below.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package badrestaurant;
 
public class Restaurant {
     
    public static void main(String[ ] args) {
         
        Bob bob = new Bob( );
        bob.managePeople( );
        bob.takeOrders( );
         
        Jane jane = new Jane( );
        jane.takeOrders( );
    }
}

A. The above classes are badly designed for the reasons described below.

The name should be an attribute, and not a class like Bob or Jane. A good OO design should hide non-essential details through abstraction. If the restaurant employs more persons, you don't want the system to be inflexible and create new classes like Peter, Jason, etc for every new employee.

The above solution's incorrect usage of the interfaces for the job roles like Waiter, Manager, etc will make your classes very rigid and tightly coupled by requiring static structural changes. What if Bob becomes a full-time manager? You will have to remove the interface Waiter from the class Bob. What if Jane becomes a manager? You will have to change the interface Waiter with Manager.

The above drawbacks in the design can be fixed as shown below by asking the right questions. Basically waiter, manager, etc are roles an employee plays. You can abstract it out as shown below.


?
1
2
3
4
5
6
package goodrestuarant;
 
public interface Role {
    public String getName( );
    public void perform( );
}

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package goodrestuarant;
 
public class Waiter implements Role {
     
    private String roleName;
     
    public Waiter(String roleName) {
        this.roleName = roleName;
    }
 
    @Override
    public String getName( ) {
       return this.roleName;
    }
 
    @Override
    public void perform( ) {
       //implementation goes here
    }
}

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package goodrestuarant;
 
public class Manager implements Role {
     
    private String roleName;  
     
    public Manager(String roleName) {
        this.roleName = roleName;
    }
 
    @Override
    public String getName( ) {
       return this.roleName;
    }
 
    @Override
    public void perform( ) {
       //implementation goes here
    }
}


The Employee class defines the employee name as an attribute as opposed to a class. This makes the design flexible as new employees can be added at run time by instantiating new Employee objects with appropriate names. This is the power of abstraction. You don't have to create new classes for each new employee. The roles are declared as a list using aggregation (i.e. containment), so that new roles can be added or existing roles can be removed at run time as the roles of employees change. This makes the design more flexible. 



?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package goodrestuarant;
 
import java.util.ArrayList;
import java.util.List;
 
public class Employee {
     
    private String name;
    private List<role> roles = new ArrayList<role>(10);
     
    public Employee(String name){
        this.name = name;
    }
 
    public String getName( ) {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public List<role> getRoles( ) {
        return roles;
    }
 
    public void setRoles(List<role> roles) {
        this.roles = roles;
    }
     
    public void addRole(Role role){
        if(role == null){
            throw new IllegalArgumentException("Role cannot be null");
        }
        roles.add(role);
    }
     
    public void removeRole(Role role){
        if(role == null){
            throw new IllegalArgumentException("Role cannot be null");
        }
        roles.remove(role);
    }
}

The following Restaurant class shows how flexible, extensible, and maintainable the above design is.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package goodrestuarant;
 
import java.util.List;
 
public class Restaurant {
     
    public static void main(String[ ] args) {
         
        Employee emp1 = new Employee ("Bob");
        Role waiter = new Waiter("waiter");
        Role manager = new Manager("manager");
         
        emp1.addRole(waiter);
        emp1.addRole(manager);
         
        Employee emp2 = new Employee("Jane");
        emp2.addRole(waiter);
         
        List<role> roles = emp1.getRoles( );
        for (Role role : roles) {
            role.perform( );
        }
         
        //you can add more employees or change roles based on
        //conditions here at runtime. More flexible.  
    }
}

Q. What do you achieve through good class and interface design?
A. 

  • Loosely coupled classes, objects, and components enabling your application to easily grow and adapt to changes without being rigid or fragile.
  • Less complex and reusable code that increases maintainability, extendability and testability.

Q. What are the 3 main concepts of OOP?
A. Encapsulation, polymorphism, and inheritance are the 3 main concepts or pillars of an object oriented programming. Abstraction is another important concept that can be applied to both object oriented and non object oriented programming. [Remember: a pie ? abstraction, polymorphism, inheritance, and encapsulation.]


Q. What problem(s) does abstraction and encapsulation solve?
A. Both abstraction and encapsulation solve same problem of complexity in different dimensions. Encapsulation exposes only the required details of an object to the caller by forbidding access to certain members, whereas an abstraction not only hides the implementation details, but also provides a basis for your application to grow and change over a period of time. For example, if you abstract out the make and model of a vehicle as class attributes as opposed to as individual classes like Toyota, ToyotaCamry, ToyotaCorolla, etc, you can easily incorporate new types of cars at runtime by creating a new car object with the relevant make and model as arguments as opposed to having to declare a new set of classes.


Q. How would you go about designing a “farm animals” application where animals like cow, pig, horse, etc move from a barn to pasture, a stable to paddock, etc? The solution should also cater for extension into other types of animals like circus animals, wild animals, etc in the future.


A. 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package subclass0;
 
public abstract class Animal {
    private int id;                                // id is encapsulated
 
    public Animal(int id) {
        this.id = id;
    }
 
    public int getId( ) {
        return id;
    }
 
    public abstract void move(Location location);
}

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package subclass0;
 
public class FarmAnimal extends Animal {
 
    private Location location = null;                   // location is encapsulated
 
    public FarmAnimal(int id, Location defaultLocation) {
        super(id);
        validateLocation(defaultLocation);
        this.location = defaultLocation;
    }
 
    public Location getLocation( ) {
        return location;
    }
 
    public void move(Location location) {
        validateLocation(location);
        System.out.println("Id=" + getId( ) + " is moving from "
                + this.location + " to " + location);
        this.location = location;
    }
 
    private void validateLocation(Location location) {
        if (location == null) {
            throw new IllegalArgumentException("location=" + location);
        }
    }
}

?
1
2
3
4
5
package subclass0;
 
public enum Location  {
    Barn, Pasture, Stable, Cage, PigSty, Paddock, Pen
}


?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package subclass0;
 
public class Example {
 
    public static void main(String[ ] args) {
        Animal pig = new FarmAnimal(1, Location.Barn);
        Animal horse = new FarmAnimal(2, Location.Stable);
        Animal cow = new FarmAnimal(3, Location.Pen);
 
        pig.move(Location.Paddock);
        horse.move(Location.Pen);
        cow.move(Location.Pasture);
    }

Output:

Id=1 is moving from Barn to Paddock
Id=2 is moving from Stable to Pen
Id=3 is moving from Pen to Pasture 

In the above example, the class FarmAnimal is an abstraction used in place of an actual farm animal like horse, pig, cow, etc. In future, you can have WildAnimal, CircusAnimal, etc extending the Animal class to provide an abstraction for wild animals like zebra, giraffe, etc and circus animals like lion, tiger, elephant, etc respectively. An Animal is a further abstraction generalizing FarmAnimal, WildAnimal, and CircusAnimal. The Location is coded as an enumeration for simplicity. The Location itself can be an abstract class or an interface providing an abstraction for OpenLocation, EnclosedLocation, and SecuredLocation further abstracting specific location details like barn, pen, pasture, pigsty, stable, cage, etc. The location details can be represented with attributes like “name”, “type”, etc. 

The FarmAnimal class is also well encapsulated by declaring the attribute “location” as private. Hence the “location” variable cannot be directly accessed. Assignment is only allowed through the constructor and move(Location location) method, only after a successful precondition check with the validateLocation(...) method. The validateLocation(...) itself marked private as it is an internal detail that does not have to be exposed to the caller. In practice, the public move(..) method can make use of many other private methods that are hidden from the caller. The caller only needs to know what can be done with an Animal. For example, they can be moved from one location to another. The internal details as to how the animals are moved is not exposed to the caller. These implementation details are specific to FarmAnimal, WildAnimal, and CircusAnimal classes. 

Comments

Popular posts from this blog

SQL Tutorial with HSQLDB

Hibernate tutorial with HSQLDB

Java Interview Questions & Answers: user defined key class