Mark As Completed Discussion

Special Methods in Object-Oriented Programming

Now you know the bare bones of Object-Oriented Programming. But, as I said in the previous lesson, there is still much more you have to discover. In today's lesson, we will go through a number of special functions that almost all OOP-supported programming languages have for convenience.

Special Methods in Object-Oriented Programming

Keep in mind that these functions are utility functions and are not essential for OOP programming languages, meaning that you can do the same things with or without these functions. However, you'll save a tremendous amount of effort and lines of code if you know these functions. So let's begin. We will be improving our previous example based on Shapes with the new knowledge we will learn in this lesson.

Let's recap what we learned previously and see the final implementation of all the Shape classes.

TEXT/X-JAVA
1// Shape.java
2public class Shape {
3    protected int maxWidth;
4    protected int maxHeight;
5
6    // Getters and Setters
7    public int getMaxWidth() { return maxWidth; }
8    public int getMaxHeight() { return maxHeight; }
9    public void setMaxWidth(int maxWidth) { this.maxWidth = maxWidth; }
10    public void setMaxHeight(int maxHeight) { this.maxHeight = maxHeight; }
11    public int getMaxArea() { return maxWidth*maxHeight; }
12
13    public double getArea() {
14        return getMaxArea();
15    }
16}
17
18// Circle.java
19public class Circle extends Shape {
20    protected int radius;
21
22    // Getters and Setters
23    public int getRadius() { return radius; }
24    public void setRadius(int radius) { this.radius = radius; }
25
26    @Override
27    public double getArea() {
28        return 3.14*radius*radius;
29    }
30}
31
32
33// Rectangle.java
34public class Rectangle extends Shape {
35    protected int height;
36    protected int width;
37
38    // Getters and Setters
39    public int getHeight() { return height; }
40    public int getWidth() { return width; }
41    public void setWidth(int width) { this.width = width; }
42    public void setHeight(int height) { this.height = height; }
43
44    @Override
45    public double getArea() {
46        return (double)height*width;
47    }
48}
49
50// Main.java
51public class Main {
52    public static void main(String[] args) {
53        // Your code here
54    }
55}

Constructors

Constructors

Programmers can't imagine OOP without constructor and destructor functions. Constructor is a special function that helps you instantiate an object of a class. Whenever you instantiate an object, a default constructor method is called.

Let's say we want to create a Rectangle object with a specific width and height (5x6). You would probably do it like below:

TEXT/X-JAVA
1// Main.java
2public class Main {
3    public static void main(String[] args) {
4        Rectangle rect = new Rectangle();
5        rect.setHeight(5);
6        rect.setHeight(6);
7    }
8}

Now, let's see what is actually happening behind the scenes.

SNIPPET
1Create all the attributes in memory with default values (0 for `height` and `width`).
2Create a class reference in memory that has all the attributes.
3Call 2 setter functions to reassign the attributes.

We can see that the attributes are first set to the default (0) when creating the instances. They are then overwritten with setter methods. Imagine you have a class with 10 attributes and you need to set all the attributes to create a usable object. In that case, you would need to call 10 setters which will increase the size of the code and make the code harder to read. This is where a constructor can help. Constructor is a function that is called during instantiation to initialize the attributes of the new object along with any other things you need to do.

Here is the same code to create a 5x6 rectangle but with only 1 line of constructor call.

TEXT/X-JAVA
1// Main.java
2public class Main {
3    public static void main(String[] args) {
4        Rectangle rect = new Rectangle(5,6);
5    }
6}

Of course, we also need to define this constructor method which takes 2 values when creating from the class:

TEXT/X-JAVA
1// Rectangle.java
2public class Rectangle extends Shape {
3    protected int height;
4    protected int width;
5
6    // Default Constructor
7    public Rectangle() {   }
8
9    // User specified constructor
10    public Rectangle(int height, int width) {
11        this.height = height;
12        this.width = width;
13    }
14
15    // Rest of the methods and Attributes
16}

