Python OOP for Java Developers

Published: (February 15, 2026 at 02:56 PM EST)
8 min read
Source: Dev.to

Source: Dev.to

The Java version

A Square is a Shape. It is defined by its position (x, y) and its side length.

Square inherits from Shape

abstract class Shape {
    abstract int area();
    abstract int perimeter();
}

public class Square extends Shape {
    private int x;
    private int y;
    private int side;

    public Square(int x, int y, int side) {
        this.x = x;
        this.y = y;
        this.side = side;
    }

    public Square(int x1, int y1, int x2, int y2) {
        if (Math.abs(x2 - x1) != Math.abs(y2 - y1)) {
            throw new IllegalArgumentException("Square must have equal sides");
        }
        x = x1;
        y = y1;
        side = Math.abs(x2 - x1);
    }

    @Override
    public int area() {
        return side * side;
    }

    @Override
    public int perimeter() {
        return 4 * side;
    }

    @Override
    public String toString() {
        return String.format("Square[x=%d,y=%d,side=%d]", x, y, side);
    }
}

This article is aimed at Java developers, so the Java code is not explained in detail. Feel free to ask questions in the comments.

Translating to Python

Class definition

class Square(Shape):
    pass   # empty blocks are not allowed in Python, so we’ll fill it later

Constructor (Python has only one __init__ method)

In Python, declaration and initialization happen in the same place, so we declare the fields inside __init__.

def __init__(self, x, y, side):
    self.x = x
    self.y = y
    self.side = side

Creating an instance does not use the new keyword:

s1 = Square(1, 3, 10)
s2 = Square(x=1, y=3, side=10)

Python does not support method overloading, so the second Java constructor (four parameters) cannot be expressed with another __init__. We’ll implement it with a @classmethod later.

Encapsulation

Python lacks the public / protected / private keywords. By convention, a leading underscore (_) marks a name as “private”.

The explicit self argument

All instance methods receive the instance as the first parameter, traditionally named self. This is analogous to Java’s this, but self is not a keyword.

def area(self):
    return self.side * self.side

def perimeter(self):
    return 4 * self.side

String representation

Python uses __str__ and __repr__ instead of Java’s toString.

  • __str__ – user‑friendly representation (used by print and str()).
  • __repr__ – unambiguous representation, ideally something that could be used to recreate the object.
def __str__(self):
    return f"Square [side={self.side}]"

def __repr__(self):
    return f"Square(side={self.side})"
sq = Square(5, 0, 5)
print(sq)               # Square [side=5]
print(str(sq) + ".")    # Square [side=5].
# print(sq + ".") would raise a TypeError because concatenation with a non‑string is not allowed.
MethodPurposeCalled by
__repr__Returns a constructor‑like expressionrepr() or the interactive interpreter
__str__Returns a readable stringstr(), print()

Static and class methods

In Java, static methods belong to the class. Python distinguishes static methods (@staticmethod) from class methods (@classmethod).

Static method example

public static double calcSideLength(double area) {
    return Math.sqrt(area);
}
import math

@staticmethod
def calc_side_length(area):
    return math.sqrt(area)   # `math` is a module, not a class

A static method receives no self or cls argument because it does not depend on an instance or the class.

Class method for the second constructor

A class method receives the class itself (cls) as its first argument, making it perfect for factory methods.

@classmethod
def from_points(cls, x1, y1, x2, y2):
    """Create a Square from two opposite corners."""
    if abs(x2 - x1) != abs(y2 - y1):
        raise ValueError("The points do not form a square")
    side = abs(x2 - x1)
    # Use the lower‑left corner as the origin (x1, y1)
    return cls(x1, y1, side)
sq = Square.from_points(0, 0, 3, 3)   # creates a 3×3 square

Exceptions

Java’s throw becomes Python’s raise. The from_points method above already demonstrates raising a ValueError when the supplied points do not form a square.

Full Python implementation

import math
from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

    @abstractmethod
    def perimeter(self):
        pass

class Square(Shape):
    def __init__(self, x, y, side):
        self.x = x
        self.y = y
        self.side = side

    @classmethod
    def from_points(cls, x1, y1, x2, y2):
        """Factory method that builds a Square from two opposite corners."""
        if abs(x2 - x1) != abs(y2 - y1):
            raise ValueError("The points do not form a square")
        side = abs(x2 - x1)
        return cls(x1, y1, side)

    @staticmethod
    def calc_side_length(area):
        """Return the side length for a given area."""
        return math.sqrt(area)

    def area(self):
        return self.side * self.side

    def perimeter(self):
        return 4 * self.side

    def __str__(self):
        return f"Square [side={self.side}]"

    def __repr__(self):
        return f"Square(side={self.side})"

You can now use the class just like the Java version, but with Pythonic idioms:

sq1 = Square(1, 2, 5)
sq2 = Square.from_points(0, 0, 5, 5)

print(sq1)                     # Square [side=5]
print(repr(sq2))               # Square(side=5)
print(Square.calc_side_length(25))  # 5.0

