📜 ⬆️ ⬇️

Object Oriented Programming in Java and Python: Similarities and Differences

Hi, Habr! I present to you the translation of the article “Object-Oriented Programming in Python vs Java” by John Fincher.


The implementation of object-oriented programming (OOP) in Java and Python is different. The principle of working with objects, types of variables and other language capabilities can cause difficulties when switching from one language to another. This article, which can be useful for both Java programmers who want to learn Python and for Python programmers who have a goal to learn Java better, describes the main similarities and differences between these languages ​​in relation to OOP.


Read more - under the cut.


Examples of classes in Python and Java


First, let's implement the simplest class in Python and Java to illustrate some of the differences in these languages, and we will gradually make changes to this class.
Imagine that we have the following definition of a Car class in Java:


1 public 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 } 

The name of the source Java file must match the name of the class stored in it, so we must name the file Car.java. Each java file can contain only one public class.
The same class in Python will look like this:


  1 class 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, anytime. Save this file as car.py.
Using these classes as a basis, we continue the study of the main components of classes and objects.


Object Attributes


In all object-oriented languages, object data is stored somewhere. In both Python and Java, this data is stored in attributes , which are variables associated with specific objects.


One of the most significant differences between Python and Java is how they define the class and object attributes and how these languages ​​control them. Some of these differences are caused by restrictions imposed by languages, while others are associated with more efficient practice.


Declaration and initialization


In Java, we declare attributes (with their type) inside the class, but outside of all methods. Before using class attributes, we need to define them:


  1 public class Car { 2 private String color; 3 private String model; 4 private int year; 

In Python, we declare and define attributes inside the class method init (), which is an analogue of the constructor in Java:


  1 def __init__(self, color, model, year): 2 self.color = color 3 self.model = model 4 self.year = year 

By specifying the self keyword in front of the variable name, we tell Python that these are attributes. Each instance of the class gets its own copy. All variables in Python are not loosely typed, and attributes are no exception.


Variables can be created outside the init () method, but this will not be the best solution and can lead to hard-to-find bugs. For example, you can add a new wheels attribute to the Car object as follows:


  1 >>> import car 2 >>> my_car = car.Car("yellow", "beetle", 1967) 3 >>> print(f"My car is {my_car.color}") 4 My car is yellow 5 6 >>> my_car.wheels = 5 7 >>> print(f"Wheels: {my_car.wheels}") 8 Wheels: 5 

However, if we forget to specify the expression my_car.wheels = 5 in the 6th line, we get an error:


  1 >>> import car 2 >>> my_car = car.Car("yellow", "beetle", 1967) 3 >>> print(f"My car is {my_car.color}") 4 My car is yellow 5 6 >>> print(f"Wheels: {my_car.wheels}") 7 Traceback (most recent call last): 8 File "<stdin>", line 1, in <module> 9 AttributeError: 'Car' object has no attribute 'wheels' 

In Python, if you declare a variable outside the method, it will be considered as a class variable. Let's change the Car class:


  1 class 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 

Now the use of wheels variable will change Instead of accessing it through an object, we 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}") 4 My car is yellow 5 6 >>> print(f"It has {car.Car.wheels} wheels") 7 It has 0 wheels 8 9 >>> print(f"It has {my_car.wheels} wheels") 10 It has 0 wheels 

Note: in Python, access to a class variable occurs according to the following syntax:


  1. The name of the file containing the class (without the .py extension)
  2. Point
  3. Class name
  4. Point
  5. Variable name

Since we saved the Car class in the car.py file, we access the wheels class variable in the 6th line like this: car.Car.wheels.


When working with the wheels variable, you need to pay attention to the fact that changing the value of an instance variable of the class my_car.wheels does not lead to changing 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}") 6 My car is yellow 7 >>> print(f"It has {my_car.wheels} wheels") 8 It has 0 wheels 9 10 >>> print(f"My other car is {my_other_car.color}") 11 My other car is red 12 >>> print(f"It has {my_other_car.wheels} wheels") 13 It 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") 19 My car has 4 wheels 20 >>> print(f"My other car has {my_other_car.wheels} wheels") 21 My 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") 27 My car has 5 wheels 28 >>> print(f"My other car has {my_other_car.wheels} wheels") 29 My other car has 4 wheels 

