Object-Oriented Programming (Java/Python)

Chapter 2
Java and Python Object Models and Tooling

Bachelors Program in Full Stack Development
Object-Oriented Programming (Java/Python)   •   Bachelor

Java and Python Object Models and Tooling

Introduction

Object-oriented programming (OOP) organizes software around objects that combine state and behavior. Java and Python both support classes, objects, inheritance, encapsulation, polymorphism, and abstraction, but they implement these ideas through significantly different object models and development ecosystems.

Java is generally described as statically typed, class-based, and compiled to platform-independent bytecode executed by the Java Virtual Machine (JVM). Python is dynamically typed, strongly object-oriented, and commonly executed by an interpreter or virtual machine after source-code compilation to bytecode. These descriptions are useful, but incomplete. Java also uses runtime reflection and dynamic class loading, while Python implementations such as CPython compile source into bytecode before execution.

Understanding these differences is essential for full-stack developers. Type systems influence defect detection and refactoring. Memory and reference semantics influence correctness. Packages, modules, build tools, and environments influence maintainability and deployment. Coding conventions influence collaboration and long-term software quality.

Learning Objectives

By the end of this chapter, you should be able to:

  1. Compare Java and Python object models, typing systems, and execution processes.
  2. Explain references, object identity, mutability, and memory-management strategies.
  3. Organize software using Java packages and Python modules and packages.
  4. Select appropriate development environments, build tools, and dependency-management practices.
  5. Apply coding conventions and quality practices in professional projects.
  6. Analyze the trade-offs between Java and Python for a realistic application.

1. The Object Model: Common Ideas and Different Realizations

An object has identity, state, and behavior. Identity answers which object it is; state consists of its data; behavior consists of operations it can perform. A class is a definition or blueprint from which objects may be created, although the precise relationship between classes and objects differs between languages.

Java is class-centered. Most application objects are instances of classes, and every class ultimately derives from java.lang.Object. Primitive values such as int, double, and boolean are not objects, although wrapper classes such as Integer, Double, and Boolean allow them to participate in object-based APIs. Java also provides records, enumerations, interfaces, and sealed classes as specialized modeling mechanisms.

Python takes a more uniform object-oriented approach. Numbers, strings, functions, classes, modules, and even types are objects. Python classes are themselves instances of a metaclass, normally type. This allows powerful runtime techniques, including introspection, decorators, dynamic attribute management, and metaprogramming. Such flexibility can improve productivity but can also make program behavior harder to determine without executing the code.

Identity, equality, and state

Two objects may contain equivalent data without being the same object. Java normally distinguishes reference equality with == from logical equality with .equals(). For example, two separate String objects may contain the same characters, so .equals() may return true even when == returns false. Classes that override .equals() should normally also override .hashCode() consistently.

Python uses is for identity and == for value comparison. The expression a is b asks whether two names refer to the same object. The expression a == b asks whether their values are equal according to the objects’ equality implementation. Using is for ordinary value comparison is a common error.

String first = new String("Java");
String second = new String("Java");
System.out.println(first == second);       // false
System.out.println(first.equals(second));  // true
first = [1, 2]
second = [1, 2]
print(first is second)  # False
print(first == second) # True

2. Static and Dynamic Typing

A type system determines which values may be used with which operations. It also determines when type errors are detected.

Java: mostly static typing

In Java, each variable, parameter, field, and method return type has a declared or inferred compile-time type. The compiler checks many invalid operations before the program runs.

int quantity = 4;
String label = "books";
// quantity = label;  // Compile-time error

Java supports subtype polymorphism. A variable declared with an interface or superclass type may refer to an object of a compatible implementing or derived class:

List<String> names = new ArrayList<>();
names.add("Amina");

The declared type controls which operations are available at compile time, while the actual object controls overridden method dispatch at runtime. This distinction is central to polymorphism.

Java also supports generic types, such as List<String>. Generics improve compile-time safety and reduce casts. Java generics are implemented primarily through type erasure, meaning much generic type information is not available at ordinary runtime. Reflection and special mechanisms can still expose some type metadata.

Python: dynamic typing with optional annotations