Constructors are very special because they have syntax which differs from typical methods in some languages. Constructors do not return a value (not even null). You may notice that they don't even have a return type (not even void).

Constructors are really powerful for several reasons. Suppose each object needs to dynamically create an array of variable size in it as an attribute. The best way to allocate the memory for that array in the heap would be through constructors. Or if you want to instantiate other objects inside that class, you need to do that inside the constructor.

TEXT/X-JAVA
1// A random class
2public class MyClass {
3    protected MyAnotherClass myProperty;
4    protected int[] myArray;
5
6    public MyClass(int propValue, int arraySize) {
7        myProperty = new MyAnotherClass(propValue);
8        myArray = new int[arraySize];
9    }
10}

There are various constructors depending on their use cases. Let's look at some of them in more detail.

Default Constructor

Whenever you instantiate an object, you are calling a method (notice the double parentheses after new ClassName). This method is the default constructor. Default constructors do not take any parameters and do not need to be explicitly defined by the user. However, you can override the default constructor if you want. For example, if we want a rectangle to be 1x1 initially, then we will need to override the default constructor.

TEXT/X-JAVA
1// Rectangle.java
2public class Rectangle extends Shape {
3    protected int height;
4    protected int width;
5
6    // Overriding default constructor
7    public Rectangle() {
8        this.height = 1;
9        this.width = 1;
10    }
11
12    // Custom Constructor
13    public Rectangle(int height, int width) {
14        this.height = height;
15        this.width = width;
16    }
17
18    // Rest of the methods and Attributes
19}

Copy Constructor

Copy constructors are called when you want to create a new object from another object that is the same type. In a constructor, you would pass another previously created object with the same type. For example, if we want to create a new rectangle from an old rectangle, we write the copy constructor.

TEXT/X-JAVA
1// Rectangle.java
2public class Rectangle extends Shape {
3    protected int height;
4    protected int width;
5
6    // Overriding default constructor
7    public Rectangle() {
8        this.height = 1;
9        this.width = 1;
10    }
11
12    // Copy constructor
13    public Rectangle(Rectangle other) {
14        this.height = other.getHeight();
15        this.width = other.getWidth();
16    }
17
18    // Custom Constructor
19    public Rectangle(int height, int width) {
20        this.height = height;
21        this.width = width;
22    }
23
24
25    // Rest of the methods and Attributes
26}

Let's test your knowledge. Click the correct answer from the options.

Which of these constructors is NOT provided by default for the below class?

TEXT/X-JAVA
1public class A {
2    public int aInt;
3}

Click the option that best answers the question.

  • A()
  • A(int aInt)
  • A(A a)

Shallow Copy vs Deep Copy

If you know the basics of pointers, then you may have heard of the shallow copy and deep copy. All classes have a default copy constructor which is a shallow copy constructor.

Shallow Copy vs Deep Copy

Shallow copying is copying by reference (as opposed to copying by value). You will not notice any difference if your class has only primitive types (int, float, double, etc.). They will be copied by value in shallow copy as well. Suppose, however, you have an array as an attribute in the class. If you copy the object using the default copy constructor, then only the reference (or pointer) of the array will be copied. As a result, if you change a value at an index in the first object, then the value in the second object's array at that index will also be changed. See the code below for an example of a shallow copy.

TEXT/X-JAVA
1// Shallow Copy
2// MyClass.java
3public class MyClass {
4    protected int[] value;
5
6    // default constructor
7    public MyClass() {
8        value = {1,2,3,4,5}
9    }
10
11    // No Copy constructor, so the default will be used
12}
13
14// Main.java
15public class Main {
16    // Instantiating
17    MyClass myObj1 = new MyClass();
18    // Copying, will be using default constructor
19    MyClass myObj2 = new MyClass(myObj1);
20
21    myObj1.value[2] = 10;
22
23    // This will print: 1 2 10 4 5
24    for(int i=0; i<myObj1.value.length; i++) {
25        System.out.println(myObj1.value[i]);
26    }
27
28    // But this will also print: 1 2 10 4 5
29    for(int i=0; i<myObj2.value.length; i++) {
30        System.out.println(myObj2.value[i]);
31    }
32}

