Classes, Objects, Methods, and ConstructorsIntroductionObject-oriented programming (OOP) is a programming paradigm in which software is organized around objects that combine data with the operations that act on that data. Java and Python are both object-oriented languages, although they express object orientation differently. Java emphasizes explicit types, class declarations, access modifiers, and compile-time checking. Python uses dynamic typing, concise syntax, and a highly flexible object model in which classes and functions are themselves objects. Classes, objects, methods, and constructors form the foundation of OOP. A class defines a type and describes the state and behavior that its objects may possess. An object is a concrete runtime instance of a class. Fields or attributes represent state, while methods represent behavior. Constructors initialize new objects and establish their initial invariants. These concepts are not merely syntax. They support abstraction, encapsulation, reuse, maintainability, and the modeling of domain entities such as users, orders, products, accounts, and services. A sound understanding is essential for full-stack development because classes appear in application logic, web frameworks, persistence layers, APIs, testing, and enterprise systems. Learning ObjectivesBy the end of this chapter, you should be able to: - define classes and instantiate objects in Java and Python;
- distinguish fields, attributes, properties, methods, and class-level members;
- implement constructors and explain object initialization;
- analyze method signatures and parameter passing;
- use instance, class, and static methods appropriately;
- compare object identity and value equality;
- explain object lifecycle behavior and resource-management concerns;
- apply encapsulation and sound class-design practices to a realistic problem.
1. The Class–Object ModelA class is a blueprint or type definition. It specifies the data an object may hold and the operations it supports. An object is an instance created from that class. If BankAccount is a class, each individual account is an object with its own balance and account number. A class commonly contains: - Fields or attributes: data associated with the object or class.
- Methods: operations that use or modify that data.
- Constructors or initialization methods: mechanisms for establishing initial state.
- Properties: controlled interfaces for reading or changing attributes.
- Class-level members: data or behavior shared by, or associated with, the class itself.
The class defines possible structure; the object provides actual state. Two objects created from the same class can have different values while supporting the same operations. A first exampleJava: public class Book {
private String title;
private String author;
public Book(String title, String author) {
this.title = title;
this.author = author;
}
public String getTitle() {
return title;
}
public void displayInfo() {
System.out.println(title + " by " + author);
}
}
Book book = new Book("Dune", "Frank Herbert");
book.displayInfo();Python: class Book:
def __init__(self, title, author):
self.title = title
self.author = author
def display_info(self):
print(f"{self.title} by {self.author}")
book = Book("Dune", "Frank Herbert")
book.display_info()In both examples, the class describes a book and the variable book refers to an instance. Java requires an explicit type declaration and uses new to create the object. Python calls __init__ during construction and uses the class call Book(...) to create an instance. 2. Fields, Attributes, and EncapsulationA field is a variable declared as part of a class, particularly in Java. In Python, the more general term attribute is used. An attribute can be created dynamically, although well-designed classes normally establish important attributes predictably during initialization. Instance stateInstance state belongs to a particular object. In Java, an instance field is declared without static: public class User {
private String username;
private boolean active;
}Each User object has its own username and active values. In Python, attributes assigned through self are instance attributes: class User:
def __init__(self, username):
self.username = username
self.active = TrueEncapsulationEncapsulation means combining state and behavior while controlling how state is accessed. It protects invariants—conditions that should remain true for a valid object. For example, an account balance should not normally be changed arbitrarily by unrelated code. Java supports encapsulation through access modifiers such as private, package-private, protected, and public. Python does not enforce private fields in the same way. A leading underscore, such as _balance, communicates that an attribute is intended for internal use; double leading underscores trigger name mangling but do not provide absolute security. A controlled Java interface might be: public class BankAccount {
private double balance;
public BankAccount(double openingBalance) {
if (openingBalance < 0) {
throw new IllegalArgumentException("Opening balance cannot be negative");
}
balance = openingBalance;
}
public double getBalance() {
return balance;
}
public void deposit(double amount) {
if (amount <= 0) {
throw new IllegalArgumentException("Deposit must be positive");
}
balance += amount;
}
}The class prevents invalid opening balances and deposits. In financial production systems, monetary values should generally use a decimal type such as Java BigDecimal or Python decimal.Decimal, rather than binary floating-point values, because floating-point representation can introduce rounding effects. 3. PropertiesA property is an attribute-like interface that may perform computation, validation, or controlled access. Java traditionally implements properties with getter and setter methods: public String getEmail() {
return email;
}
public void setEmail(String email) {
if (email == null || email.isBlank()) {
throw new IllegalArgumentException("Email is required");
}
this.email = email;
}Python provides the @property decorator: class Temperature:
def __init__(self, celsius):
self.celsius = celsius
@property
def celsius(self):
return self._celsius
@celsius.setter
def celsius(self, value):
if value < -273.15:
raise ValueError("Temperature cannot be below absolute zero")
self._celsius = valueA property is preferable to unrestricted public state when validation, calculated values, logging, compatibility, or access control may be required. Encapsulation should not produce unnecessary boilerplate, however. A simple immutable value can often be exposed through a clear read-only interface. 4. Methods and Method SignaturesA method is a function associated with a class. Instance methods operate on a particular object. A method’s signature identifies the information needed to call it. In Java, a method signature consists formally of the method name and parameter types; the return type is not part of the signature for overloading purposes. In practical API descriptions, developers commonly discuss the name, parameters, visibility, return type, and exceptions together. Example Java declaration: public int add(int first, int second) {
return first + second;
}Its name is add, its parameter types are int, int, and its return type is int. The parameter names first and second improve readability but do not distinguish overloads. Python does not use compile-time method signatures for overload resolution in the Java sense. A Python method is defined by its name and parameter behavior, including positional-only, keyword-only, default, and variadic parameters. class Formatter:
def format_name(self, first, last, *, uppercase=False):
result = f"{first} {last}"
return result.upper() if uppercase else resultThe makes uppercase keyword-only. Python developers may use default arguments, args, **kwargs, or tools such as functools.singledispatch, but defining two methods with the same name in one class does not create Java-style overloads; the later definition replaces the earlier one. Return values and side effectsA method may return a value, change object state, perform an external action, or combine these behaviors. Methods should have clear responsibilities. A method that both validates an order and sends an email may be difficult to test and reuse. Cohesive methods usually perform one conceptually focused operation. 5. Constructors and InitializationA constructor establishes an object’s initial valid state. In Java, a constructor has the class name and no return type: public class Product {
private final String name;
private final double price;
public Product(String name, double price) {
if (name == null || name.isBlank() || price < 0) {
throw new IllegalArgumentException("Invalid product");
}
this.name = name;
this.price = price;
}
}this refers to the current object. If a class declares no constructor, Java supplies a default no-argument constructor. Once any constructor is explicitly declared, the compiler does not automatically provide that no-argument constructor.
Java supports constructor overloading: public Product(String name) {
this(name, 0.0);
}The call this(name, 0.0) delegates to another constructor and should be the first statement. Python uses __init__ for initialization. Technically, object creation is associated with __new__, which creates the instance, while __init__ initializes it. Most application classes need only __init__: class Product:
def __init__(self, name, price=0.0):
if not name or price < 0:
raise ValueError("Invalid product")
self.name = name
self.price = pricePython does not provide constructor overloading by repeating __init__. Default arguments, class methods, or factory functions are common alternatives. Initialization invariantsAn invariant is a condition that must hold for every valid object. For Product, examples include “name is non-empty” and “price is non-negative.” Initialization is the first opportunity to enforce these conditions. If invalid objects can be created, every later method must defend against them, increasing complexity and risk. 6. Instance, Class, and Static MembersJavaAn instance member belongs to each object. A static field belongs to the class and is shared: public class Ticket {
private static int issuedCount = 0;
private final int number;
public Ticket() {
number = ++issuedCount;
}
public static int getIssuedCount() {
return issuedCount;
}
}issuedCount is shared by all Ticket objects. Static methods cannot directly access instance fields because they do not have a particular object on which to operate.
PythonA class attribute is defined in the class body: class Ticket:
issued_count = 0
def __init__(self):
type(self).issued_count += 1
self.number = type(self).issued_count
@classmethod
def count(cls):
return cls.issued_count
@staticmethod
def valid_number(number):
return isinstance(number, int) and number > 0An instance method receives self; a class method receives cls; a static method receives neither automatically. A class method can serve as an alternative constructor: class User:
def __init__(self, username):
self.username = username
@classmethod
def from_email(cls, email):
return cls(email.split("@")[0])Static methods are appropriate for operations logically related to a class but independent of instance or class state. They should not be used merely to avoid passing dependencies explicitly. 7. Parameter Passing and ReferencesJava is always pass-by-value. For primitive arguments, the value itself is copied. For object arguments, the value copied is a reference to an object. Consequently, a method can mutate the referenced object, but reassigning the parameter does not change the caller’s variable. static void changeName(User user) {
user.setName("A"); // changes the object
user = new User("B"); // changes only the local parameter
}Python’s model is often described as “call by object sharing” or “pass-by-assignment.” A function receives a reference to an object; rebinding a local parameter does not rebind the caller’s variable, but mutation of a mutable object is visible to the caller. def update(items):
items.append("new") # visible mutation
items = [] # local rebinding onlyThe practical design issue is mutability. Passing immutable values such as strings or tuples reduces accidental shared-state changes. When a method should not mutate an input collection, it can copy it or accept an immutable abstraction where appropriate. 8. Equality, Identity, and HashingTwo references may point to the same object, or two distinct objects may contain equivalent values. These are different concepts. - Identity asks whether two references designate the same object.
- Equality asks whether two objects should be considered equivalent by domain rules.
Java uses == for primitive value comparison and reference identity for objects. The .equals() method is used for logical equality. If a class overrides equals, it should normally override hashCode consistently: equal objects must have equal hash codes. Otherwise hash-based collections such as HashMap and HashSet may behave incorrectly. @Override
public boolean equals(Object other) {
if (this == other) return true;
if (!(other instanceof Product p)) return false;
return name.equals(p.name) && Double.compare(price, p.price) == 0;
}In modern Java, records can provide concise value-oriented classes when their semantics fit the problem. Python uses is for identity and == for equality. Classes can implement __eq__: class Point:
def __init__(self, x, y):
self.x, self.y = x, y
def __eq__(self, other):
return isinstance(other, Point) and (self.x, self.y) == (other.x, other.y)If an object is to be hashable, its equality-relevant state must not change while it is used as a dictionary key or set member. Python may make a class unhashable when __eq__ is defined without a compatible __hash__. Mutable value objects should generally not be used as hash keys. 9. Object LifecycleAn object lifecycle includes creation, initialization, use, and eventual reclamation or cleanup. Java uses garbage collection. When an object is no longer reachable, it becomes eligible for collection; programmers should not rely on a particular collection time. The finalize mechanism is obsolete and should not be used for resource management. Files, sockets, database connections, and locks should be closed explicitly, commonly with try-with-resources: try (var reader = Files.newBufferedReader(path)) {
return reader.readLine();
}Python uses reference counting in the standard implementation plus cyclic garbage collection, but language-level code should not rely on immediate destruction. __del__ is unreliable for critical cleanup, especially in cycles or interpreter shutdown. Context managers provide deterministic cleanup: with open("data.txt", encoding="utf-8") as file:
content = file.read()Garbage collection manages memory, not every external resource. The professional rule is to acquire resources deliberately and release them through structured mechanisms. 10. Applied Case: A Course-Enrolment DomainBackgroundConsider an illustrative university portal that allows students to enrol in courses. The system must represent students and courses, prevent duplicate enrolment, and expose a clear API to web controllers and persistence code. ProblemA procedural implementation stores parallel lists of student identifiers and course identifiers. This approach permits inconsistent updates, duplicates, and validation scattered across controllers. The design needs objects with clear responsibilities. AnalysisA Student should own its identity and enrolment collection. A Course should own its code and capacity. Enrollment is a domain operation rather than a controller detail. The system must also decide equality rules: two Student objects with the same institutional ID represent the same logical student, even if they are different runtime instances. ApproachPython provides a concise illustration: class Course:
def __init__(self, code, title, capacity):
if not code or capacity <= 0:
raise ValueError("Invalid course")
self.code = code
self.title = title
self.capacity = capacity
self._students = set()
@property
def enrolled_count(self):
return len(self._students)
def enroll(self, student):
if student in self._students:
return False
if self.enrolled_count >= self.capacity:
raise ValueError("Course is full")
self._students.add(student)
return True
class Student:
def __init__(self, student_id, name):
if not student_id or not name:
raise ValueError("Student identity is required")
self.student_id = student_id
self.name = name
def __eq__(self, other):
return isinstance(other, Student) and self.student_id == other.student_id
def __hash__(self):
return hash(self.student_id)The set prevents duplicate enrollment, while Student.__eq__ and __hash__ define identity by institutional ID. This design has an important limitation: student_id must not change after insertion into the set. In a Java implementation, the same principle would require consistent equals and hashCode, possibly with immutable fields. OutcomeThe domain rules are located near the data they govern. A web controller can call course.enroll(student) without knowing how duplicates or capacity are represented. Unit tests can independently verify constructor validation, equality, capacity, and repeated enrollment. Learning points- Objects should protect domain invariants.
- Equality is a domain decision, not merely a technical comparison.
- Mutable fields used in hashing create correctness hazards.
- Encapsulation reduces duplication in application layers.
- A class should expose behavior rather than merely expose all data.
11. Design Guidance, Limitations, and Professional PracticePrefer cohesive classesA class should have a focused responsibility. A User class should not also perform database migrations, render HTML, and send unrelated notifications. Separating domain objects, services, repositories, and presentation components improves testing and changeability. Favor immutability when practicalImmutable objects are easier to reason about, share, cache, and use safely across threads. In Java, final fields and records can support immutability. In Python, tuples, frozen dataclasses, and disciplined interfaces can provide similar benefits. Immutability is not absolute: references to mutable objects must also be controlled. Avoid inappropriate inheritanceInheritance expresses an “is-a” relationship and introduces coupling. If a Car merely uses an Engine, composition is usually more accurate than making Car inherit from Engine. Interfaces, protocols, and composition often support more flexible designs. Validate at boundariesConstructors and public methods should reject invalid values early. Validation should be specific, testable, and consistent. Sensitive systems must also consider authorization, privacy, auditability, and safe handling of personally identifiable information. Encapsulation alone is not a security mechanism; access control must be enforced at appropriate application and infrastructure boundaries. Testing implicationsClasses should be tested through observable behavior. Useful tests include valid and invalid construction, boundary values, state transitions, equality contracts, repeated calls, and lifecycle cleanup. Tests should also verify that exceptions are meaningful and that methods do not unexpectedly mutate inputs. Contemporary relevanceModern frameworks frequently construct objects through dependency injection, serialization, ORM mapping, and reflection. Framework requirements can conflict with ideal constructors—for example, an ORM may require a no-argument constructor. Developers must understand both language semantics and framework conventions, documenting exceptions rather than weakening invariants indiscriminately. Chapter SummaryA class defines a type’s state and behavior, while an object is a concrete instance of that class. Fields and attributes represent state; methods provide operations; properties provide controlled attribute-like access; and constructors establish valid initial state. Java uses explicit classes, access modifiers, overloaded constructors, and static members. Python uses dynamic attributes, __init__, properties, class methods, and static methods. Instance members belong to individual objects, whereas class or static members belong to the class or provide class-related utility. Java passes all arguments by value, including copied object references; Python passes references through object sharing. Identity and equality must be distinguished, and equality must remain consistent with hashing when objects are stored in hash-based collections. Garbage collection does not replace deterministic cleanup of external resources. Effective class design combines encapsulation, cohesive responsibilities, validation, suitable equality semantics, controlled mutability, and clear lifecycle management. Key TermsClass: A definition describing the state and behavior of a type. Object: A runtime instance of a class. Instance: A concrete object created from a class. Field: A variable declared as part of a class, especially in Java. Attribute: A named value associated with an object or class, especially in Python. Method: A function associated with a class. Constructor: A mechanism that creates or initializes an object. Encapsulation: Combining state and behavior while controlling access to implementation details. Property: An attribute-like interface that can perform computation or validation. Invariant: A condition that must remain true for a valid object. Instance member: A member belonging to a particular object. Class member: A member associated with the class and potentially shared by its instances. Static method: A method that does not automatically receive an instance or class reference. Method signature: The identifying parameter structure of a method; in Java, name plus parameter types for overloading. Identity: Whether two references designate the same object. Equality: Whether two objects are equivalent according to defined rules. Hashing: Converting a value to a hash code for hash-based collections. Mutability: The ability of an object’s state to change after creation. Garbage collection: Automatic reclamation of memory occupied by unreachable objects. Composition: Building a class from collaborating objects rather than inheriting from them. Review QuestionsShort Answer Questions- What is the difference between a class and an object?
- Why are constructors important for maintaining invariants?
- Distinguish an instance member from a class or static member.
- What is the difference between identity and equality?
- Why should external resources not be managed solely through garbage collection?
Long Answer Questions- Compare Java and Python approaches to fields, properties, and encapsulation.
- Explain Java argument passing and Python object sharing, using examples of mutation and rebinding.
- Discuss constructor overloading in Java and practical alternatives in Python.
- Explain why equality and hashing must be consistent in collection-oriented programs.
- Evaluate composition and inheritance as approaches to class design.
Analytical / Application Questions- Design classes for an online shopping cart. Identify attributes, constructors, properties, methods, equality rules, and suitable class-level members.
- A Python object is used as a dictionary key, but one of its equality-relevant attributes is later changed. Analyze the resulting risk and propose a safer design.
- A Java service creates database connections inside a method and relies on garbage collection to close them. Identify the lifecycle problem and redesign the resource-handling approach.
Multiple Choice Questions with AnswersQuestion 1Which statement best describes an object? A. A compile-time comment describing a class B. A runtime instance of a class C. A method parameter type only D. A shared static variable Correct Answer: B. A runtime instance of a class Question 2In Java, which member belongs separately to each object? A. An instance field B. A static field C. A static method D. A class constant only Correct Answer: A. An instance field Question 3What is the primary purpose of a constructor? A. To destroy an object immediately B. To overload every method in a class C. To establish initial valid object state D. To convert a class into an interface Correct Answer: C. To establish initial valid object state Question 4Which Python expression tests object identity? A. = B. == C. is D. equals() Correct Answer: C. is Question 5In Java, what does an object argument pass by value contain? A. A copied reference value B. The object’s source code C. A reference that can never mutate the object D. The class definition only Correct Answer: A. A copied reference value Question 6Which Python decorator creates a property interface? A. @staticmethod B. @property C. @classmethod D. @override Correct Answer: B. @property Question 7Why must Java equals and hashCode generally be consistent? A. To allow constructors to return values B. To make all fields public C. To ensure hash-based collections work correctly D. To prevent method parameters Correct Answer: C. To ensure hash-based collections work correctly Question 8Which method automatically receives cls in Python? A. An instance method B. A class method C. A static method D. A destructor method Correct Answer: B. A class method Question 9What is a major risk of changing a hash-relevant attribute after inserting an object into a set? A. The object may become difficult or impossible to locate correctly B. The program automatically becomes statically typed C. The constructor runs again D. The set becomes a list Correct Answer: A. The object may become difficult or impossible to locate correctly Question 10Which design choice usually improves deterministic cleanup of a Python file? A. Relying on __del__ B. Leaving the file open until program exit C. Using a with context manager D. Making the file a class attribute Correct Answer: C. Using a with context manager
|