Python variables do not have fixed declared types in the Java sense. A name is bound to an object, and the same name may later be rebound to an object of another type.

value = 10
value = "ten"

Python is dynamically typed, but it is not untyped. Objects have types, and operations are defined by those types. An invalid operation generally produces a runtime exception such as TypeError.

Python supports type annotations:

def total(price: float, quantity: int) -> float:
    return price * quantity

Annotations document intended usage and can be checked by tools such as mypy, Pyright, or IDE analyzers. In ordinary Python execution, annotations do not automatically enforce the declared types. Therefore, Python can combine dynamic runtime behavior with optional static analysis.

Python commonly emphasizes duck typing: an object is suitable when it supports the required operations, regardless of its nominal class. Protocols and structural typing make this approach more explicit in modern Python type checking.

Comparison

DimensionJavaPython
Primary typing styleStaticDynamic, with optional annotations
Main error-detection timeCompilation and runtimeMostly runtime, optionally analysis time
Generic programmingBuilt-in genericsGeneric annotations and protocols
FlexibilityMore constrainedMore flexible
Refactoring supportStrong compiler assistanceDepends heavily on tests and analysis tools
Typical trade-offEarly feedback and explicit contractsRapid development and adaptability

Neither approach is universally superior. Static typing can prevent broad classes of defects before deployment, while dynamic typing can reduce ceremony and support exploratory development. In professional systems, tests, code review, static analysis, and clear interfaces matter regardless of language.

3. Compilation, Interpretation, and Execution

The distinction between compiled and interpreted languages is useful but oversimplified. Modern runtimes commonly use several stages: parsing, compilation to an intermediate representation, interpretation, just-in-time compilation, optimization, and machine-code execution.

Java execution model

A Java source file is compiled by javac into bytecode stored in a .class file. The JVM loads, verifies, and executes that bytecode. Frequently executed code may be compiled at runtime by a just-in-time (JIT) compiler into native machine instructions.

Java source (.java) → javac → bytecode (.class) → JVM → interpreted/JIT-compiled execution

The JVM provides portability, automatic memory management, security boundaries, standardized libraries, and runtime services. Java applications may be packaged as JAR files. A JAR is a ZIP-based archive containing classes, resources, and metadata.

Python execution model

In CPython, source code is parsed and compiled into bytecode, often cached in __pycache__ files. The Python virtual machine executes that bytecode. Other implementations, such as PyPy, may use a JIT compiler.

Python source (.py) → parser/compiler → bytecode → Python virtual machine

Python’s implementation strategy does not imply that Python is interpreted in a simplistic sense, nor does Java’s compilation mean that Java source is directly compiled into the final machine code on every platform. The relevant engineering questions concern startup time, optimization, portability, deployment, and runtime behavior.

4. References, Mutability, and Memory Models

A reference is a value that identifies or locates an object. It is not necessarily a physical memory address exposed to the programmer. Both Java and Python use reference-based object models, but their assignment and argument-passing rules are often misunderstood.

Java references

A Java variable of a class type contains either a reference to an object or null. Assignment copies the reference, not the object:

class Counter {
    int value;
}

Counter a = new Counter();
Counter b = a;
b.value = 5;
System.out.println(a.value); // 5

The variables a and b refer to the same object. Java is always pass-by-value. When an object reference is passed to a method, the reference value is copied. The method can mutate the referenced object, but reassigning its parameter does not change the caller’s variable.

static void replace(Counter c) {
    c = new Counter();
    c.value = 99;
}

Calling replace(a) does not make a refer to the new object.

Primitive variables contain primitive values directly. Boxing converts a primitive to a wrapper object, either explicitly or through autoboxing. Developers should be cautious because boxing can introduce allocation, identity surprises, and NullPointerException risks when unboxing a null wrapper.

Python names and objects

Python variables are names bound to objects. Assignment binds a name; it does not copy an object by default.

items = ["a", "b"]
other = items
other.append("c")
print(items)  # ['a', 'b', 'c']

Python argument passing is often described as “call by object sharing” or “call by assignment.” The function receives a new local name bound to the same object. Mutating a mutable object is visible to the caller; rebinding the local name is not.

