Part 5

Learning object-oriented programming

  • Object-oriented programming is primarily about isolating concepts into their own entities or, in other words, creating abstractions.

  • Separating a concept into its own class has advantages. Firstly, certain details (such as the rotation of a hand) can be hidden inside the class (i.e., abstracted). Instead of typing an if-statement and an assignment operation, it’s simple and enough to just call a clearly-named method advance(). Secondly, a class created from a distinct concept can serve multiple purposes. The resulting clock hand may be used as a building block for other programs as well - the class could be named CounterLimitedFromTop, for example. Lastly, since the details of the implementation of the clock hand are not visible to its user, they can be changed if desired.

  • Clock with OOP:

public class ClockHand {
    private int value;
    private int limit;

    public ClockHand(int limit) {
        this.limit = limit;
        this.value = 0;
    }

    public void advance() {
        this.value = this.value + 1;

        if (this.value >= this.limit) {
            this.value = 0;
        }
    }

    public int value() {
        return this.value;
    }

    public String toString() {
        if (this.value < 10) {
            return "0" + this.value;
        }

        return "" + this.value;
    }
}

public class Clock {
    private ClockHand hours;
    private ClockHand minutes;
    private ClockHand seconds;

    public Clock() {
        this.hours = new ClockHand(24);
        this.minutes = new ClockHand(60);
        this.seconds = new ClockHand(60);
    }

    public void advance() {
        this.seconds.advance();

        if (this.seconds.value() == 0) {
            this.minutes.advance();

            if (this.minutes.value() == 0) {
                this.hours.advance();
            }
        }
    }

    public String toString() {
        return hours + ":" + minutes + ":" + seconds;
    }
}

Clock clock = new Clock();

while (true) {
    System.out.println(clock);
    clock.advance();
}
  • Review OneMinute exercise. Note

  • Object refers to an independent entity that contains both data (instance variables) and behavior (methods). Objects can come in different forms, like some describe problem-domain concepts and some help coordinate the interaction between objects. Objects interact through method calls that both request info from objects and give instructions to them.

  • Generally, each object has clearly defined boundaries and behaviors and is only aware of the objects that it needs to perform its task. In other words, the object hides its internal operations, providing access to its functionality through clearly defined methods. Moreover, the object is independent of any other object that it doesn’t require to accomplish its task.

  • The state of an object is the value of its internal variables at any given point in time.

  • A class is a blueprint that defines the types of objects that can be created from it. It contains instance variables describing the object’s data, a constructor or constructors used to create it, and methods that define its behavior.

Removing repetitive code (overloading methods and constructors)

  • Class can have multiple constructors (i.e. alternative ways to create objects). Having 2 or more constructors in a class is known as constructor overloading. A class can have multiple constructors that differ in the number and/or type of their parameters. But It’s not possible to have two constructors with the exact same parameters.
public Person(String name) {
        this.name = name;
        this.age = 0;
        this.weight = 0;
        this.height = 0;
    }

public Person(String name, int age) {
    this.name = name;
    this.age = age;
    this.weight = 0;
    this.height = 0;
}
public static void main(String[] args) {
    Person paul = new Person("Paul", 24);
    Person ada = new Person("Ada");
}
  • Calling constructor within constructor: The first constructor - the one that receives a name as a parameter - is in fact a special case of the second constructor - the one that’s given both name and age. What if the first constructor could call the second constructor?

  • A constructor can be called from another constructor using the this keyword, which refers to this object. this() is used for calling the default constructor from parameterised constructor.

public Person(String name) {
    this(name, 0);
    //here the code of the second constructor is run, and the age is set to 0
}

public Person(String name, int age) {
    this.name = name;
    this.age = age;
    this.weight = 0;
    this.height = 0;
}
  • The constructor call this(name, 0); might seem a bit weird. A way to think about it is to imagine that the call is automatically replaced with “copy-paste” of the second constructor in such a way that the age parameter is set to 0. NB! If a constructor calls another constructor, the constructor call must be the first command in the constructor.
public Product(String name, String location, int weight) {
    this.name = name;
    this.location = location;
    this.weight = weight;
}

public Product(String name){
    this(name,"shelf",1);
}
public Product(String name, String location){
    this(name,location,1);
}
public Product(String name, int weight){
    this(name,"shelf",weight);
}
  • The first constructor does not do anything by itself, but instead calls the second constructor and asks it to set the age to 0. this(name, 0) means that call is automatically replaced with “copy-paste” of the second constructor in such a way that the age parameter is set to 0. Also, if a constructor calls another constructor, the constructor call, this(), must be the first command in the constructor. But order of constructors don’t matter like second constructor can be before first and vice versa.

  • Method overloading in same way as constructor overloading: Once again, the parameters of the different versions must be different.

public void growOlder() {
    this.growOlder(1);
    //same as below code, u can do either
    //this.age = this.age + 1;
}

public void growOlder(int years) {
    this.age = this.age + years;
}
  • A Person now has two methods, both called growOlder. The one that gets executed depends on the number of parameters provided.

Primitive and reference variables

  • Primitive variable’s information is stored as the value of that variable, whereas a reference variable holds a reference to information related to that variable. reference variables are practically always objects in Java.

  • Primitive variable example:

int value = 10;
System.out.println(value);
  • Reference variable example:
public class Name {
    private String name;

    public Name(String name) {
        this.name = name;
    }
}

Name luke = new Name("Luke");
System.out.println(luke);
//prints Name@4aa298b7
  • In above example, we create a primitive int variable, and the number 10 is stored as its value. We create a reference variable called luke. A reference to an object is returned by the constructor of the Name class when we call it, and this reference is stored as the value of the variable.

  • The value of a primitive variable is concrete, whereas the value of a reference variable is a reference. When we attempt to print the value of a reference variable, the output contains the type of the variable and an identifier created for it by Java: the string Name@4aa298b7 tells us that the given variable is of type Name and its identifier is 4aa298b7.

  • Java has eight different primitive variables. These are: boolean (a truth value: either true or false), byte (a byte containing 8 bits, between the values -128 and 127), char (a 16-bit value representing a single character), short (a 16-bit value that represents a small integer, between the values -32768 and 32767), int (a 32-bit value that represents a medium-sized integer, between the values -231 and 231-1), long (a 64-bit value that represents a large integer, between values -263 and 263-1), float (a floating-point number that uses 32 bits), and double (a floating-point number that uses 64 bits).

  • Declaring a primitive variable causes the computer to reserve some memory where the value assigned to the variable can be stored. The size of the storage container reserved depends on type of the primitive.

  • The name of the variable tells the memory location where its value is stored. When you assign a value to a primitive variable with an equality sign, the value on the right side is copied to the memory location indicated by the name of the variable. For example, int first = 10 reserves a location called first for the variable, and then copies the value 10 into it. Similarly, int second = first; reserves in memory a location called second for the variable being created and then copies into it the value stored in the location of variable first.

  • The values of variables are also copied whenever they’re used in method calls. What this means is that the value of a variable that’s passed as a parameter during a method call is not mutated in the calling method by the method called. Remember the stack simulation?

  • Reference variable: Except the above 8 primitive variables, all other variables are reference type. We can also create our own variable types by defining new classes. Any object instanced from a class is a reference variable. E.g.

Name leevi = new Name("Leevi");
  • Whenever a new variable is declared, its type must be stated first. We declare a variable of type Name below. For the program to execute successfully, there must be a Name class available for the program to use.

  • We state the name of the variable as its declared. You can reference the value of the variable later on by its name. Here, the variable name is leevi.

  • new Name(“Leevi”) => Values can be assigned to variables. Objects are created from classes by calling the class constructor. This constructor defines the values assigned to the instance variables of the object being created. We’re assuming in the example below that the class Name has a constructor that takes a string as parameter.

  • The constructor call returns a value that is a reference to the newly-created object. The equality sign (=) tells the program that the value of the right-hand side expression is to be copied as the value of the variable on the left-hand side. The reference to the newly-created object, returned by the constructor call, is copied as the value of the variable leevi. A variable of type Name is declared, and the value returned by the Name class constructor is copied as its value.

  • Difference between primitive and reference variables is that primitive variables are immutable while internal state of reference variables can typically be mutated. This is because value of a primitive variable is stored directly in the variable, whereas the value of a reference variable is a reference to the variable’s data, i.e., its internal state.

  • Arithmetic operations can be used with primitive variables but these operations do not change the original values of the variables. Arithmetic operations create new values that can be stored in variables as needed. On the contrary, the values of reference variables cannot be changed by these arithmetic expressions.

  • The value of a reference variable — i.e., the reference — points to a location that contains information relating to the given variable. Let’s assume that we have a Person class, containing an instance variable ‘age’. If we’ve instantiated a person object from the class, we can access age variable by following the object’s reference. The value of this age variable can then be changed.

  • Assigning a value with the equality sign copies the value (possibly of some variable) on the right-hand side and stores it as the value of the left-hand side variable.

  • A similar kind of copying occurs during a method call. Regardless of whether the variable is primitive or reference type, the value passed to the method as an argument is copied for the called method to use. With primitive variables, the value of the variable is conveyed to the method. With reference variables, it’s a reference.

public class Person {
    private int year;

    public Person() {
        this.year = 1996;
    }

    public int getYear() {
        return this.year;
    }

    public void setYear(int year) {
        this.year = year;
    }   
}

-------------------------------

public class TimeMachine {
    private Person traveler;
    
    public TimeMachine(Person person) {
        this.traveler = person;
        // per
    }
    
    public void travelInTime(int years) {
        this.traveler.setYear(this.traveler.getYear() + years);
    }
    

}

------------------------

public static void main(String[] args) {
    Person lorraine = new Person();
    TimeMachine tardis = new TimeMachine(lorraine);
    tardis.travelInTime(6);
    System.out.println(lorraine.getYear());
    //2002
}
  • Revisit Variables and Computer Memory in this last part. I don’t get it now lol

