Java Interview Questions
Java Polymorphism - Theory Questions
1. What is polymorphism in Java and what are its types?
Polymorphism is one of the fundamental OOP concepts that allows objects of different classes to be treated as objects of a common superclass. The term means "many forms". In Java, polymorphism has two main types: Compile-time polymorphism (static polymorphism) achieved through method overloading, and Runtime polymorphism (dynamic polymorphism) achieved through method overriding and inheritance.
2. Explain compile-time polymorphism vs runtime polymorphism.
Compile-time polymorphism is resolved during compilation through method overloading. The compiler determines which method to call based on method signature. Runtime polymorphism is resolved during execution through method overriding. The JVM determines which method to call based on the actual object type at runtime. Compile-time polymorphism is faster but less flexible, while runtime polymorphism provides more flexibility but has slight performance overhead.
3. What is method overloading and what are its rules?
Method overloading is a compile-time polymorphism feature where multiple methods in the same class have the same name but different parameters. Rules include: Methods must have the same name but different parameter lists (different type, number, or order), Return type can be same or different, Access modifiers can be different, Methods can throw different exceptions. Overloaded methods are bound at compile time based on reference type.
4. What is method overriding and what are its rules?
Method overriding is a runtime polymorphism feature where a subclass provides a specific implementation of a method already defined in its superclass. Rules include: Method must have same name and parameters as parent method, Return type should be same or covariant (subtype), Access modifier cannot be more restrictive, @Override annotation is recommended, Final methods cannot be overridden, Static methods cannot be overridden (only hidden), and Constructors cannot be overridden.
5. What is the difference between method overloading and method overriding?
Key differences: Overloading occurs in same class, while overriding occurs in parent-child classes. Overloading changes parameters, while overriding changes implementation. Overloading is compile-time polymorphism, while overriding is runtime polymorphism. Overloading doesn't require inheritance, while overriding requires inheritance. Return type can differ in overloading but must be same/covariant in overriding.
6. Explain the concept of dynamic method dispatch in Java.
Dynamic method dispatch is the mechanism by which a call to an overridden method is resolved at runtime rather than compile time. When a superclass reference variable refers to a subclass object, and an overridden method is called through this reference, Java determines which version of the method to execute based on the actual object type at runtime. This is the foundation of runtime polymorphism and allows for flexible, extensible code design.
7. What is the role of the 'super' keyword in polymorphism?
The 'super' keyword is used in polymorphism to: Access overridden methods of the parent class from the child class, Call parent class constructor from child class constructor, and Access parent class fields when they are hidden by child class fields. It ensures that even when methods are overridden, the parent class implementation can still be accessed when needed, maintaining the inheritance hierarchy while allowing customization.
8. Can we override static methods in Java? Why or why not?
No, static methods cannot be overridden in Java. Static methods are bound at compile time based on the reference type, not the actual object type. If a subclass defines a static method with the same signature as a static method in the parent class, it's called method hiding, not overriding. The method called depends on the reference type, not the actual object type. This is because static methods belong to the class, not to instances.
9. What is covariant return type in method overriding?
Covariant return type, introduced in Java 5, allows an overriding method to have a return type that is a subclass of the return type of the overridden method. For example, if a parent class method returns Animal, the child class overriding method can return Dog (where Dog extends Animal). This provides more specific return types in overridden methods while maintaining type safety and making the API more intuitive.
10. Can we override private methods in Java?
No, private methods cannot be overridden because they are not visible to subclasses. If a subclass defines a method with the same name as a private method in the parent class, it's treated as a completely new method in the subclass, not an override. This is because private methods have class-level visibility only and are not part of the class's public API that can be inherited and overridden by subclasses.
11. What is the @Override annotation and why is it important?
The @Override annotation is a compile-time annotation that indicates a method is intended to override a method in the superclass. It's important because: It helps catch errors at compile time if the method doesn't actually override anything, It improves code readability by clearly indicating overriding methods, and It helps maintain code by making the developer's intent explicit. While optional, using @Override is considered a best practice.
12. How does polymorphism work with interfaces?
Polymorphism with interfaces allows objects of different classes that implement the same interface to be treated uniformly through interface references. An interface reference can point to any object that implements that interface, and method calls are resolved to the actual implementation at runtime. This enables loose coupling and allows for flexible system design where implementations can be swapped without affecting client code that uses the interface.
13. What is the difference between early binding and late binding?
Early binding (static binding) occurs at compile time where the method to be called is determined during compilation. This happens with static, final, and private methods and method overloading. Late binding (dynamic binding) occurs at runtime where the method to be called is determined based on the actual object type. This happens with instance method overriding. Early binding is faster but less flexible, while late binding enables polymorphism but has slight performance cost.
14. Can constructors be overloaded or overridden?
Constructors can be overloaded but cannot be overridden. Constructor overloading means having multiple constructors in the same class with different parameters. Constructor overriding is not possible because constructors are not inherited by subclasses. Each class, including subclasses, must define its own constructors. However, subclass constructors can call superclass constructors using the super() keyword to reuse initialization logic.
15. What is the real-world significance of polymorphism?
Polymorphism has significant real-world benefits: Code reusability - write once, use many times, Extensibility - easily add new functionality, Maintainability - changes in one part don't affect others, Loose coupling - reduces dependencies between components, and Flexible design - allows working with general types rather than specific implementations. For example, a draw() method can work with any Shape without knowing if it's a Circle, Square, or Triangle.
16. How does polymorphism relate to abstraction and inheritance?
Polymorphism works closely with abstraction and inheritance: Inheritance provides the hierarchy that enables polymorphism by creating parent-child relationships. Abstraction (through abstract classes and interfaces) defines the contracts that enable polymorphic behavior. Polymorphism leverages inheritance to allow objects to be treated as their parent types and uses abstraction to define common behaviors that can have multiple implementations.
17. What are the performance implications of polymorphism?
Polymorphism has some performance considerations: Runtime polymorphism has a slight performance overhead due to dynamic method lookup (virtual method table), Compile-time polymorphism has no runtime overhead as resolution happens during compilation, Modern JVMs use optimizations like inline caching to minimize polymorphism overhead, and The benefits of flexible, maintainable code usually outweigh the minor performance costs in most applications.
18. Can we achieve polymorphism without inheritance?
Yes, polymorphism can be achieved without inheritance through: Interfaces - classes implementing the same interface can be used polymorphically, Method overloading - compile-time polymorphism within the same class, and Duck typing patterns (though not natively supported in Java). However, inheritance-based polymorphism (using abstract classes and concrete subclasses) is the most common and fundamental approach in Java.
19. What is the "Liskov Substitution Principle" in polymorphism?
The Liskov Substitution Principle (LSP) states that objects of a superclass should be replaceable with objects of its subclasses without affecting the correctness of the program. This means subclasses should: Not strengthen preconditions, Not weaken postconditions, Preserve invariants of the superclass, and Only throw exceptions that are the same or subtypes of exceptions thrown by the superclass method. Violating LSP can break polymorphism and lead to unexpected behavior.
20. What are the best practices for using polymorphism in Java?
Best practices include: Use interfaces and abstract classes for defining polymorphic behavior, Follow Liskov Substitution Principle, Use @Override annotation consistently, Prefer composition over inheritance when appropriate, Keep method signatures consistent in inheritance hierarchy, Use design patterns that leverage polymorphism (Factory, Strategy, Template Method), and Document polymorphic behavior and expectations clearly.