def change(values):
    values.append(3)  # Mutates the shared list
    values = [100]    # Rebinds only the local name

Use copying deliberately. A shallow copy duplicates the outer container but may retain references to nested objects. A deep copy recursively copies many nested objects, but it may be expensive or inappropriate for objects connected to files, databases, locks, or external resources.

Mutability and immutability

Mutable objects can change after creation. Java arrays, ArrayList, and many domain objects are mutable. Python lists, dictionaries, and sets are mutable. Java String, Python str, and Python tuples are immutable in normal use.

Immutability simplifies reasoning, supports safe sharing, and reduces accidental side effects. A class intended to be immutable should generally prevent externally visible state changes, use private final fields where appropriate, avoid exposing mutable internals, and validate construction inputs.

Memory management

Java and CPython use automatic memory management, but their implementations differ. Java garbage collectors identify objects that are no longer reachable and reclaim their memory. Different collectors make different throughput, latency, and footprint trade-offs.

CPython primarily uses reference counting supplemented by a cyclic garbage collector. An object whose reference count reaches zero can often be reclaimed immediately, while cyclic references require cycle detection. Alternative Python implementations may use different memory-management strategies.

Automatic memory management does not eliminate resource-management responsibilities. Files, sockets, database connections, and locks should be released explicitly. Java uses try-with-resources; Python commonly uses context managers and with statements.

5. Packages, Modules, and Namespaces

Namespaces reduce naming conflicts and provide structure for large systems.

Java packages

A Java package groups related classes and interfaces:

package com.example.orders;

public class OrderService {
    // implementation
}

The package declaration normally corresponds to a directory structure. Classes are imported with import, and access modifiers regulate visibility. public exposes a type or member broadly; package-private access limits access to the same package; protected has package and inheritance implications; private limits access to the declaring class.

A package name is conventionally written in reverse-domain form, such as com.company.product. The Java Platform Module System, introduced in Java 9, adds a larger unit called a module. A module can declare required modules and exported packages in module-info.java. Modules support stronger encapsulation than packages alone.

Python modules and packages

A Python module is usually a .py file. A package is a structured collection of modules, commonly identified by an __init__.py file, although namespace packages can exist without one.

# shop/pricing.py
def calculate_total(price, quantity):
    return price * quantity
from shop.pricing import calculate_total

Python imports execute module-level code once per interpreter process and cache the resulting module in sys.modules. This makes import-time side effects important. Avoid performing expensive operations or modifying global state unnecessarily during import.

Use absolute imports for clarity in most application code. Relative imports can be useful within a closely related package. Define public interfaces deliberately; a leading underscore conventionally indicates an internal name, but it is not a strict access barrier.

6. Development Environments and Toolchains

An integrated development environment (IDE) combines editing, navigation, debugging, testing, refactoring, and project management. Common Java environments include IntelliJ IDEA, Eclipse, and Visual Studio Code with Java extensions. Python developers use PyCharm, Visual Studio Code, JupyterLab, and other editors.

A productive environment should provide:

  • language-aware completion and navigation;
  • syntax and type diagnostics;
  • test discovery and debugging;
  • formatting and import organization;
  • version-control integration;
  • secure handling of configuration and secrets.

The command line remains important because build servers, containers, production systems, and continuous-integration pipelines may not provide an IDE. Developers should understand the commands behind IDE buttons.

Java projects normally require a compatible JDK, not merely a runtime. The java command runs applications, while javac compiles source. Python projects require a selected interpreter, and the interpreter version must be compatible with the application and dependencies.

7. Build Tools and Dependency Management

Build tools automate compilation, testing, packaging, documentation, static analysis, and publication.

Java: Maven and Gradle

Maven uses a declarative pom.xml file. It applies conventional project lifecycles such as compiling, testing, packaging, and installing artifacts. Dependencies are identified by coordinates such as group, artifact, and version.

Gradle uses build scripts written in a domain-specific language based on Groovy or Kotlin. It emphasizes flexible task graphs, incremental execution, and build performance. Both Maven and Gradle support dependency resolution, transitive dependencies, plugins, test execution, and repository configuration.