Now, to define a deep copy constructor, we need to create a separate array and then copy each element to the new array. See the code below:

TEXT/X-JAVA
1// Deep Copy
2// MyClass.java
3public class MyClass {
4    // For the sake of this example, we make it public to avoid excess getters
5    public int[] value;
6
7    // default constructor
8    public MyClass() {
9        value = {1,2,3,4,5};
10    }
11
12    // Copy constructor (Deep Copy)
13    public MyClass(MyClass other) {
14        this.value = new int[5];
15        for(int i = 0; i<5; i++) {
16            this.value[i] = other.value[i];
17        }
18    }
19}
20
21// Main.java
22public class Main {
23    // Instantiating
24    MyClass myObj1 = new MyClass();
25    // Copying, will be using default constructor
26    MyClass myObj2 = new MyClass(myObj1);
27
28    myObj1.value[2] = 10;
29
30    // This will print: 1 2 10 4 5
31    for(int i=0; i<myObj1.value.length; i++) {
32        System.out.println(myObj1.value[i]);
33    }
34
35    // This will now print unchanged: 1 2 3 4 5
36    for(int i=0; i<myObj2.value.length; i++) {
37        System.out.println(myObj2.value[i]);
38    }
39}

Try this exercise. Click the correct answer from the options.

Which of the following classes needs a copy constructor for deep-copy implementation?

TEXT/X-JAVA
1class A {
2    int x;
3}
4class B {
5    int x;
6    float y;
7}
8class C {
9    String x;
10}
11class D {
12    Integer x;
13}

Click the option that best answers the question.

  • A
  • B
  • C
  • D

Destructors

Destructors are the exact opposite of constructor methods. They are automatically called when an object goes out of scope or when you forcefully delete an object.

It is difficult for a programmer to forcefully delete an object using a language with a garbage collector. Since Java is a garbage-collected language, you cannot predict when (or even if) an object will be destroyed. Hence, there is no direct equivalent of a destructor in Java.

According to a popular question on stackoverflow.

There is an inherited method called finalize, but this is called entirely at the discretion of the garbage collector. So for classes that need to explicitly tidy up, the convention is to define a close method and use finalize only for sanity checking (i.e. if close has not been called do it now and log an error).

