OOP syntax in different programming languages

Reading time10 min

As we saw in the introduction to OOP concepts course, a class is a kind of template for creating objects. It’s in the class that we’ll define our methods and attributes.

OOP is particularly useful when you need to represent data that’s a little more complex than a simple number or string. Of course, there are classes that every programming language (including Python) defines for us: numbers, strings and lists are among them. But we’d be limited if we couldn’t create our own classes.

Class definition

To introduce the syntax, let’s create a first, very simple class representing a person (class Person). For us here, a person is characterized by the surname, first name, age and place of residence (city). The class will therefore have 4 attributes and a constructor.

The creation of this class corresponds to the code below.

class Person:
  """ Represents a person with a name, firstname, age, and city.
  """
  def __init__(self, name: str,  firstname: str, age: int, city: str): # The constructor
    """ Constructs a new Person object with the specified name, firstname, age, and city.
    """
    self.name = name
    self.firstname = firstname
    self.age = age
    self.city = city
/**
 * Represents a person with a name, firstname, age, and city.
 */
public class Person {

  private String name;
  private String firstname;
  private int age;
  private String city;

  /**
   * Constructs a new Person object with the specified name, firstname, age, and city.
   *
   * @param name      the person's last name
   * @param firstname the person's first name
   * @param age       the person's age
   * @param city      the person's city of residence
   */
  public Person(String name, String firstname, int age, String city) { // The constructor
    this.name = name;
    this.firstname = firstname;
    this.age = age;
    this.city = city;
  }
}