Objects and references (Revisit! V. useful. Use Mooc.fi’s notes)

  • Calling a constructor with the command new causes several things to happen. First, space is reserved in the computer memory for storing object variables. Then default or initial values are set to object variables (e.g. an int type variable receives an initial value of 0). Lastly, the source code in the constructor is executed.

  • A constructor call returns a reference to an object. A reference is information about the location of object data.

  • Object as a method parameter: Since objects are reference variables, any type of object can be defined to be a method parameter.

public class AmusementParkRide {
    private String name;
    private int lowestHeight;

    public AmusementParkRide(String name, int lowestHeight) {
        this.name = name;
        this.lowestHeight = lowestHeight;
    }

    public boolean allowedToRide(Person person) {
        if (person.getHeight() < this.lowestHeight) {
            return false;
        }

        return true;
    }

    public String toString() {
        return this.name + ", minimum height: " + this.lowestHeight;
    }
}
  • So the method allowedToRide of an AmusementParkRide object is given a Person object as a parameter. Like earlier, the value of the variable — in this case, a reference — is copied for the method to use. The method handles a copied reference, and it calls the getHeight method of the person passed as a parameter.

  • Review HealthStation and CardPayments exercise

  • Every already-made Java class or any class we create inherits class Object.

  • Object as object variable: (object containing references to other object) the thing I am struggling with

  • return String.format(“%s, has a friend called %s (%s)”, this.name, this.pet.getName(), this.pet.getBreed());

  • Abstraction is to conceptualize the programming code so that each concept has its own clear responsibilities.

public class SimpleDate {
    private int day;
    private int month;
    private int year;

    public SimpleDate(int day, int month, int year) {
        this.day = day;
        this.month = month;
        this.year = year;
    }

    public String toString() {
        return this.day + "." + this.month + "." + this.year;
    }

    // used to check if this date object (`this`) is before
    // the date object given as the parameter (`compared`)
    public boolean before(SimpleDate compared) {
        // first compare years
        if (this.year < compared.year) {
            return true;
        }

        if (this.year > compared.year) {
            return false;
        }
    }
  • Even though the object variables year, month, and day are encapsulated (private) object variables, we can read their values by writing compared.variableName. This is because private variable can be accessed from all the methods contained by that class. Notice that the syntax here matches calling some object method. Unlike when calling a method, we refer to a field of an object, so the parentheses that indicate a method call are not written.
public class Person {
    // ...

    public boolean olderThan(Person compared) {
        if (this.birthday.before(compared.getBirthday())) {
            return true;
        }

        return false;

        // or return more directly:
        // return this.birthday.before(compared.getBirthday());
    }
}
  • Why we use .equals() to compare strings and cannot use equal sign: With primitive variables such as int, comparing two variables can be done with two equality signs. This is because the value of a primitive variable is stored directly in the “variable’s box”. The value of reference variables, in contrast, is an address of the object that is referenced; so the “box” contains a reference to the memory location. Using two equality signs compares the equality of the values stored in the “boxes of the variables” — with reference variables, such comparisons would examine the equality of the memory references.

  • .equals() doesnt work really well for strings so need to implement design ourselves if want to compare 2 objects

public class SimpleDate {
    private int day;
    private int month;
    private int year;

    public SimpleDate(int day, int month, int year) {
        this.day = day;
        this.month = month;
        this.year = year;
    }

    public int getDay() {
        return this.day;
    }

    public int getMonth() {
        return this.month;
    }

    public int getYear() {
        return this.year;
    }

    public boolean equals(Object compared) {
        // if the variables are located in the same position, they are equal
        if (this == compared) {
            return true;
        }

        // if the type of the compared object is not SimpleDate, the objects are not equal
        if (!(compared instanceof SimpleDate)) {
            return false;
        }

        // convert the Object type compared object
        // into a SimpleDate type object called comparedSimpleDate
        SimpleDate comparedSimpleDate = (SimpleDate) compared;

        // if the values of the object variables are the same, the objects are equal
        if (this.day == comparedSimpleDate.day &&
            this.month == comparedSimpleDate.month &&
            this.year == comparedSimpleDate.year) {
            return true;
        }
        // or return this.day == comparedSimpleDate.day && this.month == comparedSimpleDate.month && this.year == comparedSimpleDate.year

        // otherwise the objects are not equal
        return false;
    }

    @Override
    public String toString() {
        return this.day + "." + this.month + "." + this.year;
    }
}
  • The contains() method of a list uses the equals() method that is defined for the objects in its search for objects.

  • The equals method is implemented in such a way that it can be used to compare the current object with any other object. The method receives an Object-type object as its single parameter — all objects are Object-type, in addition to their own type. The equals method first compares if the addresses are equal. After this, we examine if the types of the objects are the same. Next, the Object-type object passed as the parameter is converted to the type of the object that is being examined by using a type cast, so that the values of the object variables can be compared.

  • Object as a method’s return value: For example below, factory object could also be used to create and return new Car objects

public class Factory {
    private String make;

    public Factory(String make) {
        this.make = make;
    }

    public Car procuceCar() {
        return new Car(this.make);
    }
}
  • final keyword means values of these object variables cannot be modified after they have been set in the constructor. The objects of Money class are unchangeable so immutable — if we want to e.g. increase the amount of money, we must create a new object to represent that new amount of money.