Arvutiteaduse instituut
  1. Kursused
  2. 2025/26 sügis
  3. Objektorienteeritud programmeerimine (MTAT.03.130)
EN
Logi sisse

Objektorienteeritud programmeerimine 2025/26 sügis

  • Introduction
  • Week 3: Classes, Objects
  • Week 4: Strings

Classes and Objects

Table of Contents

  • Classes and Objects
  • Constructors
  • Arrays
  • Methods
  • Method toString
  • Get and Set methods
  • Access Modifiers
  • Data Types - Primitive and Non-Primitive data type
  • Test

Classes and Objects

In object-oriented programming, classes and objects are the two main concepts. Classes are user-defined data types that act as the blueprint for individual objects, attributes and methods. They represent a set of properties or methods common to all objects of one type. For example, if we have a lot of different fruits, we could define a class called "Fruit" and all individual fruits would belong to this class.

What are objects? An object is an instance of a class and has an identity, state and behaviour. Identity and state are represented through fields, also known as attributes or variables. They represent the characteristics or properties associated with an object. All fields can hold different data types, such as integers or strings. The behaviour of an object is defined by methods, which determine what actions the object can perform.

For example, if we create a class called Fruit, objects of this class could be apples, bananas, pears and lemons. Additionally, let’s add some characteristics, also known as fields, to the objects of this class.

To create a class in Java, we use the “class” keyword:

//class that describes fruit and what we can do with them
public class Fruit {
   String name; //attribute that holds fruit name
   int taste; //attribute that holds fruit's taste rating on a 10-point scale
}

In this example, the Fruit class has two attributes: name and taste.

Now let’s make some objects of this class:

In Java, we can create objects in the main method of the class using the “new” keyword. (Tip: to quickly create the main method of the class we can type “psvm”)

//class that describes fruit
public class Fruit {
   String name; //attribute that holds fruit name
   int taste; //attribute that holds fruit's taste rating on a 10-point scale

   public static void main(String[] args) {
       Fruit apple =  new Fruit(); // Fruit object "apple"
       Fruit orange =  new Fruit(); // Fruit object "orange"
   }
}

Here we created two objects of the class Fruit: apple and orange.

For better organisation, especially as code grows, it is recommended to define the main method in a separate class. The file name should match the class name.


//First class
public class Fruit {
   String name; //attribute that holds fruit name
   int taste; //attribute that holds fruit's taste rating on a 10-point scale
}


//Create a test class
public class TestFruit {
   public static void main(String[] args) {
      Fruit apple =  new Fruit(); // Fruit object "apple"
      Fruit orange =  new Fruit(); // Fruit object "orange"
   }
}

In this example class TestFruit contains the main method, while class Fruit describes object attributes.

At this point we have created variables that hold reference to the objects, however those objects do not have values such as name and taste. Let’s print out the name of the object “apple”:

//test class for class Fruit
public class TestFruit {
   public static void main(String[] args) {
       Fruit apple =  new Fruit();
       Fruit orange =  new Fruit();
       System.out.println(apple.name);
   }
}

If we run the program, we can see the name is null.

We can define attribute values in multiple ways. Firstly, we can define the name and taste as a default value. This would result in that we have the same values for all objects:

//class that describes fruit
public class Fruit {
   String name = "apple"; //default name of the fruit
   int taste = 9; //default taste rating on a 10-point scale
}

//test class for class Fruit
public class TestFruit {
   public static void main(String[] args) {
       Fruit apple =  new Fruit();
       Fruit orange =  new Fruit();
       System.out.println(apple.name);
       System.out.println(orange.name);
   }
}

If we run this code we see that Java prints out apple and apple. So this is not a good practice if we want to make objects with unique names and tastes.

We can also set the name and taste attributes in the test class like this:

//test class for class Fruit
public class TestFruit {
    public static void main(String[] args) {
        Fruit apple =  new Fruit();
        Fruit orange =  new Fruit();

        apple.name = "Apple"; 
        orange.name = "Orange";
        apple.taste = 9;
        orange.taste = 8;

    }
}

However, this is not considered good practice, as it doesn't align with object-oriented programming principles. Assigning values this way can lead to incomplete objects that lack essential information. To create objects with unique values, it is better to use constructors.

Constructors