A dependency graph may contain version conflicts. Teams should use dependency locking or reproducible version constraints where appropriate, review transitive dependencies, and scan for known vulnerabilities. A build that succeeds only because of undeclared local dependencies is not reproducible.

Python: pip, virtual environments, and project metadata

Python projects commonly use venv to create isolated environments:

python -m venv .venv
source .venv/bin/activate        # Unix-like systems
.venv\Scripts\activate           # Windows
python -m pip install -r requirements.txt

Modern Python projects may use pyproject.toml as a central configuration file. Tools such as Poetry, Hatch, PDM, and uv can manage environments, dependencies, packaging, and lock files. A requirements file may pin exact versions for deployment, whereas a project declaration may express compatible version ranges. The appropriate strategy depends on whether the file defines an application environment, a reusable library, or a development environment.

Good dependency practice includes using isolated environments, separating production and development dependencies, recording resolved versions, verifying package sources, updating regularly, and removing unused packages.

8. Coding Conventions and Quality Practices

Conventions reduce cognitive overhead. Java commonly follows conventions documented by style guides such as Google Java Style or organizational standards: classes use PascalCase, methods and variables use camelCase, constants use uppercase with underscores, and packages use lowercase names.

Python’s major reference is PEP 8. Classes generally use CapWords; functions, variables, and modules use snake_case; constants use uppercase with underscores. PEP 257 addresses docstrings. Formatters such as black, isort, and language-specific IDE tools automate consistency.

Conventions should be enforced with automation rather than memory alone. A professional pipeline may include formatting checks, linters, type analysis, unit tests, integration tests, security scanning, and code coverage measurement. Coverage is evidence about exercised code, not a guarantee of correctness.

Useful design practices in both languages include:

  • prefer small classes and methods with clear responsibilities;
  • keep public interfaces minimal and stable;
  • validate inputs at boundaries;
  • distinguish domain errors from infrastructure failures;
  • avoid global mutable state;
  • document decisions, assumptions, and externally visible behavior;
  • treat warnings as information requiring evaluation;
  • use version control and reproducible builds.

9. Worked Comparison: A Small Order Model

Consider an order that contains line items. In Java, an explicit type model might be written as follows:

public record LineItem(String sku, int quantity, double unitPrice) {
    public LineItem {
        if (quantity <= 0 || unitPrice < 0) {
            throw new IllegalArgumentException("Invalid line item");
        }
    }

    public double subtotal() {
        return quantity * unitPrice;
    }
}

The record communicates intended state and supplies useful methods automatically. The compiler verifies that callers provide a String, an int, and a double in the correct positions.

A Python equivalent might use a dataclass:

from dataclasses import dataclass

@dataclass(frozen=True)
class LineItem:
    sku: str
    quantity: int
    unit_price: float

    def __post_init__(self):
        if self.quantity <= 0 or self.unit_price < 0:
            raise ValueError("Invalid line item")

    def subtotal(self) -> float:
        return self.quantity * self.unit_price

The annotation communicates intent, while frozen=True prevents ordinary attribute reassignment after construction. Python still requires runtime validation for untrusted input; annotations alone do not guarantee valid values.

The example demonstrates a broader principle: language features differ, but sound modeling requires explicit invariants. An invariant is a condition that should remain true for every valid object, such as a positive quantity or nonnegative price.

10. Case Study: Choosing a Stack for an Order-Processing Service

This is an illustrative case study.

Background

A retail company needs an order-processing service. The service receives web requests, validates orders, calculates totals, stores records, and publishes events to other systems. The team has experience in both Java and Python.

Problem

The company must select a language and tooling strategy. The system requires reliable interfaces, maintainable deployments, security updates, automated testing, and integration with databases and messaging systems. The decision must not be based only on developer preference.

Analysis

Java provides compile-time checking, mature build conventions, strong IDE refactoring, and a broad ecosystem for long-running services. Its explicit types can make contracts easier to discover across a large team. The JVM also offers mature observability and performance tooling.

Python enables rapid development and has extensive libraries for web services, automation, data processing, and integration. Type annotations, linters, and static analyzers can improve maintainability, but the team must deliberately establish those checks because the interpreter will not enforce most annotations.