On the 2nd and 3rd lines we defined two Car objects: my_car and my_other_car.
At first, the wheels property of both objects is zero. On the 16th line, we set the class variable: car.Car.wheels = 4, both objects now have 4 wheels. However, then when on the 24th line we change the property of the object my_car.wheels = 5, the property of the second object remains intact.


This means that we now have two different copies of the wheels attribute:


  1. Class variable that applies to all Car objects
  2. A specific class instance variable that applies only to the my_car object.
    Because of this, you can accidentally refer to the wrong copy and make an inconspicuous mistake.

In Java, the equivalent of 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; } } 

Usually we refer to static variables in Java through the class name. You can refer to them through an instance of the class, as in Python, but this would not be the best solution.


Our java class is starting to lengthen. One of the reasons why Java is “more verbose” in Python is the concept of public (public) and private (private) methods and attributes.


Public and private


Java controls access to methods and attributes by distinguishing between public and private data.
In Java, it is expected that attributes will be declared as private (or protected — protected if you need to provide access to class descendants). Thus, we restrict access to them from the outside. To provide access to private attributes, we declare public methods that establish or obtain this data (more on this later).


Recall that in our Java code, the color variable was declared private. Therefore, the following code will not compile:


 Car myCar = new Car("blue", "Ford", 1972); // Paint the car myCar.color = "red"; 

If you do not specify the level of access to attributes, then by default it will be set as package protected , which limits access to classes within the package. If we want the above code to work, then we will have to make the attribute public.


However, Java does not welcome declaring attributes public. It is recommended to declare them private, and then use public methods, like getColor () and getModel (), as indicated in the code above.


In contrast, Python lacks the notion of public and private data. In Python, everything is public. This Python code will work with a bang:


 >>> my_car = car.Car("blue", "Ford", 1972) >>> # Paint the car ... my_car.color = "red" 

Instead of private variables, Python has the notion of non-public instance variables of a class. All variables whose names begin with a single underscore are considered non-public. This naming convention does not prevent us from accessing the variable directly.


Add the following line to our 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 

We 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 allows you to access such a variable, although some development environments like VS Code will issue a warning:



In addition, in Python, in order to hide an attribute, a double underscore is used at the beginning of the variable name. When Python sees such a variable, it automatically changes its name to make it difficult to access it directly. However, this mechanism still does not prevent us from addressing it. Let us demonstrate this with the following example:


 class Car: wheels = 0 def __init__(self, color, model, year): self.color = color self.model = model self.year = year self.__cupholders = 6 

Now, if we turn to the variable __cupholders, we get an 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 does the __cupholders attribute not exist?
The point is this. When Python sees an attribute with a double underscore at the very beginning, it changes it by adding the class name with an underscore to the beginning. In order to access the attribute directly, you must also change the name:


 >>> print(f"It has {my_car._Car__cupholders} cupholders") It has 6 cupholders 

Now the question arises: if the Java class attribute is declared private and the Python class attribute is preceded by a double underscore in the name, then how to reach this data?


Access control


In Java, we access private attributes using setters and getters . In order for the user to repaint his car, we add the following piece of code to the Java class:


 public String getColor() { return color; } public void setColor(String color) { this.color = color; } 

Since the getColor () and setColor () methods are public, any user can call them and get / change the color of the machine. The use of private attributes, which we access by public getters and setters, is one of the reasons why Java is more verbose than Python.


As shown above, in Python we can access attributes directly. Since everything is public, we can reach out to anything, anytime, anywhere. We can get and set attribute values ​​directly by calling on their name. In Python, we can even delete attributes, which is unthinkable 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, it also happens that we want to control access to attributes. In this case, Python-properties (properties) come to the rescue.


In Python, properties provide controlled access to class attributes using decorators. Using properties, we declare functions in Python classes like getters and setters in Java (bonus is the removal of attributes).