Let’s look at the syntax in detail:

  • The class is defined by the class keyword, followed by the class name and the ritual colon :. By convention, class names are capitalized.

  • A docstring commenting on the class. This is optional, but highly recommended. It is placed just after the class name and surrounded by three quotation marks """.

  • The constructor definition. The name of a constructor is always __init__ and the first parameter self. We’ll see later what this means. The other parameters are separated by , as with functions in procedural programming.

  • The constructor contains the declaration and initialization of the class attributes. Here a person is created with a name, firstname, age, and the city where he/she lives.

  • A comment on the class. This is optional, but highly recommended. It is placed just before the class name and surrounded by /** **/.

  • The class is defined by the class keyword, followed by the class name and the ritual {. By convention, class names are capitalized. The public keyword is what is called a visiblity modifier. It indicates that the class is visible from all the other classes of the program.

  • Attributes declaration. Attributes are declared outside the code of a method. The private keyword (visibility modifier) indicates that the attribute is accessible only from within the class, i.e., the encapsulation principle is applied.

  • The constructor definition. The name of a constructor corresponds to the name of the class. The parameters are separated by , as with functions in procedural programming. Again, it has a visibility modifier to indicate that the constructor can be used from outside the class (any other class can create instances of this class).

  • The constructor contains the declaration and initialization of the class attributes. Here a person is created with a name, firstname, age, and the city where he/she lives. We’ll see later what the this keyword means.

Create a person.py file (or a Person.java file for the Java code) and copy the above code. If you try to execute it, nothing happens, the Java code raises even an exception… You’ve just defined the class, we need to instantiate it before we can use it.

Class instantiation: creating objects

We want to create two people.

if __name__ == '__main__':
  # Create a person named Alice Weber from London and 33 years old
  one_person = Person("Weber", "Alice", 33, "London")
  print(one_person.to_string())

  # Create a person named Bob Smith from Brighton and 25 years old
  a_second_person = Person("Smith", "Bob", 25, "Brighton")
  print(a_second_person)
  public static void main(String[] args) {
    // Create a person named Alice Weber from London and 33 years old
    Person personOne = new Person("Weber", "Alice", 33, "London");
    System.out.println(personOne);
    // Create a person named Alice Weber from London and 33 years old
    Person personTwo = new Person("Smith", "Bob", 25, "Brighton");
    System.out.println(personTwo);
  }

Copy/paste the code above at the end of the person.py file (or the Person.java file for the Java code). Execute it. Now, you can see something not very comprehensible… Here, the most important thing is to see the class from which the object comes. So we can check that both objects do indeed come from our Person class.

We’re now going to add a method that returns a string representing a person, and we’ll call it after instantiating the class.

class Person:
  """ Represents a person with a name, firstname, age, and city.
  """
  def __init__(self, name: str,  firstname: str, age: int, city: str): # The constructor
    """ Constructs a new Person object with the specified name, firstname, age, and city.
    """
    self.name = name
    self.firstname = firstname
    self.age = age
    self.city = city

  def to_string(self) -> str:
    """ Returns a string representation of the person.
    """
    return f"{self.firstname} {self.name} ({self.age} old, from {self.city})"

if __name__ == '__main__':
  one_person = Person("Weber", "Alice", 33, "London")
  print(one_person.to_string())

  a_second_person = Person("Smith", "Bob", 25, "Brighton")
  print(a_second_person.to_string())
/**
 * Represents a person with a name, firstname, age, and city.
 */
public class Person {
  private String name;
  private String firstname;
  private int age;
  private String city;

  /**
   * Constructs a new Person object with the specified name, firstname, age, and city.
   *
   * @param name      the person's last name
   * @param firstname the person's first name
   * @param age       the person's age
   * @param city      the person's city of residence
   */
  public Person(String name, String firstname, int age, String city) { // The constructor
    this.name = name;
    this.firstname = firstname;
    this.age = age;
    this.city = city;
  }

  public String toString() {
    return firstname + " " + name + " (" + age + " old) from " + city;
  }

  public static void main(String[] args) {
    Person personOne = new Person("Weber", "Alice", 33, "London");
    System.out.println(personOne.toString());

    Person personTwo = new Person("Smith", "Bob", 25, "Brighton");
    System.out.println(personTwo.toString());
  }
}

As you can see in the code above, the definition of a method in a class is similar to the definition of a classic function, apart from the existence of the self keyword as the first argument of the method. The method call is made by preceding the name of the method by the object on which the method is to be called. In our exemple, one_person.to_string() executes the code of the to_string() method of the one_person object.

Details

Python offers what are known as special methods. These are methods that Python recognizes and knows how to use. The name of such a method takes a special syntax: __specialmethod__. In fact, you’re already familiar with one, __init__. Another such method is __str__ (which takes no arguments apart from self like any other method), which is called by Python to display an object. You can test this by adding the __str__ (self) method to our Person class and using print(one_person) to see the result. Similarly, the __repr__ method is called by Python to display an object in the console. You can test this by adding the __repr__ (self) method to our Person class and using one_person to see the result. __str__ is used to display the object to the user in human-readable way. __repr__ provides a complete string representation of the object, which is useful for debugging, logging, and development. Thus, it is more for developers than for users.

As you can see in the code above, the definition of a method consists of the name of the method, its parameters (type and name) separated by ,, the result type of the method if any, its visibility, and the code of the method. The method call is made by preceding the name of the method by the object on which the method is to be called. In our exemple, personOne.toString() executes the code of the toString() method of the personOne object.

Details

Every class in Java is a child of the Object class either directly or indirectly. And since the Object class contains a toString() method, we can call toString() on any instance and get its (default) string representation. So, if we don’t define a toString() method in our class, then Object#toString() is invoked. You can test this by adding the method to our Person class and using System.out.println(personOne) to see the result. Here, we override the toString() method to return a custom string representation of the object.

Complementay information can be found here.

Now we can talk about the self (this in Java) keyword… When you create an object, in this case a person, the values of its attributes are unique to it (the value of the name attribute of one_person (personOne) is not the same as that of a_second_person (personTwo)). This is logical, as each person has his/her own name, etc. So when you call a method from an object, it’s important to know on which object it should be executed and this is the role of the self (this) keyword.

Encapsulation

Finally, let’s look at the concept of encapsulation. As a reminder, this concept consists in protecting or hiding certain attributes of an object.

In Python, by default, the value of an object’s attribute is directly accessible: just write my_object.my_attribute and you’re done (try the code below in your person.py file). There are two strategies to look for attributes encapsulation:

  • using attributes’ identifier in the form _my_attribute. This _single_leading_underscore is just a convention used in Python to indicate attributes that are not supposed to be read or written from outside the class. But this is only a convention! nothing prevents them from being used directly outside the class. You can read the documentation for more information.

  • using attributes’ identifier in the form __my_attribute. It reinforces encapsulation, as such attributes’ value cannot be directly accessed from outside the class. However, encapsulation is not really implemented as Python mangles these names with the class name: __my_attribute in class Class can be accessed from object o by calling o._Class__my_attribute.

Let’s see in practice for our Person class.

class Person:
  """ Represents a person with a name, firstname, age, and city.
  """
  def __init__(self, name: str,  firstname: str, age: int, city: str): # The constructor
    """ Constructs a new Person object with the specified name, firstname, age, and city.
    """
    self.name = name
    self.firstname = firstname
    self.__age = age
    self._city = city

  def to_string(self) -> str:
    """ Returns a string representation of the person.
    """
    return f"{self.firstname} {self.name} ({self.__age} old, from {self._city})"

if __name__ == '__main__':
  one_person = Person("Weber", "Alice", 33, "London")
  print(one_person.name) # No error. Expected output: "Weber"
  print(one_person._city) # No error. Expected output: "London"
  print(one_person.__age) # Raises an error: AttributeError: 'Person' object has no attribute '__age'
  print(one_person._Person__age) # No error. Expected output: 33
Information

The single and double leading underscore forms can also be used when naming methods to indicate non-public methods. See documentation for more information.

Java fully supports the concept of encapsulation by providing the visibility modifier on attributes of a class. By convention, attributes that are not supposed to be read or written from outside the class must be written as private AttrType myAttribute. Trying to directly access such attributes from outside the class will result in an error (try the code below in your Person.java file).

 public static void main(String[] args) {
    Person personOne = new Person("Weber", "Alice", 33, "London");
    personOne.firstname = "Alice"; // Raises an error: firstname is private
  }
Information

The visibility modifier private can also be used for methods to indicate non-public methods..

Inheritance

Inheritance is a way of structuring code that defines hierarchical relationships between classes. To illustrate how to create classes that inherit from another, let’s suppose we want to create a program that manipulates dogs and cats. Both have a nickname and we want to track the number of meals they have eaten since born. However, dogs bark and cats meow and we want to track the weight of a dog.

As dogs and cats have common characteristics (nickname and number of meals), we decide to create an Animal class with two attributes (one to store the name of the animal and the other one to count the number of times it has eaten) and one method, executed each time the animal eats something.

Bellow you’ll find the code of the Animal class.

# Here, we create a class "Animal"
"""
Represents an animal with a nickname and the number of meals it has eaten.
"""
class Animal ():

  # Classes have a special method "__init__" called a constructor.
  # This is the code that will be executed when you instantiate an animal later.
  # All methods of a class should the keyword "self" as first argument.
  # This keyword references the object itself when the class is instantiated.
  def __init__ (self, name: str):
    """
    Constructs a new Animal object with the specified nickname.
    name: the animal's nickname
    """
    self.nickname = name # Create an attribute to store the name
    self.__nb_meals = 0 # Create an attribute to count how many meals the animal had

  # Now, let's define methods that do custom stuff

  def get_nickname (self) -> str:
    """
    This method returns the nickname of the animal.
    """
    return self.nickname

  def eat_stuff (self, stuff_name: str):
    """
    This method is called when the animal eats something.
    stuff_name: the name of the stuff the animal is eating
    """
    print("Yum, tasty", stuff_name)
    self.__nb_meals += 1

  def get_nb_meals(self) -> int:
    """
    This method returns the number of meals the animal has eaten.
    """
    return self.__nb_meals
// Here we create the class Animal.

/**
 * Represents an animal with a nickname and the number of meals it has eaten.
 * Thus, it has two attributes:
 * - nickname: the name of the animal
 * - nbMeals: the number of times it has eaten
 **/
public class Animal {

  // Attributes
  private String nickname;
  private int nbMeals;

  /**
   * Constructs a new Animal object with the specified nickname.
   * When created an animal has no eaten meal.
   * @param name      the animal's nickname
   */
  public Animal(String name) {
    this.nickname = name;
    this.nbMeals = 0;
  }

  /**
   * Returns the nickname of the animal.
   * @return the nickname of the animal
   */
  public String getNickname() {
    return this.nickname;
  }

  // Now, let's define methods that do custom stuff.

  /**
   * This method is called when the animal eats something.
   * @param stuffName the name of the stuff the animal is eating
   */
  public void eatStuff(String stuffName) {
    System.out.println("Yum, tasty", stuffName)
    this.nbMeals += 1
  }

  /**
   * Returns the number of meals the animal has eaten.
   * @return the number of meals the animal has eaten
   */
  public int getNbMeals() {
    return this.nbMeals
  }
}

Now, how dogs and cats are reified in our program? On one hand, as an animal, a dog should have a nickname and the number of times it has eaten but a dog has also a weight and can bark. On the other hand, a cat is also an animal but it also meows. Therefore, we’re going to implement the Dog and Cat classes as childs of the Animal class so that they have access to its attributes and methods automatically. The Dog class will have a new attribute (weight) and a new method (bark) and the Cat class will provide a method meow. The code for these classes is presented bellow.

# Here, we create a class "Dog"
"""
Represents a dog with a nickname, the number of meals it has eaten, and its weight.
A Dog inherits from an Animal.
"""
class Dog (Animal):

  # It is good practice to also call the parent's constructor
  # Then you can complement with additional codes if needed
  # It is also good practice not to repeat arguments of the parent class
  # Arguments *args and **kwargs are here for this purpose
  def __init__ (self, size, *args, **kwargs):
    """
    Constructs a new Dog object with the specified nickname and weight.
    size: the dog's weight
    """
    super().__init__(*args, **kwargs) # Call parent's constructor
    self.weight = weight # Create an attribute to store the weight

  # The weight of a dog can be accessed
  def get_weight (self) -> float:
    """
    This method returns the weight of the dog.
    """
    return self.weight

  # Now, let's define a new method
  # A dog can bark

  def bark (self):
    """
    This method is called when the dog barks.
    """
    print("Woof")

# Here, we create a class "Cat"
"""
Represents a cat with a nickname and the number of meals it has eaten.
A Cat inherits from an Animal.
"""
class Cat (Animal):

  # It is good practice to also call the parent's constructor
  # Then you can complement with additional codes if needed
  # It is also good practice not to repeat arguments of the parent class
  # Arguments *args and **kwargs are here for this purpose
  def __init__ (self, *args, **kwargs):
    """
    Constructs a new Cat object with the specified nickname.
    """
    super().__init__(*args, **kwargs) # Call parent's constructor

  # Now, let's define a new method
  # A cat can meow

  def meow (self):
    """
    This method is called when the cat meows.
    """
    print("Meow")
// Here we create the class Dog.
/**
 * Represents a dog with a nickname, the number of meals it has eaten, and its weight.
 * A Dog inherits from an Animal.
 **/
public class Dog extends Animal {

  // The specific attribute of a Dog
  private float weight;

  // Because Dog inherits from Animal, the parent's constructor must be called
  // Then you can complement with additional codes if needed

  /**
   * Constructs a new Dog object with the specified nickname and weight.
   *
   * @param name      the dog's nickname
   * @param weight    the dog's weight when created
   */
  public Dog(String name, float weight) {
    super(name); // Call parent's constructor to initialize nickname and nbMeals
    this.weight = weight; // Initialize the weight attribute
  }

  // The weight of a dog can be accessed

  /**
   * This method returns the weight of the dog.
   * @return the weight of the dog
   */
  public float getWeight() {
    return this.weight;
  }

  // Now, let's define a new method
  // A Dog can bark

  /**
   * This method is called when the dog barks.
   */
  public void bark() {
    System.out.println("Woof");
  }
}

// Here we create the class Cat.
/**
 * Represents a cat with a nickname and the number of meals it has eaten.
 * A Cat inherits from an Animal.
 **/
public class Cat extends Animal {

  /**
   * Constructs a new Cat object with the specified nickname.
   *
   * @param name      the cat's nickname
   */
  public Cat(String name) {
    super(name); // Call parent's constructor to initialize nickname and nbMeals
  }

  // Now, let's define a new method
  // A Cat can meow

  /**
   * This method is called when the cat meows.
   */
  public void meow() {
    System.out.println("Meow");
  }
}

Let’s try this code:

# Instantiate an object of class Dog
a_dog = newDog(42, "Snoopy")

# Show its attributes "Snoopy", 42, 0
print(a_dog.get_nickname(), a_dog.get_weight(), a_dog.get_nb_meals())

# Call its methods
a_dog.eat_stuff("cookie") # Shows "Yum, tasty cookie"
a_dog.bark() # Shows "Woof"

# Show its attributes again
print(a_dog.get_nickname(), a_dog.get_weight(), a_dog.get_nb_meals()) # Shows "Snoopy", 42, 1

# Instantiate an object of class Cat
a_cat = Cat("Garfield")

# Show its attributes "Garfield", 0
print(a_cat.get_nickname(), a_cat.get_nb_meals())

# Call its methods
a_cat.eat_stuff("kittens") # Shows "Yum, tasty kittens"
a_cat.meow() # Shows "Meow"

a_cat.bark() # Raises an error
// Instantiate an object of class Dog
Dog aDog = new Dog("Snoopy", 42)

// Show its attributes "Snoopy", 42, 0
System.out.println(a.getNickname() + "," + a.getWeight() + "," + a.getNbMeals())

// Call its methods
aDog.eatStuff("cookie") // Shows "Yum, tasty cookie"
aDog.bark() // Shows "Woof"

// Show its attributes again
System.out.println(a.getNickname() + "," + a.getWeight() + "," + a.getNbMeals()) // Shows "Snoopy", 42, 1

// Instantiate an object of class Cat
Cat aCat = new Cat("Garfield")

// Show its attributes "Garfield", 0
System.out.println(a.nickname + "," + a.getNbMeals())

// Call its methods
aCat.eatStuff("kittens") // Shows "Yum, tasty kittens"
aCat.meow() // Shows "Meow"

aCat.bark() // Raises an error