The decision also involves deployment. Java teams may standardize on Maven or Gradle, a supported JDK, and a container image. Python teams may standardize on pyproject.toml, a lock strategy, a supported interpreter, and isolated environments. Both stacks require dependency scanning, secrets management, monitoring, and reliable tests.

Approach and solution

The team selects Java for the core transaction service because the service has a large domain model, multiple teams will modify shared APIs, and compile-time contracts are valuable. It uses a modular package structure, Maven, dependency locking, records for value-like data, and automated checks in continuous integration.

The team selects Python for a separate reconciliation worker because it integrates with existing data-analysis tools and has a smaller, independently deployable scope. The worker uses type annotations, Pyright, pytest, a locked environment, and explicit context managers for files and database resources.

Outcome

The company obtains two services with different but disciplined toolchains. The choice is not “Java is better than Python” or the reverse. It is a fit between system constraints, team capability, ecosystem needs, and operational requirements.

Learning points

  1. Language choice should follow requirements and constraints.
  2. Tooling is part of architecture, not an afterthought.
  3. Static analysis is valuable even in a dynamic language.
  4. Reproducible dependencies reduce deployment risk.
  5. Clear boundaries allow different languages to coexist responsibly.

11. Limitations, Risks, and Contemporary Practice

Static typing does not prevent logic errors, incorrect requirements, security vulnerabilities, or failures caused by external systems. Dynamic typing does not necessarily produce poor software; disciplined tests, annotations, contracts, and review can provide strong assurance.

Automatic memory management can produce pauses, overhead, or unexpected retention when objects remain reachable through caches or global structures. It also does not close external resources automatically at the correct business time.

Package ecosystems create supply-chain risks. Malicious or compromised dependencies, abandoned projects, typosquatting, vulnerable versions, and excessive transitive dependencies can threaten applications. Organizations should use trusted registries, software composition analysis, signature or provenance mechanisms where available, least-privilege build credentials, and review of dependency changes.

AI-assisted coding tools can generate boilerplate, suggest tests, and explain unfamiliar APIs. Their output must be reviewed for type errors, insecure patterns, licensing concerns, hallucinated APIs, privacy violations, and inappropriate disclosure of proprietary code. Generated code remains the responsibility of the development team.

Best-Practice Checklist

  • Select a supported Java JDK or Python interpreter version.
  • Use project-local, reproducible environments.
  • Declare and review all dependencies.
  • Separate identity comparison from value comparison.
  • Understand aliasing before mutating shared objects.
  • Release external resources explicitly.
  • Keep package and module boundaries meaningful.
  • Automate formatting, linting, testing, and security checks.
  • Use annotations and static analysis where they improve clarity.
  • Document public APIs, invariants, and significant design decisions.
  • Never commit passwords, tokens, or private keys to source control.

Chapter Summary

Java and Python share core object-oriented concepts but realize them differently. Java is primarily statically typed and class-centered, while Python is dynamically typed and treats nearly everything as an object. Java source becomes JVM bytecode, and Python implementations commonly compile source to bytecode before virtual-machine execution; both ecosystems may use runtime optimization.

In both languages, variables commonly hold references to objects. Assignment usually aliases an existing object rather than copying it, and mutability therefore requires careful design. Java distinguishes primitive values from references, while Python presents a more uniform object model. Java uses packages and optional modules; Python uses modules and packages with import-time namespace behavior.

Professional development depends on more than language syntax. IDEs, command-line tools, build systems, dependency managers, virtual environments, lock strategies, formatters, analyzers, tests, and security scanning establish reproducible engineering practices. The choice between Java and Python should be based on requirements, team skills, ecosystem fit, operational constraints, and long-term maintainability.

Key Terms

Object: An entity with identity, state, and behavior.

Class: A definition used to create or describe objects.

Reference: A value that identifies an object.

Identity: The property distinguishing one object from another.

Equality: A comparison of object values or logical content.

Static typing: A typing approach in which types are checked primarily before execution.

Dynamic typing: A typing approach in which types are determined and checked during execution.

Type annotation: Metadata expressing intended types, especially in Python.

Bytecode: Intermediate instructions executed by a virtual machine.

JIT compiler: A compiler that translates frequently executed code during runtime.