The reason for this is that all Java objects are heap-allocated and garbage collected. Without explicit deallocation (i.e. C++'s delete operator), there is no sensible way to implement real destructors. You will have a chance to peek at an example of an OOP language with a destructor in C++. See the code below for the previously implemented MyClass class:

TEXT/X-C++SRC
1class MyClass {
2public:
3    int* array;
4
5    // default constructor
6    MyClass() {
7        this->value = new int[5];
8        for(int i = 1; i <= 5; i++) {
9            this->value[i] = i;
10        }
11    }
12
13    // Destructor
14    ~MyClass() {
15        delete [] this->array;
16    }
17}

Getter and Setter Functions

We have already covered this a little in our previous lesson. In this lesson, we will be explaining the usage of getter and setter functions in more detail.

Getter and Setter Functions

Suppose that you are using the Shape class and all its subclasses to create a very large drawing project with many other collaborators. Many developers will be importing your drawables package (suppose this contains all your classes) and instantiating many objects. Among them, one person mistakenly puts this line of code somewhere in their large file.

TEXT/X-JAVA
1Rectangle rect = new Rectangle(5,6);
2// Do some stuff
3rect.height = -1
4// Try to do something like draw the rectangle on the screen

Later in his own implementations, he finds very weird bugs that should not be there. He is not even opening the file where he put the above line. Imagine the horrific nights he and others might have just because your class does not have any way to validate its attributes.

Somehow, you should check if the assigned value to the property is valid or not. The best way is to validate a value is to raise an Exception when a user attempts to set an invalid value. However, we will be covering exceptions in our next lesson. For now, we will just print to the console that the user is attempting to set an invalid value. Unfortunately, we cannot validate the value if it is directly assigned to the attribute which is why we need setter methods. When you use a setter, you can make sure that you are getting the correct value for the correct attribute.

There are other reasons you should use setter methods. For example, you should use setter methods if you always want to convert the given value to something else before assigning it to a variable or if you want to do some database/internet operations while setting attributes.

TEXT/X-JAVA
1// Rectangle.java
2public class Rectangle extends Shape {
3    // Make these attributes protected/private so others will not use it outside
4    protected int height;
5    protected int width;
6
7    // getters and setters will always be public
8    // getters 
9    public int getWidth() { return width; }
10    public int getHeight() { return height; }
11
12    // setters
13    public void setWidth(int width) {
14        if(width <= 0) {
15            System.out.println("Invalid width for Rectangle");
16            return;
17        }
18        this.width = width;
19    }
20    public void setHeight(int height) {
21        if(height <= 0) {
22            System.out.println("Invalid height for Rectangle");
23            return;
24        }
25        this.height = height;
26    }
27    // Other methods and attributes
28}

Try this exercise. Fill in the missing part by typing it in.

What should the proper java-beans convention-wise getter function name be for the property algodailyIsTheBest?

Write the missing line below.

Class Methods/Static Methods

Suppose you need a value that will always be the same (but not fixed) for all objects of that class. If you had to do it with the knowledge you gathered on OOP till now, you might try to keep track of all the objects in an array and keep a property constant for all the objects when one object changes the value.

However, this approach is very tedious and not efficient at all. Most of the OOP-supported languages will give you the convenience of a static keyword. A static property or method is "static" to all the objects. Only one unit of memory will be allocated for a class so that it can be accessed from the class and all of its instances. All the objects of that class will be sharing this single value.

There are a lot of usages for static attributes and methods of a class. For example, we can keep a count of the number of instances created from that class by maintaining a static count attribute. This cannot be done with a normal attribute since it would be reset when a new object is created, plus all instances would have their own value for each attribute (which is not desired).

TEXT/X-JAVA
1// ObjectCounter.java
2public class ObjectCounter {
3    private static int count = 0;
4
5    // Note: we cannot access static attributes with this keyword
6    public ObjectCounter() { count++; }
7
8    // all methods using static attribute or method will need to be static
9    public static int getCount() {
10        return count;
11    }
12
13    public void finalize() { count--; }
14}
15
16// Main.java
17public class Main {
18    public static void main(String[] args) {
19        // Static attributes/methods can be accessed from the class
20        System.out.println(ObjectCounter.getCount()); // 0
21
22        ObjectCounter obj = new ObjectCounter();
23        ObjectCounter obj2 = new ObjectCounter();
24        ObjectCounter obj3 = new ObjectCounter();
25
26        System.out.println(ObjectCounter.getCount()); // 3
27
28        // This is the way to cleanly delete an object before scope in java
29        obj.finalize(); obj = null;
30        obj2.finalize(); obj2 = null;
31
32        System.out.println(ObjectCounter.getCount()); // 2
33    }
34}

Are you sure you're getting this? Click the correct answer from the options.

When should you consider a method of a class static?

Click the option that best answers the question.

  • When the method is using a static property of that class
  • When a method is calling a static method
  • When that method does not depend on any of its non-static properties
  • When a static property is set by that method

Equality Comparison function

Suppose you want to compare two types of the Shape class. Since Shapes are custom classes, the compiler does not know how to compare two objects with different properties or which property to take into account. First, we need to decide what we need to compare in order to determine the equality of two Shape objects.

Let's assert that we declare two shapes equal whenever their areas are equal. We can do this by overriding the default equals method.

TEXT/X-JAVA
1public class Shape {
2    protected int maxWidth;
3    protected int maxHeight;
4
5    public double getArea() {
6        return getMaxArea();
7    }
8
9    @Override
10    public boolean equals(Object obj) {
11        if (obj == null)
12            return false;
13        if (obj == this)
14            return true;
15        System.out.println("Comparing");
16        return this.getArea() == ((Shape) obj).getArea();
17    }
18    // Other attributes and Methods
19}

Stringify Function

If you want to print your custom class to the console or make it a string to send to another object for some purpose, you'll most likely use a built-in class function named toString() in Java. Currently, the default toString() method returns the name of the class along with its package name as well as the hash code in hex format of the class calculated with another built-in method hashCode(). So if you were to print an object of type Rectangle, you would get something like this "shapes.Rectangle@abcdefgh" as output.

If you want to print something different, then - like other methods - you can override the toString() method and return whatever you want to print. For our example, we will print the name of the Rectangle with its package name and its area inside parentheses. All the built-in classes (Integer, Double, BigInteger, etc.) have overridden the toString() method to return a more informative string (the value instead of Class name and hash code).

TEXT/X-JAVA
1package shapes;
2
3// Rectangle.java
4public class Rectangle extends Shape {
5    protected int height;
6    protected int width;
7
8    @Override
9    public double getArea() {
10        return (double)height*width;
11    }
12
13    @Override
14    public String toString() {
15        return "shapes.Rectangle(area=" + this.getArea() + ")";
16    }
17}
18
19// Main.java
20public class Main {
21    public static void main(String[] args) {
22        Rectangle rect = new Rectangle();
23        rect.setHeight(5); rect.setWidth(4);
24        System.out.println(rect); // shapes.Rectangle(20)
25    }
26}

Conclusion

All of the above functions are very important to understand in order to get a good grasp of OOP. In the next lesson, instead of looking at implementations, we will go through some advanced concepts of OOP. After the next lesson, you will understand the full potential of OOP.

One Pager Cheat Sheet

  • Object-Oriented Programming (OOP) provides special functions to help save time and effort when coding, such as getters and setters for classes, as demonstrated in the example of Shape and its subclasses.
  • Constructors are special-purpose methods used to set initial values and perform initialization tasks during object instantiation.
  • A default constructor is a method (instantiated with new ClassName()) that initiates an object, which can be overridden by the user if desired.
  • The Copy Constructor is used to create a new object from an existing object of the same type by passing the existing object as an argument.
  • A user-defined constructor takes one int argument and assigns it to the data member aInt in the class A.
  • **Shallow copy** copies by reference and copies the **pointer** of an array, while **deep copy** copies the data by making a *new array* for it.
  • A custom copy constructor for deep copy needs to be implemented for classes A, B and D in order to copy the primitive wrapper type and non-primitive type objects by value instead of by reference.
  • Java objects are heap-allocated and automatically garbage collected, so there is no direct equivalent of a destructor as found in languages such as C++, where explicit deallocation is used via the delete operator.
  • Usinggetter and setterfunctions ensures that values assigned to object attributes are valid and can help prevent errors from occurring when working in collaboration with other developers.
  • The correct getter function name for the algodailyIsTheBest property according to the JavaBeans convention is getAlgodailyIsTheBest.
  • Static attributes and methods provide a convenient way to share values among all instances of a class, and can be used to keep track of the number of objects created from that class.
  • A static method can be accessed from the class directly and can be useful for situations where all the objects of the class need to share state, such as keeping a count of all objects created of the same class.
  • We can compare different Shape objects by overriding the default equals method to determine equality based on their areas.
  • Overriding the toString() method allows you to return a more informative string than the default output of the class name and hash code.
  • After completing the next lesson, you will have a full understanding of the Object-Oriented Programming capabilities.