Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Python vs Java: Object Oriented Programming
Java programmers making a move to Python often struggle with Python’s approach to object-oriented programming (OOP). The approach to working with objects, variable types, and other language capabilities taken by Python vs Java are quite different. It can make switching between both languages very confusing.
This article compares and contrasts object-oriented programming support in Python vs Java. By the end, you’ll be able to apply your knowledge of object-oriented programming to Python, understand how to reinterpret your understanding of Java objects to Python, and use objects in a Pythonic way.
Over the course of this article, you’ll:
- Build a basic class in both Java and Python
- Explore how object attributes work in Python vs Java
- Compare and contrast Java methods and Python functions
- Discover inheritance and polymorphism mechanisms in both languages
- Investigate reflection across Python vs Java
- Apply everything in a complete class implementation in both languages
This article isn’t a primer on object-oriented programming. Rather, it compares object-oriented features and principles of Python vs Java. Readers should have good knowledge of Java, and also be familiar with coding Python. If you are unfamiliar with object-oriented programming, then check out Intro to Object-Oriented Programming (OOP) in Python. All Python examples will work with Python 3.6 or later.
Download Sample Code: Click here to download the commented sample object definitions and source code for the Java and Python objects in this article.
Sample Classes in Python vs Java
To begin, you’ll implement the same small class in both Python and Java to illustrate the differences between them. You’ll make modifications to them as the article progresses.
First, assume you have the following Car
class definition in Java:
1public class Car {
2 private String color;
3 private String model;
4 private int year;
5
6 public Car(String color, String model, int year) {
7 this.color = color;
8 this.model = model;
9 this.year = year;
10 }
11
12 public String getColor() {
13 return color;
14 }
15
16 public String getModel() {
17 return model;
18 }
19
20 public int getYear() {
21 return year;
22 }
23}
Java classes are defined in files with the same name as the class. So, you have to save this class in a file named Car.java
. Only one class can be defined in each file.
A similar small Car
class is written in Python as follows:
1class Car:
2 def __init__(self, color, model, year):
3 self.color = color
4 self.model = model
5 self.year = year
In Python you can declare a class anywhere, in any file, at any time. Save this class in the file car.py
.
Using these classes as your base, you can explore the basic components of classes and objects.
Object Attributes
All object-oriented languages have some way to store data about the object. In Java and Python, data is stored in attributes, which are variables associated with specific objects.
One of the most significant differences between Python vs Java is how they define and manage class and object attributes. Some of these differences come from constraints imposed by the languages, while others come from best practices.
Declaration and Initialization
In Java, you declare attributes in the class body, outside of any methods, with a definite type. You must define class attributes before they are used:
1public class Car {
2 private String color;
3 private String model;
4 private int year;
In Python, you both declare and define attributes inside the class __init__()
, which is the equivalent of Java’s constructor:
1def __init__(self, color, model, year):
2 self.color = color
3 self.model = model
4 self.year = year
By prefixing the variable names with self
, you tell Python these are attributes. Each instance of the class will have a copy. All variables in Python are loosely typed, and these attributes are no exception.
You can also create instance variables outside of .__init__()
, but it’s not a best practice as their scope is often confusing. If not used properly, instance variables created outside of .__init__()
can lead to subtle bugs that are hard to find. For example, you can add a new attribute .wheels
to a Car
object like this:
1>>> import car
2>>> my_car = car.Car("yellow", "beetle", 1967)
3>>> print(f"My car is {my_car.color}")
4My car is yellow
5
6>>> my_car.wheels = 5
7>>> print(f"Wheels: {my_car.wheels}")
8Wheels: 5
However, if you forget the my_car.wheels = 5
on line 6, then Python displays an error:
1>>> import car
2>>> my_car = car.Car("yellow", "beetle", 1967)
3>>> print(f"My car is {my_car.color}")
4My car is yellow
5
6>>> print(f"Wheels: {my_car.wheels}")
7Traceback (most recent call last):
8 File "<stdin>", line 1, in <module>
9AttributeError: 'Car' object has no attribute 'wheels'
In Python, when you declare a variable outside of a method, it’s treated as a class variable. Update the Car
class as follows:
1class Car:
2
3 wheels = 0
4
5 def __init__(self, color, model, year):
6 self.color = color
7 self.model = model
8 self.year = year
This changes how you use the variable wheels
. Instead of referring to it using an object, you refer to it using the class name:
1>>> import car
2>>> my_car = car.Car("yellow", "beetle", 1967)
3>>> print(f"My car is {my_car.color}")
4My car is yellow
5
6>>> print(f"It has {car.Car.wheels} wheels")
7It has 0 wheels
8
9>>> print(f"It has {my_car.wheels} wheels")
10It has 0 wheels
Note: In Python, you refer to a class variable using the following syntax:
- The name of the file containing the class, without the
.py
extension - A dot
- The name of the class
- A dot
- The name of the variable
Since you saved the Car
class in the file car.py
, you refer to the class variable wheels
on line 6 as car.Car.wheels
.
You can refer to my_car.wheels
or car.Car.wheels
, but be careful. Changing the value of the instance variable my_car.wheels
will not change the value of the class variable car.Car.wheels
:
1>>> from car import *
2>>> my_car = car.Car("yellow", "Beetle", "1966")
3>>> my_other_car = car.Car("red", "corvette", "1999")
4
5>>> print(f"My car is {my_car.color}")
6My car is yellow
7>>> print(f"It has {my_car.wheels} wheels")
8It has 0 wheels
9
10>>> print(f"My other car is {my_other_car.color}")
11My other car is red
12>>> print(f"It has {my_other_car.wheels} wheels")
13It has 0 wheels
14
15>>> # Change the class variable value
16... car.Car.wheels = 4
17
18>>> print(f"My car has {my_car.wheels} wheels")
19My car has 4 wheels
20>>> print(f"My other car has {my_other_car.wheels} wheels")
21My other car has 4 wheels
22
23>>> # Change the instance variable value for my_car
24... my_car.wheels = 5
25
26>>> print(f"My car has {my_car.wheels} wheels")
27My car has 5 wheels
28>>> print(f"My other car has {my_other_car.wheels} wheels")
29My other car has 4 wheels
You define two Car
objects on lines 2 and 3:
my_car
my_other_car
At first, both of them have zero wheels. When you set the class variable using car.Car.wheels = 4
on line 16, both objects now have four wheels. However, when you set the instance variable using my_car.wheels = 5
on line 24, only that object is affected.
This means that there are now two different copies of the wheels
attribute:
- A class variable that applies to all
Car
objects - A specific instance variable applicable to the
my_car
object only
It isn’t difficult to accidentally refer to the wrong one and introduce subtle bugs.
Java’s equivalent to a class attribute is a static
attribute:
public class Car {
private String color;
private String model;
private int year;
private static int wheels;
public Car(String color, String model, int year) {
this.color = color;
this.model = model;
this.year = year;
}
public static int getWheels() {
return wheels;
}
public static void setWheels(int count) {
wheels = count;
}
}
Normally, you refer to static variables using the Java class name. You can refer to a static variable through a class instance like Python, but it’s not a best practice.
Your Java class is getting long. One of the reasons why Java is more verbose than Python is the notion of public and private methods and attributes.
Public and Private
Java controls access to methods and attributes by differentiating between public data and private data.
In Java, it is expected that attributes are declared as private
, or protected
if subclasses need direct access to them. This limits access to these attributes from code outside the class. To provide access to private
attributes, you declare public
methods which set and retrieve data in a controlled manner (more on that later).
Recall from your Java class above that the color
variable was declared as private
. Therefore, this Java code will show a compilation error at the highlighted line:
Car myCar = new Car("blue", "Ford", 1972);
// Paint the car
myCar.color = "red";
If you don’t specify an access level, then the attribute defaults to package protected, which limits access to classes in the same package. You have to mark the attribute as public
if you want this code to work.
However, declaring public attributes is not considered a best practice in Java. You’re expected to declare attributes as private
, and use public
access methods, such as the .getColor()
and .getModel()
shown in the code.
Python doesn’t have the same notion of private
or protected
data that Java does. Everything in Python is public
. This code works with your existing Python class just fine:
>>> my_car = car.Car("blue", "Ford", 1972)
>>> # Paint the car
... my_car.color = "red"
Instead of private
, Python has a notion of a non-public instance variable. Any variable which starts with an underscore character is defined to be non-public. This naming convention makes it harder to access a variable, but it’s only a naming convention, and you can still access the variable directly.
Add the following line to your Python Car
class:
class Car:
wheels = 0
def __init__(self, color, model, year):
self.color = color
self.model = model
self.year = year
self._cupholders = 6
You can access the ._cupholders
variable directly:
>>> import car
>>> my_car = car.Car("yellow", "Beetle", "1969")
>>> print(f"It was built in {my_car.year}")
It was built in 1969
>>> my_car.year = 1966
>>> print(f"It was built in {my_car.year}")
It was built in 1966
>>> print(f"It has {my_car._cupholders} cupholders.")
It has 6 cupholders.
Python lets you access ._cupholders
, but IDEs such as VS Code may issue a warning through linters that support PEP 8. For more on PEP 8, read How to Write Beautiful Python Code With PEP 8.
Here’s the code in VS Code, with a warning highlighted and displayed:
Python further recognizes using double underscore characters in front of a variable to conceal an attribute in Python. When Python sees a double underscore variable, it changes the variable name internally to make it difficult to access directly. This mechanism avoids accidents but still doesn’t make data impossible to access.
To show this mechanism in action, change the Python Car
class again:
class Car:
wheels = 0
def __init__(self, color, model, year):
self.color = color
self.model = model
self.year = year
self.__cupholders = 6
Now, when you try to access the .__cupholders
variable, you see the following error:
>>> import car
>>> my_car = car.Car("yellow", "Beetle", "1969")
>>> print(f"It was built in {my_car.year}")
It was built in 1969
>>> my_car.year = 1966
>>> print(f"It was built in {my_car.year}")
It was built in 1966
>>> print(f"It has {my_car.__cupholders} cupholders.")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Car' object has no attribute '__cupholders'
So why doesn’t the .__cupholders
attribute exist?
When Python sees an attribute with double underscores, it changes the attribute by prefixing the original name of the attribute with an underscore, followed by the class name. To use the attribute directly, you need to change the name you use as well:
>>> print(f"It has {my_car._Car__cupholders} cupholders")
It has 6 cupholders
When you use double underscores to conceal an attribute from the user, Python changes the name in a well-documented manner. This means that a determined developer can still access the attribute directly.
So if your Java attributes are declared private
, and your Python attributes are prefaced with double underscores, then how do you provide and control access to the data they store?
Access Control
In Java, you access private
attributes using setters and getters. To allow users to paint their cars, add the following code to your Java class:
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
Since .getColor()
and .setColor()
are public
, anyone can call them to change or retrieve the car’s color. Java’s best practices of using private
attributes accessed with public
getters and setters is one of the reasons why Java code tends to be more verbose than Python.
As you saw above, you access attributes directly in Python. Since everything is public
, you can access anything at any time from anywhere. You set and get attribute values directly by referring to their names. You can even delete attributes in Python, which isn’t possible in Java:
>>> my_car = Car("yellow", "beetle", 1969)
>>> print(f"My car was built in {my_car.year}")
My car was built in 1969
>>> my_car.year = 1966
>>> print(f"It was built in {my_car.year}")
It was built in 1966
>>> del my_car.year
>>> print(f"It was built in {my_car.year}")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Car' object has no attribute 'year'
However, there are times you may want to control access to an attribute. In that case, you can use Python properties.
In Python, properties provide controllable access to class attributes using Python decorator syntax. (You can learn about decorators in the video course Python Decorators 101.) Properties allow functions to be declared in Python classes that are analogous to Java getter and setter methods, with the added bonus of allowing you to delete attributes as well.
You can see how properties work by adding one to your Car
class:
1class Car:
2 def __init__(self, color, model, year):
3 self.color = color
4 self.model = model
5 self.year = year
6 self._voltage = 12
7
8 @property
9 def voltage(self):
10 return self._voltage
11
12 @voltage.setter
13 def voltage(self, volts):
14 print("Warning: this can cause problems!")
15 self._voltage = volts
16
17 @voltage.deleter
18 def voltage(self):
19 print("Warning: the radio will stop working!")
20 del self._voltage
Here, you expand the notion of the Car
to include electric vehicles. You declare the ._voltage
attribute to hold the battery voltage on line 6.
To provide controlled access, you define a function called voltage()
to return the private value on lines 9 and 10. By using the @property
decoration, you mark it as a getter that anyone can access directly.
Similarly, you define a setter function on lines 13 to 15, also called voltage()
. However, you decorate this function with @voltage.setter
. Lastly, you use @voltage.deleter
to decorate a third voltage()
on lines 18 to 20, which allows controlled deletion of the attribute.
The names of the decorated functions are all the same, indicating they control access to the same attribute. The function names also become the name of the attribute you use to access the value. Here’s how these properties work in practice:
1>>> from car import *
2>>> my_car = Car("yellow", "beetle", 1969)
3
4>>> print(f"My car uses {my_car.voltage} volts")
5My car uses 12 volts
6
7>>> my_car.voltage = 6
8Warning: this can cause problems!
9
10>>> print(f"My car now uses {my_car.voltage} volts")
11My car now uses 6 volts
12
13>>> del my_car.voltage
14Warning: the radio will stop working!
Note that you use .voltage
in the highlighted lines above, not ._voltage
. This tells Python to use the property functions you defined:
- When you print the value of
my_car.voltage
on line 4, Python calls.voltage()
decorated with@property
. - When you assign a value to
my_car.voltage
on line 7, Python calls.voltage()
decorated with@voltage.setter
. - When you delete
my_car.voltage
on line 13, Python calls.voltage()
decorated with@voltage.deleter
.
The @property
, @.setter
, and @.deleter
decorations make it possible to control access to attributes without requiring users to use different methods. You can even make attributes appear to be read-only properties by omitting the @.setter
and @.deleter
decorated functions.
self
and this
In Java, a class refers to itself with the this
reference:
public void setColor(String color) {
this.color = color;
}
this
is implicit in Java code: it doesn’t normally need to be written, unless there may be confusion between two variables with the same name.
You can write the same setter this way:
public void setColor(String newColor) {
color = newColor;
}
Since Car
has an attribute named .color
, and there isn’t another variable in scope with the same name, a reference to that name works. We used this
in the first example to differentiate between the attribute and parameter both named color
.
In Python, the keyword self
serves a similar purpose. It’s how you refer to member variables, but unlike Java’s this
, it’s required if you want to create or refer to a member attribute:
class Car:
def __init__(self, color, model, year):
self.color = color
self.model = model
self.year = year
self._voltage = 12
@property
def voltage(self):
return self._voltage
Python requires each self
in the code above. Each one either creates or refers to the attributes. If you omit them, then Python will create a local variable instead of an attribute.
The difference between how you use self
and this
in Python and Java is due to underlying differences between the two languages and how they name variables and attributes.
Methods and Functions
This difference between Python vs Java is, simply put, that Python has functions, and Java doesn’t.
In Python, the following code is perfectly fine (and very common):
>>> def say_hi():
... print("Hi!")
...
>>> say_hi()
Hi!
You can call say_hi()
from anywhere it’s visible. This function has no reference to self
, which indicates that it’s a global function, not a class function. It can’t alter or store any data in any classes but can use local and global variables.
In contrast, every line of Java code you write belongs to a class. Functions can’t exist outside of a class, and by definition, all Java functions are methods. In Java, the closest you can get to a pure function is by using a static method:
public class Utils {
static void SayHi() {
System.out.println("Hi!");
}
}
Utils.SayHi()
is callable from anywhere without first creating an instance of Utils
. Since you can call SayHi()
without creating an object first, the this
reference doesn’t exist. However, this is still not a function in the sense that say_hi()
is in Python.
Inheritance and Polymorphism
Inheritance and polymorphism are two fundamental concepts in object-oriented programming.
Inheritance allows objects to derive attributes and functionality from other objects, creating a hierarchy moving from more general objects to more specific. For example, a Car
and a Boat
are both specific types of Vehicles
. Objects can inherit their behavior from a single parent object or multiple parent objects, and are referred to as child objects when they do.
Polymorphism allows two or more objects to behave like one another, which allows them to be used interchangeably. For example, if a method or function knows how to paint a Vehicle
object, then it can also paint a Car
or Boat
object, since they inherit their data and behavior from the Vehicle
.
These fundamental OOP concepts are implemented quite differently in Python vs Java.
Inheritance
Python supports multiple inheritance, or creating classes that inherit behavior from more than one parent class.
To see how this works, update the Car
class by breaking it into two categories, one for vehicles, and one for devices that use electricity:
class Vehicle:
def __init__(self, color, model):
self.color = color
self.model = model
class Device:
def __init__(self):
self._voltage = 12
class Car(Vehicle, Device):
def __init__(self, color, model, year):
Vehicle.__init__(self, color, model)
Device.__init__(self)
self.year = year
@property
def voltage(self):
return self._voltage
@voltage.setter
def voltage(self, volts):
print("Warning: this can cause problems!")
self._voltage = volts
@voltage.deleter
def voltage(self):
print("Warning: the radio will stop working!")
del self._voltage
A Vehicle
is defined as having .color
and .model
attributes. Then, a Device
is defined to have a ._voltage
attribute. Since the original Car
object had these three attributes, it can be redefined to inherit both the Vehicle
and Device
classes. The color
, model
, and _voltage
attributes will be part of the new Car
class.
In the .__init__()
for Car
, you call the .__init__()
methods for both of the parent classes to make sure everything is initialized properly. Once done, you can add any other functionality you want to your Car
. In this case, you add a .year
attribute specific to Car
objects, and getter and setter methods for .voltage
.
Functionally, the new Car
class behaves as it always has. You create and use Car
objects just as before:
>>> from car import *
>>> my_car = Car("yellow", "beetle", 1969)
>>> print(f"My car is {my_car.color}")
My car is yellow
>>> print(f"My car uses {my_car.voltage} volts")
My car uses 12 volts
>>> my_car.voltage = 6
Warning: this can cause problems!
>>> print(f"My car now uses {my_car.voltage} volts")
My car now uses 6 volts
Java, on the other hand, only supports single inheritance, which means classes in Java can inherit data and behavior from only a single parent class. However, Java objects can inherit behavior from many different interfaces. Interfaces provide a group of related methods an object must implement, and allow multiple child classes to behave similarly.
To see this in action, split the Java Car
class into a parent class and an interface
:
public class Vehicle {
private String color;
private String model;
public Vehicle(String color, String model) {
this.color = color;
this.model = model;
}
public String getColor() {
return color;
}
public String getModel() {
return model;
}
}
public interface Device {
int getVoltage();
}
public class Car extends Vehicle implements Device {
private int voltage;
private int year;
public Car(String color, String model, int year) {
super(color, model);
this.year = year;
this.voltage = 12;
}
@Override
public int getVoltage() {
return voltage;
}
public int getYear() {
return year;
}
}
Remember that each class
and interface
needs to live in its own file.
As you did with Python, you create a new class called Vehicle
to hold the more general vehicle related data and functionality. However, to add the Device
functionality, you need to create an interface
instead. This interface
defines a single method to return the voltage of the Device
.
Redefining the Car
class requires you to inherit from Vehicle
using extend
, and implement the Device
interface using implements
. In the constructor, you call the parent class constructor using the built-in super()
. Since there is only one parent class, it can only refer to the Vehicle
constructor. To implement the interface
, you write getVoltage()
using the @Override
annotation.
Rather than getting code reuse from Device
as Python did, Java requires you to implement the same functionality in every class that implements the interface
. Interfaces only define the methods—they cannot define instance data or implementation details.
So why is this the case for Java? It all comes down to types.
Types and Polymorphism
Java’s strict type checking is what drives its interface
design.
Every class
and interface
in Java is a type. Therefore, if two Java objects implement the same interface
, then they are considered to be the same type with respect to that interface
. This mechanism allows different classes to be used interchangeably, which is the definition of polymorphism.
You can implement device charging for your Java objects by creating a .charge()
that takes a Device
to charge. Any object that implements the Device
interface can be passed to .charge()
. This also means that classes that do not implement Device
will generate a compilation error.
Create the following class in a file called Rhino.java
:
public class Rhino {
}
Now you can create a new Main.java
to implement .charge()
and explore how Car
and Rhino
objects differ:
public class Main{
public static void charge(Device device) {
device.getVoltage();
}
public static void main(String[] args) throws Exception {
Car car = new Car("yellow", "beetle", 1969);
Rhino rhino = new Rhino();
charge(car);
charge(rhino);
}
}
Here is what you should see when you try to build this code:
Information:2019-02-02 15:20 - Compilation completed with
1 error and 0 warnings in 4 s 395 ms
Main.java
Error:(43, 11) java: incompatible types: Rhino cannot be converted to Device
Since the Rhino
class doesn’t implement the Device
interface, it can’t be passed into .charge()
.
In contrast to Java’s strict variable typing, Python uses a concept called duck typing, which in basic terms means that if a variable “walks like a duck and quacks like a duck, then it’s a duck.” Instead of identifying objects by type, Python examines their behavior. You can learn more about the Python type system, and duck typing, in The Ultimate Guide to Python Type Checking.
You can explore duck typing by implementing similar device charging capabilities for your Python Device
class:
>>> def charge(device):
... if hasattr(device, '_voltage'):
... print(f"Charging a {device._voltage} volt device")
... else:
... print(f"I can't charge a {device.__class__.__name__}")
...
>>> class Phone(Device):
... pass
...
>>> class Rhino:
... pass
...
>>> my_car = Car("yellow", "Beetle", "1966")
>>> my_phone = Phone()
>>> my_rhino = Rhino()
>>> charge(my_car)
Charging a 12 volt device
>>> charge(my_phone)
Charging a 12 volt device
>>> charge(my_rhino)
I can't charge a Rhino
charge()
must check for the existence of the ._voltage
attribute in the object it’s passed. Since the Device
class defines this attribute, any class that inherits from it (such as Car
and Phone
) will have this attribute, and will therefore show they are charging properly. Classes that do not inherit from Device
(like Rhino
) may not have this attribute, and will not be able to charge (which is good, since charging rhinos can be dangerous).
Default Methods
All Java classes descend from the Object
class, which contains a set of methods every other class inherits. Subclasses can either override them or keep the defaults. The Object
class defines the following methods:
class Object {
boolean equals(Object obj) { ... }
int hashCode() { ... }
String toString() { ... }
}
By default, equals()
compares the addresses of the current Object
with a second Object
passed in, and hashcode()
computes a unique identifier that also uses the address of the current Object
. These methods are used in many different contexts in Java. For example, utility classes, such as collections that sort objects based on value, need both of them.
toString()
returns a String
representation of the Object
. By default, this is the name of the class and the address. This method is called automatically when an Object
is passed to a method that requires a String
argument, such as System.out.println()
:
Car car = new Car("yellow", "Beetle", 1969);
System.out.println(car);
Running this code will use the default .toString()
to show the car
object:
Car@61bbe9ba
Not very useful, right? You can improve this by overriding the default .toString()
. Add this method to your Java Car
class:
public String toString() {
return "Car: " + getColor() + " : " + getModel() + " : " + getYear();
}
Now, when you run the same sample code, you’ll see the following:
Car: yellow : Beetle : 1969
Python provides similar functionality with a set of common dunder (short for “double underscore”) methods. Every Python class inherits these methods, and you can override them to modify their behavior.
For string representations of an object, Python provides __repr__()
and __str__()
, which you can learn about in Pythonic OOP String Conversion: __repr__
vs __str__
. The unambiguous representation of an object is returned by __repr__()
, while __str__()
returns a human readable representation. These are roughly analogous to .hashcode()
and .toString()
in Java.
Like Java, Python provides default implementations of these dunder methods:
>>> my_car = Car("yellow", "Beetle", "1966")
>>> print(repr(my_car))
<car.Car object at 0x7fe4ca154f98>
>>> print(str(my_car))
<car.Car object at 0x7fe4ca154f98>
You can improve this output by overriding .__str__()
, adding this to your Python Car
class:
def __str__(self):
return f'Car {self.color} : {self.model} : {self.year}'
This gives you a much nicer result:
>>> my_car = Car("yellow", "Beetle", "1966")
>>> print(repr(my_car))
<car.Car object at 0x7f09e9a7b630>
>>> print(str(my_car))
Car yellow : Beetle : 1966
Overriding the dunder method gave us a more readable representation of your Car
. You may want to override the .__repr__()
as well, as it is often useful for debugging.
Python offers a lot more dunder methods. Using dunder methods, you can define your object’s behavior during iteration, comparison, addition, or making an object callable directly, among other things.
Operator Overloading
Operator overloading refers to redefining how Python operators work when operating on user-defined objects. Python’s dunder methods allow you to implement operator overloading, something that Java doesn’t offer at all.
Modify your Python Car
class with the following additional dunder methods:
class Car:
def __init__(self, color, model, year):
self.color = color
self.model = model
self.year = year
def __str__(self):
return f'Car {self.color} : {self.model} : {self.year}'
def __eq__(self, other):
return self.year == other.year
def __lt__(self, other):
return self.year < other.year
def __add__(self, other):
return Car(self.color + other.color,
self.model + other.model,
int(self.year) + int(other.year))
The table below shows the relationship between these dunder methods and the Python operators they represent:
Dunder Method | Operator | Purpose |
---|---|---|
__eq__ |
== |
Do these Car objects have the same year? |
__lt__ |
< |
Which Car is an earlier model? |
__add__ |
+ |
Add two Car objects in a nonsensical way |
When Python sees an expression containing objects, it calls any dunder methods defined that correspond to operators in the expression. The code below uses these new overloaded arithmetic operators on a couple of Car
objects:
>>> my_car = Car("yellow", "Beetle", "1966")
>>> your_car = Car("red", "Corvette", "1967")
>>> print (my_car < your_car)
True
>>> print (my_car > your_car)
False
>>> print (my_car == your_car)
False
>>> print (my_car + your_car)
Car yellowred : BeetleCorvette : 3933
There are many more operators you can overload using dunder methods. They offer a way to enrich your object’s behavior in a way that Java’s common base class default methods don’t.
Reflection
Reflection refers to examining an object or class from within the object or class. Both Java and Python offer ways to explore and examine the attributes and methods in a class.
Examining an Object’s Type
Both languages have ways to test or check an object’s type.
In Python, you use type()
to display the type of a variable, and isinstance()
to determine if a given variable is an instance or child of a specific class:
>>> my_car = Car("yellow", "Beetle", "1966")
>>> print(type(my_car))
<class 'car.Car'>
>>> print(isinstance(my_car, Car))
True
>>> print(isinstance(my_car, Device))
True
In Java, you query the object for its type using .getClass()
, and use the instanceof
operator to check for a specific class:
Car car = new Car("yellow", "beetle", 1969);
System.out.println(car.getClass());
System.out.println(car instanceof Car);
This code outputs the following:
class com.realpython.Car
true
Examining an Object’s Attributes
In Python, you can view every attribute and function contained in any object (including all the dunder methods) using dir()
. To get the specific details of a given attribute or function, use getattr()
:
>>> print(dir(my_car))
['_Car__cupholders', '__add__', '__class__', '__delattr__', '__dict__',
'__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',
'__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__',
'__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
'__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__',
'_voltage', 'color', 'model', 'voltage', 'wheels', 'year']
>>> print(getattr(my_car, "__format__"))
<built-in method __format__ of Car object at 0x7fb4c10f5438>
Java has similar capabilities, but the language’s access control and type safety make it more complicated to retrieve.
.getFields()
retrieves a list of all publicly accessible attributes. However, since none of the attributes of Car
are public
, this code returns an empty array:
Field[] fields = car.getClass().getFields();
Java treats attributes and methods as separate entities, so public
methods are retrieved using .getDeclaredMethods()
. Since public
attributes will have a corresponding .get
method, one way to discover if a class contains a specific property might look like this:
- Use
.getDeclaredMethods()
to generate an array of all the methods. - Loop through all the methods returned:
- For each method discovered, return true if the method:
- Begins with the word
get
OR accepts zero arguments - AND doesn’t return
void
- AND includes the name of the property
- Begins with the word
- Otherwise, return false.
- For each method discovered, return true if the method:
Here’s a quick-and-dirty example:
1public static boolean getProperty(String name, Object object) throws Exception {
2
3 Method[] declaredMethods = object.getClass().getDeclaredMethods();
4 for (Method method : declaredMethods) {
5 if (isGetter(method) &&
6 method.getName().toUpperCase().contains(name.toUpperCase())) {
7 return true;
8 }
9 }
10 return false;
11}
12
13// Helper function to get if the method is a getter method
14public static boolean isGetter(Method method) {
15 if ((method.getName().startsWith("get") ||
16 method.getParameterCount() == 0 ) &&
17 !method.getReturnType().equals(void.class)) {
18 return true;
19 }
20 return false;
21}
getProperty()
is your entry point. Call this with the name of an attribute and an object. It returns true
if the property is found, and false
if not.
Calling Methods Through Reflection
Both Java and Python provide mechanisms to call methods through reflection.
In the Java example above, instead of simply returning true
if the property was found, you could call the method directly. Recall that getDeclaredMethods()
returns an array of Method
objects. The Method
object itself has a method called .invoke()
, which will call the Method
. Instead of returning true
when the correct method is found on line 7 above, you can return method.invoke(object)
instead.
This capability exists in Python as well. However, since Python doesn’t differentiate between functions and attributes, you have to look specifically for entries that are callable
:
>>> for method_name in dir(my_car):
... if callable(getattr(my_car, method_name)):
... print(method_name)
...
__add__
__class__
__delattr__
__dir__
__eq__
__format__
__ge__
__getattribute__
__gt__
__init__
__init_subclass__
__le__
__lt__
__ne__
__new__
__reduce__
__reduce_ex__
__repr__
__setattr__
__sizeof__
__str__
__subclasshook__
Python methods are simpler to manage and call than in Java. Adding the ()
operator (and any required arguments) is all you need to do.
The code below will find an object’s .__str__()
and call it through reflection:
>>> for method_name in dir(my_car):
... attr = getattr(my_car, method_name)
... if callable(attr):
... if method_name == '__str__':
... print(attr())
...
Car yellow : Beetle : 1966
Here, every attribute returned by dir()
is checked. You get the actual attribute object using getattr()
, and check if it’s a callable function using callable()
. If so, you then check if its name is __str__()
, and then call it.
Conclusion
Throughout the course of this article, you learned how object-oriented principles differ in Python vs Java. As you read, you:
- Built a basic class in both Java and Python
- Explored how object attributes work in Python vs Java
- Compared and contrasted Java methods and Python functions
- Discovered inheritance and polymorphism mechanisms in both languages
- Investigated reflection across Python vs Java
- Applied everything in a complete class implementation in both languages
If you want to learn more about OOP in Python, be sure to read Object-Oriented Programming (OOP) in Python 3.
Understanding the differences in Python vs Java when handling objects, and the syntax choices each language makes, will help you apply best practices and make your next project smoother.
In order to compare some concrete examples side by side, you can click the box below to download our sample code to get the complete commented object definitions for the Java Car
, Device
, and Vehicle
classes, as well as the complete commented definitions for the Python Car
and Vehicle
classes:
Download Sample Code: Click here to download the commented sample object definitions and source code for the Java and Python objects in this article.
Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Python vs Java: Object Oriented Programming