The work of properties can be seen in the following Car class example:


  1 class 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 

In this example, we are expanding the concept of Car class, including electric cars. Line 6 declares the _voltage attribute to store the battery voltage in it.


In lines 9 and 10, for controlled access, we create the voltage () function and return the value of the private variable. Using the @property decorator, we turn it into a getter, to which now any user gets access.


In lines 13-15, we define a function, also called voltage (). However, we decorate it differently: voltage .setter. Finally, in lines 18-20, we decorate the voltage () function with the help of voltage .deleter and we can remove the _voltage attribute if necessary.


Decorated functions have the same name, indicating that they control access to the same attribute. These function names also become the attribute names used to get their values. Here's how it works:


  1 >>> from car import * 2 >>> my_car = Car("yellow", "beetle", 1969) 3 4 >>> print(f"My car uses {my_car.voltage} volts") 5 My car uses 12 volts 6 7 >>> my_car.voltage = 6 8 Warning: this can cause problems! 9 10 >>> print(f"My car now uses {my_car.voltage} volts") 11 My car now uses 6 volts 12 13 >>> del my_car.voltage 14 Warning: the radio will stop working! 

Please note that we use voltage, not _voltage. So we tell Python to use the properties that we just defined:



The above decorators give us the ability to control access to attributes without using different methods. You can even make an attribute a read-only property by removing the decorated @ .setter and @ .deleter functions.


self and this


In Java, the class refers to itself using the this keyword:


 public void setColor(String color) { this.color = color; } 

this is implied in java code. In principle, it is not even necessary to write it, except when the variable names are the same.


You can write a setter like this:


 public void setColor(String newColor) { color = newColor; } 

Since the Car class has an attribute called color and there are no more variables with that name in the scope, the link to this name works. We used the this keyword in the first example to distinguish between an attribute and a parameter with the same name color.


In Python, the self keyword serves a similar purpose: referring to member-attributes, but unlike Java, it is mandatory :


 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 self writing. Each self either creates or refers to an attribute. If we skip it, then Python will simply create a local variable instead of an attribute.


The difference in how we use self and this in Python and Java is due to the basic differences between the two languages ​​and the way they name variables and attributes.


Methods and Functions


The difference between the languages ​​in question is that there are functions in Python, but not in Java.


In Python, the following code will work without problems (and is used everywhere):


 >>> def say_hi(): ... print("Hi!") ... >>> say_hi() Hi! 

We can call say_hi () from any location. This function does not contain a reference to self, which means that it is a global function, not a class function. It will not be able to change or save any data of any class, but it may use local and global variables.


In contrast, every line we write in Java belongs to a class. Functions do not exist outside the class, and by definition all Java functions are methods. In Java, the static method is closest to the pure function:


 public class Utils { static void SayHi() { System.out.println("Hi!"); } } 

Utils. SayHi () is called from anywhere without first creating an instance of the Utils class. Since we call SayHi () without creating an object, this reference does not 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 OOP. Thanks to the first, objects get (in other words, inherit) the attributes and functionality of other objects, creating a hierarchy from more general objects to more specific ones. For example, both Car class (car) and Boat class (boat) are specific types of Vehicle class. Both objects inherit the behavior of a single parent object or a set of parent objects. In this case, they are called child objects.


Polymorphism, in turn, is the ability to work with different objects using the same function or method.


Both of these fundamental OOP concepts are implemented in Java and Python in completely different ways.


Inheritance


Python supports multiple inheritance, that is, creating a class from more than one parent.


To demonstrate this, we divide the Car class into two categories: one for vehicles and one for cars using 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 

In the Vehicle class, the color and model attributes are defined. The Device class has the _voltage attribute. The Car class comes from these two classes, and the color, model, and _voltage attributes are now part of a new class.


The init () method of the Car class calls the init () methods of both parent classes so that all data is initialized properly. After that, we can add any desired functionality to the Car class. In this example, we will add the year attribute, as well as the getter and setter for _voltage.


