Advanced Object-Oriented Programming in Java: Static Members and Inheritance
Static Members
The static keyword in Java is used to modify member variables and methods, creating class-level members.
Static Member Variables
Member variables in Java are categorized into two types based on the presence of the static modifier: class variables and instance variables.
- Class Variables: Belong to the class itself. Only one copy exists in memory, shared by all instances. Accessed via
ClassName.variableName. - Instance Variables: Belong to individual objects. Each object has its own copy. Accessed via
object.variableName.
Memory representation shows class variables stored in the method area, while instance variables reside within individual heap objects.
Summary:
- Class variables: Single copy per class, accessed by class name.
- Instance variibles: One copy per object, accessed by object reference.
Application of Static Member Variables
Static variables are ideal for data that should be singular and shared across all instances, such as counters or configuration settings.
Example: Tracking Object Creation
public class UserAccount {
public static int totalAccounts;
public UserAccount() {
UserAccount.totalAccounts++;
}
}
public class ApplicationTest {
public static void main(String[] args) {
new UserAccount();
new UserAccount();
new UserAccount();
new UserAccount();
System.out.println("Total UserAccount objects created: " + UserAccount.totalAccounts);
}
}
Output: Total UserAccount objects created: 4
Static Member Methods
Methods are similarly divided:
- Class Methods: Modified with
static. Belong to the class, invoked viaClassName.methodName(). - Instance Methods: No
staticmodifier. Belong to objects, invoked viaobject.methodName().
Example:
public class StudentRecord {
double examScore;
// Class method
public static void displayWelcome() {
System.out.println("Welcome!");
System.out.println("Welcome!");
}
// Instance method
public void showResult() {
System.out.println(examScore >= 60 ? "Pass" : "Fail");
}
}
public class TestDemo {
public static void main(String[] args) {
// Invoke class method
StudentRecord.displayWelcome();
// Invoke instance method
StudentRecord record = new StudentRecord();
record.showResult();
// Not recommended: calling class method via object
record.displayWelcome();
}
}
Memory Principle:
- Class methods load with the class, enabling direct class invocation.
- Instance methods may access instance variables, requiring object creation first.
Utility Classes
A utility class contains only static methods, providing convenient functionality accessible via the class name.
Example: Verification Code Generator
import java.util.Random;
public final class CodeGenerator {
// Private constructor to prevent instantiation
private CodeGenerator() {}
public static String generateCode(int length) {
StringBuilder codeBuilder = new StringBuilder();
String characters = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
Random randomizer = new Random();
for (int i = 0; i < length; i++) {
int index = randomizer.nextInt(characters.length());
codeBuilder.append(characters.charAt(index));
}
return codeBuilder.toString();
}
}
// Usage in different contexts
public class LoginScreen {
public static void main(String[] args) {
System.out.println(CodeGenerator.generateCode(6));
}
}
public class RegistrationForm {
public static void main(String[] args) {
System.out.println(CodeGenerator.generateCode(8));
}
}
Static Usage Notes
public class Student {
static String university; // Class variable
double grade; // Instance variable
// Class method: can access class members, not instance members
public static void showMessage() {
university = "Tech University"; // Valid
displayStatic(); // Valid
// System.out.println(grade); // Invalid
// displayInstance(); // Invalid
// System.out.println(this); // Invalid
}
public static void displayStatic() {}
// Instance method: can access both class and instance members
public void displayInstance() {
university = "Tech University"; // Valid
showMessage(); // Valid
System.out.println(grade); // Valid
displayPrivate(); // Valid
System.out.println(this); // Valid
}
private void displayPrivate() {}
}
Static Code Blocks
Code blocks can be static or instance-based.
Static Code Block: Executes once when the class loads.
public class Course {
static int maxStudents = 100;
static String department;
static {
System.out.println("Static block executed.");
department = "Computer Science";
}
}
public class UniversityTest {
public static void main(String[] args) {
System.out.println(Course.maxStudents);
System.out.println(Course.maxStudents);
System.out.println(Course.department); // "Computer Science"
}
}
Instance Code Block: Executes before each constructor call during object creation.
public class Employee {
int yearsOfService;
// Instance code block
{
System.out.println("Instance block executed.");
yearsOfService = 1;
System.out.println("Object created: " + this);
}
public Employee() {
System.out.println("Default constructor executed.");
}
public Employee(String name) {
System.out.println("Parameterized constructor executed.");
}
}
public class CompanyTest {
public static void main(String[] args) {
Employee emp1 = new Employee();
Employee emp2 = new Employee("Alice");
System.out.println(emp1.yearsOfService); // 1
System.out.println(emp2.yearsOfService); // 1
}
}
Singleton Design Pattern
Design patterns represent optimal solutions to common programming problems. The Singleton pattern ensures a class has only one instance.
Inheritance
Inheritance is a fundamental OOP feature enabling code reuse and hierarchical class relationships.
Quick Start with Inheritance
public class BaseClass {
public int publicField;
public void publicMethod() {
System.out.println("---publicMethod---");
}
private int privateField;
private void privateMethod() {
System.out.println("---privateMethod---");
}
}
public class DerivedClass extends BaseClass {
public void additionalMethod() {
// Can access inherited public members
System.out.println(publicField); // Valid
publicMethod(); // Valid
// Cannot access private members
// System.out.println(privateField); // Invalid
// privateMethod(); // Invalid
}
}
public class InheritanceTest {
public static void main(String[] args) {
DerivedClass obj = new DerivedClass();
System.out.println(obj.publicField); // Valid
obj.publicMethod(); // Valid
// System.out.println(obj.privateField); // Invalid
// obj.privateMethod(); // Invalid
}
}
A subclass object contains both its own members and inherited public members from the parent class.
Benefits of Inheritance
Primary benefit: Code reusability. Common attributes and behaviors can be defined in a parent class.
Example:
public class Person {
private String fullName;
public String getFullName() {
return fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
}
public class Instructor extends Person {
private String expertise;
public String getExpertise() {
return expertise;
}
public void setExpertise(String expertise) {
this.expertise = expertise;
}
public void displayInfo() {
System.out.println(getFullName() + " specializes in: " + expertise);
}
}
public class SchoolTest {
public static void main(String[] args) {
Instructor teacher = new Instructor();
teacher.setFullName("Dr. Smith");
teacher.setExpertise("Data Structures");
System.out.println(teacher.getFullName());
System.out.println(teacher.getExpertise());
teacher.displayInfo();
}
}
Access Modifiers
Access modifiers control member visibility:
private: Class only- Default (no modifier): Class and same package
protected: Class, same package, and subclassespublic: Anywhere
public class Parent {
private void privateFunc() {
System.out.println("private");
}
void defaultFunc() {
System.out.println("default");
}
protected void protectedFunc() {
System.out.println("protected");
}
public void publicFunc() {
System.out.println("public");
}
public void testAccess() {
privateFunc(); // Valid
defaultFunc(); // Valid
protectedFunc(); // Valid
publicFunc(); // Valid
}
}
// In same package
public class AccessDemo {
public static void main(String[] args) {
Parent p = new Parent();
// p.privateFunc(); // Invalid
p.defaultFunc(); // Valid
p.protectedFunc(); // Valid
p.publicFunc(); // Valid
}
}
Single Inheritence and Object Class
Java supports single inheritance (one direct parent) but allows multi-level inheritance. All classes implicitly inherit from Object.
class LevelA {}
class LevelB extends LevelA {}
// class LevelC extends LevelB, LevelA {} // Invalid
class LevelD extends LevelB {}
public class InheritanceDemo {
public static void main(String[] args) {
LevelA a = new LevelA();
LevelB b = new LevelB();
java.util.ArrayList list = new java.util.ArrayList();
list.add("Java");
System.out.println(list.toString()); // Inherited from Object
}
}
Method Overriding
Subclasses can override inherited methods to provide specialized implementations.
public class Shape {
public void draw() {
System.out.println("Drawing shape");
}
public void resize(int width, int height) {
System.out.println("Resizing shape");
}
}
public class Circle extends Shape {
@Override
public void draw() {
System.out.println("Drawing circle");
}
@Override
public void resize(int width, int height) {
System.out.println("Resizing circle");
}
}
public class DrawingTest {
public static void main(String[] args) {
Circle circle = new Circle();
circle.draw(); // "Drawing circle"
circle.resize(10, 10); // "Resizing circle"
}
}
Rules:
- Use
@Overrideannotation - Overriding method access must be equal or broader
- Return type must match or be a subclass
- Private and static methods cannot be overridden
Comon Application: Overriding Object.toString()
public class Product {
private String itemName;
private double price;
public Product(String itemName, double price) {
this.itemName = itemName;
this.price = price;
}
@Override
public String toString() {
return "Product{name='" + itemName + "', price=" + price + "}";
}
}
public class StoreTest {
public static void main(String[] args) {
Product p = new Product("Laptop", 999.99);
System.out.println(p); // Calls overridden toString()
}
}
Member Access Characteristics in Subclasses
Proximity Princpile: When accessing members, the closest declaration is used.
public class Vehicle {
String type = "Vehicle";
public void start() {
System.out.println("Vehicle starting");
}
}
public class Car extends Vehicle {
String type = "Car";
public void showType() {
String type = "Local Car";
System.out.println(type); // "Local Car"
System.out.println(this.type); // "Car"
System.out.println(super.type); // "Vehicle"
}
@Override
public void start() {
System.out.println("Car starting");
}
public void demonstrate() {
start(); // "Car starting"
super.start(); // "Vehicle starting"
}
}
public class GarageTest {
public static void main(String[] args) {
Car myCar = new Car();
myCar.showType();
myCar.demonstrate();
}
}
Constructor Access in Subclasses
Rules:
- Subclass constructors implicitly call
super()first - Execution order: Parent constructor → Subclass constructor
Explicit Parent Constructor Call:
public class Animal {
private String species;
public Animal(String species) {
this.species = species;
System.out.println("Animal constructor: " + species);
}
}
public class Mammal extends Animal {
private boolean hasFur;
public Mammal(String species, boolean hasFur) {
super(species); // Must be first statement
this.hasFur = hasFur;
System.out.println("Mammal constructor: fur=" + hasFur);
}
}
public class ZooTest {
public static void main(String[] args) {
Mammal dog = new Mammal("Canine", true);
}
}
Calling Own Constructors:
public class Rectangle {
private int width;
private int height;
public Rectangle() {
this(1, 1); // Calls parameterized constructor
}
public Rectangle(int width, int height) {
this.width = width;
this.height = height;
}
}
Summary of this and super:
// Accessing own members
this.memberVariable
this.memberMethod()
this() // Own no-arg constructor
this(parameters) // Own parameterized constructor
// Accessing parent members
super.memberVariable
super.memberMethod()
super() // Parent no-arg constructor
super(parameters) // Parent parameterized constructor
// Note: Constructor calls must be the first statement in a constructor.