A constructor in Java is a special method that is used to initialise objects. This means that when an object of a class is created, the constructor is automatically invoked. Constructor name must match the class name and it cannot have a return type.

There are multiple types of constructors. First type is a constructor that does not have any parameters:

//constructor without parameters
public Fruit () {
   name = "fruit";
   taste = 0;
}

//test class for class Fruit
public class TestFruit {
   public static void main(String[] args) {
       Fruit apple =  new Fruit(); // Fruit object assigned to variable apple
       Fruit orange =  new Fruit(); // Fruit object assigned to variable orange
       System.out.println(apple.name);
       System.out.println(orange.name);
   }
}

This code prints out fruit two times because we wrote that all of the class Fruit object’s name and taste are the same.

We can also make a constructor that has parameters:

//constructor with parameters
public Fruit (String n1, int t1) {
   name = n1;
   taste = t1;
}

Now if we want to make Fruit objects we need to say what the name and taste are:

//test class for class Fruit
public class TestFruit {
   public static void main(String[] args) {
       Fruit apple =  new Fruit("apple", 3); // Fruit object "apple"
       Fruit orange =  new Fruit("orange", 10); // Fruit object "orange"
       System.out.println(apple.name);
       System.out.println(orange.name);
   }
}

When we run this code, we see in the console that both objects' names are different. This method allows us to add unique data to each object.

Sometimes we may find ourselves in a situation where we need to create multiple objects that have different data. For example if we know everything about one object (name and taste) but know nothing about the other. This is where we need two different constructors, one with parameters and one without any parameters. Having multiple constructors in the same class is called constructor overloading. Those constructors need to have different parameters listed.

//class that describes fruit
public class Fruit {
   String name; //attribute that holds fruit name
   int taste; //attribute that holds fruit's taste rating on a 10-point scale

   //First constructor without parameters
   public Fruit() {
       name = "fruit";
       taste = 0;
   }

   //Second constructor with parameters
   public Fruit (String n1, int t1) {
       name = n1;
       taste = t1;
   }
}

Now let’s run the following code:

//test class for class Fruit
public class TestFruit {
   public static void main(String[] args) {
       Fruit apple =  new Fruit("apple", 3); // Fruit object "apple"
       Fruit orange =  new Fruit(); // Fruit object "orange"
       System.out.println(apple.name);
       System.out.println(orange.name);
   }
}

We can see that the choice of constructor is determined by the syntax used during object creation. The object named orange uses the no parameter constructor, whereas the object named apple uses the constructor with parameters.

What happens if we want the constructor parameter names to be the same as the field names in a class? There is a simple solution for this - we can use the "this" keyword in constructors. This keyword allows us to reference the object of the current class. It allows us to differentiate instance variables from method's parameters when they have same names within a constructor or method.

//class that describes fruit
public class Fruit {
   String name; //attribute that holds fruit name
   int taste; //attribute that holds fruit's taste rating on a 10-point scale

   //constructor with parameters
   public Fruit(String name, int taste) {
       this.name = name;
       this.taste = taste;
   }

Tip: to easily make constructors we can double click and select generate → constructor and select the parameters we want the constructor to have, to select multiple parameters we can hold down shift key and select the needed parameters.

The “this” keyword can also be used to call one constructor from another within the same class:

//class that describes fruit and what we can do with them
public class Fruit {
   String name; //attribute that holds fruit name
   int taste; //attribute that holds fruit's taste rating on a 10-point scale

   public Fruit(String name, int taste) {
       this.name = name;
       this.taste = taste;
   }