Happy coding! If anything is unclear, leave a comment and we’ll clarify.

Using Square.make_square_from_points

try:
    sq = Square.make_square_from_points(4, 5, 7, 11)
except ValueError as ve:
    print(ve)

Output

ValueError: (4, 5, 7, 11)

Error hierarchy

Creating a Custom Exception

We’ll create our own error MalformedSquareException by inheriting from ValueError.

class MalformedSquareException(ValueError):
    def __init__(self, x1, y1, x2, y2):
        self.x1 = x1
        self.y1 = y1
        self.x2 = x2
        self.y2 = y2

    # Note the difference wrt __str__
    def __repr__(self):
        return "MalformedSquareException(%d, %d, %d, %d)" % (
            self.x1, self.y1, self.x2, self.y2
        )

    def __str__(self):
        return (
            "The points (%d, %d) and (%d, %d) are not the opposite corners of a square"
            % (self.x1, self.y1, self.x2, self.y2)
        )

try:
    sq = Square.make_square_from_points(4, 7, 5, 11)
except MalformedSquareException as ve:
    print(ve)

Output

The points (4, 5) and (7, 11) are not the opposite corners of a square

Abstract Classes

The Shape abstract base class can be written in Python as follows:

import abc

# alternative: class Shape(abc.ABC)
class Shape(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def area(self):
        ...  # Python abstract methods require a body

    @abc.abstractmethod
    def perimeter(self):
        ...

Python abstract classes are created either by:

  • Declaring ABCMeta as a metaclass, or
  • Subclassing ABC (whose metaclass is ABCMeta).

As in Java, Python abstract classes can have constructors and non‑abstract methods. They can also define static and class abstract methods.

Example: Static Abstract Method

class Shape(metaclass=abc.ABCMeta):
    @staticmethod
    @abc.abstractmethod
    def nb_sides():
        ...

class Square(Shape):
    @staticmethod
    def nb_sides():
        return 4

Note: @staticmethod must precede @abc.abstractmethod.

Operator Overloading

Python supports operator overloading via special methods such as __add__, __mul__, __lt__, __gt__, etc. In contrast, Java only overloads + for string concatenation.

import abc
import math

class Square(metaclass=abc.ABCMeta):
    # sq2 = sq1 * 3 creates sq2 that's √3 times bigger than sq1
    def __mul__(self, n):
        return Square(self.x, self.y, self.side * math.sqrt(n))

    def __lt__(self, other):
        return self.side < other.side

s1 = Square(0, 0, 10)
s2 = s1 * 3          # calls __mul__
print(s1 < s2)       # calls __lt__

Multiple Inheritance

Python supports multiple inheritance, unlike Java which allows only single inheritance. When a class has multiple ancestors, the resolution order is:

  1. Immediate ancestors have higher priority than those farther up.
  2. Within ancestors of the same hierarchy level, the order in the class definition determines priority (left → higher, right → lower).

Example Diagram

Updated Shape, Rectangle, Rhombus & updated Square classes

Code

class Rectangle(Shape):
    def __init__(self, x, y, length, breadth):
        self.x, self.y = x, y
        self.length, self.breadth = length, breadth

    def area(self):
        return self.length * self.breadth

    def perimeter(self):
        return 2 * (self.length + self.breadth)

    @staticmethod
    def nb_sides():
        return 4

class Rhombus(Shape):
    def __init__(self, x, y, side, angle):
        self.x, self.y = x, y
        self.side, self.angle = side, angle

    def area(self):
        return self.side * self.side * math.sin(self.angle * math.pi / 180)

    def perimeter(self):
        return 4 * self.side

    @staticmethod
    @abc.abstractmethod
    def nb_sides():
        return 4

class Square(Rhombus, Rectangle):
    def __init__(self, side):
        # Rhombus expects (x, y, side, angle); we set x=y=0 and angle=90°
        super().__init__(0, 0, side, 90)

    def area(self):
        return super().area()

    @staticmethod
    def nb_sides():
        return 4

Key points to note:

  • Square inherits from both Rhombus and Rectangle.
  • The method resolution order (MRO) follows the rules described above.
  • The super() call in Square.__init__ correctly forwards arguments to Rhombus.__init__ (the first parent in the MRO).

Understanding super() and Constructor Calls in Python

  • The super() call does not invoke the parent constructor automatically; it merely provides a reference to the parent object. Therefore, you must call __init__ explicitly if you need the parent’s initialization logic.
  • Because the declaration of Square lists Rhombus before Rectangle, super() will resolve to Rhombus first.
0 views
Back to Blog

Related posts

Read more »

Access Modifiers in Java

markdown !Nanthini Ammuhttps://media2.dev.to/dynamic/image/width=50,height=50,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2...

Module-01--Interview Question _JAVA

1. How many data types are in Java? - Primitive Data Types 8 total: byte, short, int, long, float, double, char, boolean - Non‑Primitive Reference Data Types:...