The functionality of the new Car class remains the same. We can create and use class objects, as we did with several examples earlier:


 >>> 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 

The Java language, in turn, supports only single inheritance, which means that classes in Java can inherit data and behavior from only one parent class. But in Java, inheritance from multiple interfaces is possible. Interfaces provide a group of related methods that need to be implemented, allowing child classes to behave in a similar way.


To see this, we divide the Java Car class into a parent class and 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; } } 

Do not forget that every class and every interface in Java must be placed in its own file.


As in the above example with Python, we create a new class Vehicle for storing common data and functionality inherent in vehicles. However, to add Device functionality, we need to create an interface that defines the method for obtaining the voltage (voltage) of the device.


The Car class is created by inheriting from the Vehicle class using the extends keyword and implementing the Device interface using the implements keyword. In the class constructor, we call the parent constructor using super (). Since there is only one parent class, we refer to the constructor of the Vehicle class. To implement the interface, override getVoltage () with the Override annotation.


Instead of reusing code from Device, as is done in Python, Java requires that we implement the same functionality in each class that implements the interface. Interfaces merely define methods — they cannot define the class instance data or implementation details.


So why does this happen with Java? .



Java . , Java- , , . , .


Java- charge(), Device. , Device, charge().


Rhino.java:


 public class Rhino { } 

Main.java charge() , Car Rhino.


 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); } } 

    ,    : 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 

Rhino Device, charge().


( — strict variable typing, , Python ) , Java, Python , : « , » ( : " , , , , " – . ). , Python .


Python:


 >>> 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() _voltage. Device , - (Car Phone) , , , . , Device ( Rhino), , , , (rhino) .



Java Object, . , . Object :


 class Object { boolean equals(Object obj) { ... } int hashCode() { ... } String toString() { ... } } 

equals() , , hashCode() , . Java . , , , .


toString() . . , , , , System.out.println():


 Car car = new Car("yellow", "Beetle", 1969); System.out.println(car); 

car:


 Car@61bbe9ba 

, ? , toString(). Car:


 public String toString() { return "Car: " + getColor() + " : " + getModel() + " : " + getYear(); } 

, , :


 Car: yellow : Beetle : 1969 

Python (dunder — double underscore). Python- , , , .


Python : repr () str (). repr (), str () . hashcode() toString() Java.


Java, Python :


 >>> my_car = Car("yellow", "Beetle", "1966") >>> print(repr(my_car)) <car.Car object at 0x7fe4ca154f98> >>> print(str(my_car)) <car.Car object at 0x7fe4ca154f98> 

, str () Python- Car:


 def __str__(self): return f'Car {self.color} : {self.model} : {self.year}' 

:


 >>> my_car = Car("yellow", "Beetle", "1966") >>> print(repr(my_car)) <car.Car object at 0x7f09e9a7b630> >>> print(str(my_car)) Car yellow : Beetle : 1966 

. repr (), .


Python , , , .



Python . Python , Java .


Python- Car :


 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)) 

, :


eq==Car?
lt<Car ?
add+Car

Python , , , .


Car:


 >>> 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 

, , , Java.


Reflection


– . Java, Python .



. Python type() isinstance () , :


 >>> 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 

Java getClass() instanceof :


 Car car = new Car("yellow", "beetle", 1969); System.out.println(car.getClass()); System.out.println(car instanceof Car); 

:


 class com.realpython.Car true 


Python dir() , ( ). , 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 , , , .


getFields() . , Car , :


 Field[] fields = car.getClass().getFields(); 

Java , getDeclaredMethods(). get-, , , :


1) getDeclaredMethods()
2) :



:


  1 public 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 14 public 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() – . . true, , false.



Java, Python .
Java- true , , . , getDeclaredMethods() Method. Method invoke(), Method. 7 true, , method.invoke(object).


Python. , Python , , :


 >>> 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 , Java. str () :


 >>> 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 

, dir(). , getattr(), callable(), . , , str (), .


')

Source: https://habr.com/ru/post/455796/


All Articles