   public Fruit() {
       this("fruit", 0); // use "this" keyword to call out the other constructor
   }
}

If you don’t create any constructors, a default constructor is provided by Java, otherwise this default constructor is not invoked.

Arrays

As we can make String, int and other types of arrays we can also make an array of a user-defined class objects. Arrays are used to hold multiple instances of a particular class.

Before we created a class called Fruit, let’s make an array of this class’ objects.

//test class for class fruit
public class TestFruit {
   public static void main(String[] args) {
       Fruit apple =  new Fruit("apple", 3); // Fruit object "apple"
       Fruit orange =  new Fruit("orange", 10); // Fruit object "orange"

       //new array fruits that stores class Fruit objects, the array can hold up to 100 objects
       Fruit[] fruits = new Fruit[100];

   }
}

In this example we created an array called “fruits”, that stores a maximum of 100 class Fruit objects. At the moment this array consists of null values.

To prove this we can run the following code:


System.out.println(fruits[10]); //printing out the object which index is 10

Let’s add some objects to this array. We have already initialized two objects: apple and orange and can add them to the array:

//test class for class Fruit
public class TestFruit {
   public static void main(String[] args) {
       Fruit apple =  new Fruit("apple", 3); // Fruit object "apple"
       Fruit orange =  new Fruit("orange", 10); // Fruit object "orange"


       //new array fruits that stores class Fruit objects, the array can hold up to 100 objects
       Fruit[] fruits = new Fruit[100];


       fruits[0] = apple; //adding apple to the first position in the array
       fruits[1] = orange; //adding orange to the second position in the array
   }
}

To easily iterate through arrays we can use loops. Let’s say we want to print out all the array values, then we can use a for-loop.

Here's an example of a for-loop:


//for-loop
for (int i = 0; i < fruits.length; i++) {
   System.out.println(fruits[i]);
}

Methods

Class methods define an object's behaviour and execute specific actions. Class methods are defined within a class and are responsible for carrying out specific operations. Class methods are also called instance methods and we can access them through objects. Instance methods can access and modify class attributes. There are also methods we can directly access without an object, these methods are called static methods. To define a static method we just need to add the "static" keyword to the method signature.

To create an instance method:

  1. we can define the type of access (public, private)
  2. add the return type of the method (int, double, String, void)
  3. add the name of the method
  4. add the parameter list if it’s necessary (parameter consists of data type and name)
  5. write the method body.

An example of a method:

Let's add a new field called pricePerKilo to the Fruit class and change the constructors accordingly. Next, we can create a method called priceOfFruit that takes a parameter, kilograms, which represents how much of the fruit the user wants to buy. This method calculates the price of the fruit based on the weight and returns the double-type value.

//class that describes fruit and what we can do with them
public class Fruit {
    String name; //attribute that holds fruit name
    int taste; //attribute that holds fruit's taste rating on a 10-point scale
    double pricePerKilo;

    public Fruit(String name, int taste, double price) {
        this.name = name;
        this.taste = taste;
        this.pricePerKilo = price;
    }

    public Fruit() {
        this("fruit", 0, 2); // use "this" keyword to call out the other constructor
    }

    //method that returns the price of the fruit
    public double priceOfFruit(double kilograms) {
        //Method body: calculate the price based on the weight
        return pricePerKilo * kilograms;
    }
}

As mentioned earlier, to access instance methods, we need to create an object of the class and invoke the method using that object:

//test class for class Fruit
public class TestFruit {
    public static void main(String[] args) {
        Fruit apple =  new Fruit("apple", 9, 4); // Fruit object "apple"
        Fruit orange =  new Fruit("orange", 7, 3); // Fruit object "orange"
        System.out.println(apple);
        System.out.println(orange);

        apple.priceOfFruit(3.2); // call out the instance method using object called apple
    }
}

Method toString

Now let's try to print out the class Fruit object apple. What's printed out in the console? Can we get any useful information about the object?

As we can see printing out the object does not give us any information about the name or taste of the fruit and something like this appears in the console: Fruit@a09ee92. This happens because, if we don't define a class toString() method, the default method from the Object class is used. This method returns the class name followed by "@" sign and the unsigned hexademical representation of the hash code of the object, for example this: Fruit@a09ee92.

When we want to get the string representation of an object, we need to write a new toString() class method. It declares how the user-defined object is shown in the console. Whenever we want to print the object reference then the toString() method is invoked.

We can do it like this:


@Override
    public String toString() {
        return "Fruit{" +
                "name='" + name + '\'' +
                ", taste=" + taste +
                ", pricePerKilo=" + pricePerKilo +
                '}';
    }

Tip: to easily make toString() methods we can double click and select generate → toString().

Get and Set methods

In object-oriented programming encapsulation is one of the most important concepts. The idea of this is that all the “sensitive” data is hidden from other classes. To achieve encapsulation in Java we need to declare the class variables as private. To access these variables we need specific methods and this is where public get and set methods come to help.

The main task of the get method is to return the variable value. With the set method we can change and set the value of a variable.

//class that describes fruit and what we can do with them
public class Fruit {
    private String name; //attribute that holds fruit name
    private int taste; //attribute that holds fruit's taste rating on a 10-point scale
    private double pricePerKilo;