Mutability: The ability of an object to change after creation.

Immutability: A property whereby an object’s observable state cannot normally change.

Garbage collection: Automatic reclamation of unreachable memory.

Package: A namespace and organizational unit containing related code.

Module: A deployable or importable unit of code; in Python, commonly a source file.

Dependency: External software required by a project.

Build tool: Software that automates compilation, testing, packaging, or related tasks.

Virtual environment: An isolated environment containing a project’s interpreter context and packages.

Invariant: A condition that must remain true for a valid object or system.

Static analysis: Examination of code without executing it, including type and security checks.

Review Questions

Short Answer Questions

  1. What is the difference between object identity and object equality in Java and Python?
  2. Why is Java described as statically typed?
  3. What does assignment usually copy in Java and Python when the assigned value is an object?
  4. Distinguish a Java package from a Python module.
  5. Why are isolated environments important for dependency management?

Long Answer Questions

  1. Compare Java’s and Python’s execution models, including source code, bytecode, virtual machines, and runtime compilation.
  2. Explain mutability, aliasing, and argument passing in both languages, using suitable code examples.
  3. Analyze the advantages and limitations of static typing and dynamic typing in a large software project.
  4. Describe how Maven or Gradle differs from Python tools based on pyproject.toml and virtual environments.
  5. Explain how coding conventions, automated quality checks, and dependency security contribute to maintainability.

Analytical / Application Questions

  1. A Python team frequently receives runtime type errors after deployment. Propose a tooling and process improvement plan using annotations, static analysis, testing, and code review.
  2. A Java application has classes in one enormous package and relies on undeclared local libraries. Analyze the risks and design a migration toward clear packages and reproducible builds.
  3. For a payment service and a data-reconciliation script, compare Java and Python using typing, runtime behavior, ecosystem, deployment, and team-maintenance criteria. Recommend a language for each and justify the decision.

Multiple Choice Questions with Answers

Question 1

Which operator tests object identity in Python?

A. ==

B. is

C. equals

D. same

Correct Answer: B. is

Question 2

What does Java assignment of an object variable normally copy?

A. The complete object state

B. The object’s class definition

C. The reference value

D. Only primitive fields

Correct Answer: C. The reference value

Question 3

Which statement about Python type annotations is correct?

A. They always enforce types at runtime

B. They are ignored by every development tool

C. They communicate intended types and can support static analysis

D. They convert Python into a statically compiled language

Correct Answer: C. They communicate intended types and can support static analysis

Question 4

What is the usual result of compiling Java source with javac?

A. A native executable for one processor only

B. JVM bytecode in class files

C. A Python module

D. A database schema

Correct Answer: B. JVM bytecode in class files

Question 5

Which Java comparison is normally used for logical string content?

A. ==

B. !=

C. .equals()

D. is

Correct Answer: C. .equals()

Question 6

What is the primary purpose of a Python virtual environment?

A. To replace the operating system

B. To isolate a project’s interpreter and installed packages

C. To compile Python into Java bytecode

D. To prevent all runtime exceptions

Correct Answer: B. To isolate a project’s interpreter and installed packages

Question 7

Which practice improves build reproducibility?

A. Depending on undeclared packages installed globally

B. Removing all version information

C. Recording resolved dependency versions

D. Sharing one mutable environment across projects

Correct Answer: C. Recording resolved dependency versions

Question 8

What is a key effect of making an object immutable?

A. It must use more inheritance

B. Its observable state cannot normally be changed after creation

C. It cannot be referenced by another variable

D. It is automatically stored permanently

Correct Answer: B. Its observable state cannot normally be changed after creation

Question 9

Which tool category checks code without executing the program?

A. Static analysis

B. Garbage collection

C. JIT compilation

D. Resource allocation

Correct Answer: A. Static analysis

Question 10

Why should external resources such as files and database connections be managed explicitly?

A. Garbage collection does not reliably perform timely business-level cleanup

B. They are always immutable objects

C. They cannot be represented by references

D. Static typing automatically closes them

Correct Answer: A. Garbage collection does not reliably perform timely business-level cleanup

Object-Oriented Programming (Java/Python)
Chapter 2 of 15