Java to Python: Classes, Exceptions, and the Standard Library
Part 2 of the Python for Java Developers track. Last updated: October 2026.
You know OOP cold — Python's version is the same ideas with less ceremony. This post maps classes (and the explicit self), the absence of interfaces, the absence of checked exceptions, and a tour of the standard library modules that replace the Java APIs you reach for daily.
Classes: same ideas, less ceremony
No public class boilerplate, no getters/setters required — just a class, an __init__ constructor, and methods. The one surprise is self: Python passes the receiver explicitly as the first parameter (Java's this, but written out):
// Java
public class Order {
private final String id;
private double total;
public Order(String id, double total) {
this.id = id; this.total = total;
}
public String summary() {
return id + ": $" + total;
}
}
# Python — __init__ is the constructor, self is the receiver
class Order:
def __init__(self, id: str, total: float):
self.id = id
self.total = total
def summary(self) -> str:
return f"{self.id}: ${self.total}"
print(Order("ORD-1", 84.5).summary())
# ORD-1: $84.5
No interfaces — duck typing (and ABC when you want contracts)
Python has no interface keyword because it doesn't need one: if an object has a quack() method, it's a duck. When you do want an explicit contract (framework code, plugin systems), abc.ABC gives you abstract base classes with @abstractmethod:
from abc import ABC, abstractmethod
class Notifier(ABC): # the "interface"
@abstractmethod
def send(self, message: str) -> None: ...
class EmailNotifier(Notifier): # the implementation
def send(self, message: str) -> None:
print(f"Email: {message}")
def alert(n: Notifier): # duck typing also works untyped
n.send("deploy done")
alert(EmailNotifier())
# Email: deploy done
Exceptions: everything is unchecked
Python has no checked exceptions — nothing like throws IOException forcing callers to catch. Raise with raise, catch specific types with except, and remember the hierarchy root is Exception (never catch bare except: in library code — it swallows KeyboardInterrupt too):
// Java: checked exceptions force handling at every level
public Order load(String id) throws IOException { ... }
# Python: raise and catch what you can handle, let the rest propagate
class OrderNotFound(Exception):
pass
def load_order(order_id: str) -> dict:
if order_id != "ORD-1":
raise OrderNotFound(f"No order {order_id}")
return {"id": order_id}
try:
load_order("ORD-9")
except OrderNotFound as e:
print("Handled:", e)
# Handled: No order ORD-9
Context managers: try-with-resources, generalized
Java's try-with-resources closes one kind of thing. Python's with statement works with any context manager — files, locks, database connections, timers you write yourself:
// Java: try-with-resources
try (BufferedReader br = Files.newBufferedReader(path)) {
return br.readLine();
}
# Python: with — same guarantee, any resource
from pathlib import Path
with open("orders.csv") as f: # closed automatically, even on exceptions
first_line = f.readline()
print(Path("orders.csv").read_text().splitlines()[0] == first_line.strip())
# True
Decorators: annotations that wrap behavior
Java annotations are metadata; Python decorators (the @ syntax) are functions that wrap other functions — they actually change behavior. @property gives you getter-style access, @staticmethod/@classmethod mirror Java's static (with classmethod also receiving the class):
from functools import lru_cache
class Circle:
def __init__(self, radius: float):
self.radius = radius
@property # accessed like an attribute: c.diameter
def diameter(self) -> float:
return 2 * self.radius
@lru_cache(maxsize=128) # decorator adding memoization
def fib(n: int) -> int:
return n if n < 2 else fib(n - 1) + fib(n - 2)
c = Circle(5)
print(c.diameter, fib(20)) # no parentheses on diameter!
# 10 6765
Standard library: your Java API cheat sheet
The batteries are included. Here is where your daily Java APIs live in Python — pathlib replaces java.io/java.nio, datetime replaces java.time, json needs no Jackson:
from pathlib import Path
from datetime import date, timedelta
import json
from collections import Counter, defaultdict
p = Path("data") / "orders.csv" # / operator joins paths — no Paths.get()
print(p.name, p.suffix) # orders.csv .csv
today = date.today()
print(today - timedelta(days=7)) # date arithmetic is just operators
payload = json.loads('{"id": "ORD-1"}') # no ObjectMapper needed
print(payload["id"], Counter("aabbc")["a"])
# ORD-1 2
grouped = defaultdict(list) # map of lists without computeIfAbsent
for sku in ["a1", "b2", "a3"]:
grouped[sku[0]].append(sku)
print(dict(grouped))
# {'a': ['a1', 'a3'], 'b': ['b2']}
Key takeaways
- Classes need no boilerplate; self is the explicit receiver, __init__ is the constructor.
- No interface keyword — duck typing by default, abc.ABC when you want a contract.
- No checked exceptions: raise freely, catch specifically, never use bare except:.
- with generalizes try-with-resources to any context manager.
- Decorators wrap behavior (not just metadata): @property, @lru_cache, @staticmethod.
- Standard library mapping: pathlib → java.nio, datetime → java.time, json → Jackson, collections → Guava-style helpers.
Next in this series: From Maven to pip: Python Tooling, Testing, and Packaging for Java Developers.
Comments
Post a Comment