    public Fruit(String name, int taste, double price) {
        this.name = name;
        this.taste = taste;
        this.pricePerKilo = price;
    }

    public Fruit() {
        this("fruit", 0, 2); // use "this" keyword to call out the other constructor
    }

    //Get and set methods for name and taste fields
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getTaste() {
        return taste;
    }

    public void setTaste(int taste) {
        this.taste = taste;
    }
}

To use these methods we can write the following code in the test class:

//test class for class Fruit
public class TestFruit {
   public static void main(String[] args) {
       Fruit apple =  new Fruit("apple", 3, 4); // Fruit object "apple"
       Fruit orange =  new Fruit(); // Fruit object "orange"
       System.out.println(apple.getName());
       orange.setName("or");
       System.out.println(orange.getName());
   }
}

Because our name field is declared as private, we couldn’t have accessed the name with command “apple.name” and had to use the get method. We can also add validation checks in setter methods to control the data before assigning it.

Tip: to easily make get and set methods we can double click and select generate.

Access Modifiers

We can control how we can access our methods and fields with different access modifiers like public, private, protected and default. Default access modifier is used when no explicit access modifier is specified for a method or field. All methods and fields that are declared with default access modifier are available only within the same package (package is a namespace that organizes a set of related classes and interface, they are similar to different folders in our computers). On the other hand using a public access modifier, we can access methods and fields in any other class, even outside the package.

Example of a public method:

//method that has a public access modifier
public double priceOfFruit(double kilograms) {
     //Method body
     return pricePerKilo * kilograms;
}

If we use a private access modifier, the methods and fields can only be accessed within the declared class and it is the most restricted access level. To make the previously shown method private we just need to change public access modifier to private. We should use private access modifiers when we want to hide methods from other classes

Protected access modifiers are used when we want to use the methods or fields in the same class, its subclasses and other classes in the same package.

Example of a protected field:


protected String name; //attribute that holds fruit name

Here's a table illustrating the access modifiers:

Data Types – Primitive and Non-Primitive data type

In Java, data types are divided into two categories: primitive and non-primitive.

Primitive data types represent simple values. There are eight primitive types: boolean, char, int, short, byte, long, float and double. Primitive data types always hold a value and this value can be a specific type (this means the value can't be null). Primitive data types are best to use for faster access and are more memory efficient.

Non-primitive data types store references rather than values and are also called reference types because they refer to objects. Common non-primitive data types are for example String, Arrays (int[], String[]) and user-defined Classes. It’s best to use non-primitive data types when storing complex data or working with objects. Non-primitive types can be null and store references to the object in memory.

For example in the following code we have used both data types:


//non-primitive data types
Fruit apple =  new Fruit("apple", 3, 4); // Fruit object "apple"
Fruit orange =  new Fruit("orange", 10, 2); // Fruit object "orange"
//primitive data types
int price = 200;
boolean isAvailable = true;

For example, if we know a person's age and want to write it on paper, we would use a primitive data type because the value is stored directly in memory. However, if we don’t know the age directly but know about a "folder" that contains this information, we would use a non-primitive data type, since we would store a reference to the "folder" rather than the actual value.

Here's a table illustrating the differences between data types:

Test

Please leave your feedback for reading materials and videos ->

Please leave your feedback for self-assessment tests ->

 Homework ->
  • Arvutiteaduse instituut
  • Loodus- ja täppisteaduste valdkond
  • Tartu Ülikool
Tehniliste probleemide või küsimuste korral kirjuta:

Kursuse sisu ja korralduslike küsimustega pöörduge kursuse korraldajate poole.
Õppematerjalide varalised autoriõigused kuuluvad Tartu Ülikoolile. Õppematerjalide kasutamine on lubatud autoriõiguse seaduses ettenähtud teose vaba kasutamise eesmärkidel ja tingimustel. Õppematerjalide kasutamisel on kasutaja kohustatud viitama õppematerjalide autorile.
Õppematerjalide kasutamine muudel eesmärkidel on lubatud ainult Tartu Ülikooli eelneval kirjalikul nõusolekul.
Courses’i keskkonna kasutustingimused