Kihagyás

Practice 05

The examples shown require the Cat and Human classes prepared in the previous lesson.

Next we will look at the possible relationships between the classes. We want Human and Cat to know about each other, for a humman in the program to be able to pet a cat in the program.

The code for the Human class is something like this:

public class Human {
    private String name;

    private int numberOfScratches;

    private String email;

    public static int GMAIL_ACCOUNT_NUMBER = 0;

    public Human(String name, String email) {
        this.name = name;
        this.email = email;
        this.numberOfScratches = 0;
    }

    public void incrementScratches() {
        this.numberOfScratches++;
    }

    public int getNumberOfScratches() {
        return numberOfScratches;
    }

    public String getName() {
        return name;
    }

}

Relationships between classes

They implement object interactions. Basic relations between classes: association, aggregation (weak, strong), inheritance. These relationships may have different properties (name, direction, role, multiplicity, etc.).

Association

A bidirectional link between classes, its direction (direction of the message) can be specified. The existence of the classes involved in the relationship is usually independent of each other, but at least one knows and/or uses the other. Practically, there is a relationship between objects created from classes.

Its mark: a line drawn between two classes, possibly with an arrow at one end. The relationship may also include a direction, which is indicated by a plain arrow.

For example, there may be an association between the Cat and Human classes, because the human can pet the cat.

Association between Cat and Human

Associations can occur in the source code in many ways, for example: as local variables, function parameters, returned values, etc. In this case, at least one class knows the other, but of course the association can be bidirectional, for example if there is a mutual relationship between two instances.

Java code

In our example, Human can pet the instances of Cat, through the isPet method of Cat.

    public void gettingPet(Human who) {
        numberOfPets++;
        if (!friendly) {
            who.incrementScratches();
            System.out.println(this.name + " scratched " + who.getName());

            if (numberOfPets > 30) {
                friendly = true;
            }
        }
    }

This method causes the current cat's numberOfPets property to be incremented, and if it is not friendly, it will scratch the person who pets it. Unfriendly cats will become friendly over time, after which they will not scratch.

Association class

The property of association can be denoted by a dashed line drawn to the association, as shown in the example below. In the example, the association between the Cat and Doctor class has an Examination property, since the association between a given cat and a given doctor has several properties.

Association

Aggregation

Aggregation is a part-whole relationship, a special, stronger form of association. An object may physically contain or possess another. Aggregation is denoted by a line drawn between two classes, with a rhombus on the container side, which can have two variants: empty rhombus or full rhombus.

Weak aggregation

A special association in which the container can exist without the contained, e.g. apple and worm, or mailbox and house, where house and mailbox can exist separately, but each house can have a mailbox.

Its mark: a line drawn between the two classes, with an empty rhombus on the container side

Example of weak aggregation

Cats may have a collar with the owner's details on it. A Cat instance may contain a Collar instance, but this containment is not too strict, as a Cat may exist without a Collar (and for that matter a Collar without a Cat). If you want containment in your code, you must create the Collar class as well. Of course, this is a completely separate class with a few simple data members, getters and setters. For simplicity, it will only have the name of the Cat, and the name of the owner, perhaps the collars can have a flea control property, in which case the stray cat instance won't pick up as many fleas.

On a class diagram, a containment can be represented in two ways: simply as a data member an instance of another class, or by writing the name, visibility, and other information of the data member on the aggregation.

Example of weak aggregation

Here you can see the numerosity, since a Cat object can have either 0 or 1 Collar. Let's make the implementation.

Java code

We won't show the details of the implementation of the Collar class separately here, but this is what the finished class looks like:

public class Collar {
    private String nameTag;
    private String ownerName;

    private boolean fleaRepellent;

    public Collar(String nameTag, String ownerName, boolean fleaRepellent) {
        this.nameTag = nameTag;
        this.ownerName = ownerName;
        this.fleaRepellent = fleaRepellent;
    }

    public Collar(String nameTag, String ownerName) {
        this.nameTag = nameTag;
        this.ownerName = ownerName;
        this.fleaRepellent = true;
    }

    public String getnameTag() {
        return nameTag;
    }

    public void setnameTag(String nameTag) {
        this.nameTag = nameTag;
    }

    public String getownerName() {
        return ownerName;
    }

    public void setownerName(String ownerName) {
        this.ownerName = ownerName;
    }

    public boolean isfleaRepellent() {
        return fleaRepellent;
    }

    public void setfleaRepellent(boolean fleaRepellent) {
        this.fleaRepellent = fleaRepellent;
    }
}

The containment part is the easiest: just add the new data member to the class.

public class Cat {
    private String name;
    private double weight;
    private boolean friendly;
    private int numberOfFleas;
    private Collar collar;
    private int numberOfPets = 0;
    private static int UNFRIENDLY_NUMBER = 0;


    // ...
}

You should also be able to create or set up a Collar instance somewhere inside the Cat class, for example using a traditional setter, or in the constructor.

    public Cat(String name, boolean friendly, String ownerName) {
        this(name, friendly);
        this.collar = new Collar(name, ownerName);
    }

You can see that we call another constructor of the class, and then create the Collar instance. If we want to use it somewhere else, we can create a function called stray, for example, which returns whether our given Cat instance is a stray. A cat is a stray if it has no collar (at least in this program).

The implementation is not too complicated. We know that Java is a reference-based language, the initial value of non-primitive data members is null. If no collar is created, then the value of the primitive data member will be null. We compare this simply with an equals sign, not with equals, because if the equals data member is null (so there is no Collar object pointed to by the equals data member), its equals method does not exist either, so we would get a NullPointerException error.

    public boolean stray() {
        return collar == null;
    }

If you want to create additional functionality, you could, for example, make the roams function of Cat only give your cat fleas if it is not wearing a flea collar.

    public void roams(double distance) {
        weight--;
        if (collar == null || !collar.isfleaRepellent()) {
            numberOfFleas += (distance / 20) + 1;
        }
    }

Of course, there may not be a collar data member here, so we need to check for this before calling the isfleaRepellent method of the collar on the cat.

Another simple example might be the case of a refrigerator-food-compressor. You can store food in a fridge, put food in, take food out, or even have no food in it at all.

Refrigerator example

Composition

This is a stronger physical containment, here the lifetime of the parts is strictly equal to the lifetime of the whole, e.g.: cat-head, apartment-beamconstruction.

Its mark: a line drawn between two classes, with a full rhombus on the side of the container.

Example of composition

In the example, there is a strong containment, composition, between the AnimalHospital and the ExamRoom, since the ExamRoom has no meaning in itself, it exists only within the AnimalHospital. However, there is aggregation between ExamRoom and Cat, since ExamRoom may contain Cat, but their existence is not strictly interdependent.

Technically, in Java, strict containment is not fully feasible, memory management is fully automatic, so there is no way to enforce composition (unlike, for example, C/C++, where you can store by value/pointer/reference). However, when designing, you still need to distinguish between aggregation and composition. In the previous Collar example, since a Collar instance is always created only in the Cat constructor, you might as well use composition on the diagram.

When designing, we should basically always choose between association/aggregation/composition based on the "strength" of the containment. But perhaps this simpler idea might also help:

  • "Has-A": Aggregation (contains, e.g. the fridge contains food)
  • "P art-of": composition, composition (part of, e.g. the compressor is part of the refrigerator)

The compressor is part of the refrigerator, it is strongly contained in the refrigerator. In the case of a composition, in general, the contained cannot exist without its container, we can't really interpret and use separate Compressor instances in our program.

Refrigerator example

Java code

Implementation is the same as traditional aggregation in Java, usually we see compositions as data members.

Inheritance

A relationship between classes where one class shares its structure and/or behavior with one or more other classes. This is a good thing, because we don't have to write them again, but we can implement some of the methods inherited from the parent in a more specific way, override them, or even define new ones. By inheritance you can create a whole hierarchy, the derived class inherits from the parent class(es). This is one of the foundational tools for reusability. The inheritance relation has no name, multiplicity. In inheritance, attributes (effectively data members in a program) and operations (methods) must be defined at the highest possible level. A derived (child) class inherits everything from the parent (attributes, behaviour, relationships) and can add its own. A derived class can also give an inherited operation its own implementation. This is called overriding and is the basis of polymorphism.

The typical number of inheritance levels (parent-child relationship) is around 3-5.

Example of inheritance

Multiple inheritance

It is possible for a class to inherit from not just one but several parent classes, i.e. it inherits all the properties and operations of all its parents, this is called multiple inheritance. However, it is worth being careful with this, because if a class inherits directly from many other classes, the class can easily become ambiguous and unmanageable.

Example of multiple inheritance

Note

In most object-oriented languages, including Java and C#, there is no multiple inheritance, but there is in e.g. C++, Python.

Java code

In Java there is only single inheritance!

This means that a class cannot have two or more direct parent classes. It does not, however, rule out the possibility of a class having more than one children's class, or of a children's class having its own children's class.

A new visibility is introduced called protected. With this visibility we can ensure that each data member or method is visible to child classes, but not to the outside world. This visibility can be seen in UML diagrams as #, so properties/operations that start with the prefix # are designed by the designer to have protected visibility.

We will continue with the cat example we started. However, besides continuing the Cat class we have so far, we will have a Tiger class in addition to Cat. The Tiger class will inherit everything from the Cat class, all the traits, all the behaviours. However, before we do that, we also need to clarify the Cat class. We want to split the two concepts, because our Cat class so far has included both general Cat properties and also specifically domestic cat properties. So let's start splitting these two concepts.

Cat inheritance

In the example, we can see that the numberOfFleas property in the Feline class now has protected visibility instead of private, which will mean that it will be convenient to access it in descendant classes. However, it is true that what was private cannot be accessed directly from descendant classes either, only via getter/setter. You can see that one of our constructors has also been protected, and that we have added a new data member that will store the exact taxonomic name of the particular kind of Feline. This can be implemented in the code as follows:

public class Feline {
    private String name;
    private double weight;
    protected int numberOfFleas;
    private String taxonomicClassification;

    public Feline(String name, double weight) {
        this.name = name;
        this.weight = weight;
        this.numberOfFleas = 0;
        this.taxonomicClassification = "Felidae";
    }

    protected Feline(String name, double weight, String taxonomicClassification) {
        this.name = name;
        this.weight = weight;
        this.numberOfFleas = 0;
        this.taxonomicClassification = taxonomicClassification;
    }

    public void meows() {
        System.out.println("<Don't know the exact meow.>");
    }

    public void roams(double distance) {
        weight--;
        numberOfFleas += (distance / 20) + 1;
    }

    public void treatFleas() {
        this.numberOfFleas = 0;
    }

    // Generated getter and setter functions
}

By default, all Cat types belong to the "Felidae" family if this property is not specified, but this property can only be set using the constructor available from the descendant classes.

Extends, super

This is a generic Feline class, however there will be more specialised classes in the code where we want to use the Feline class we already have, but want to give it more specialised functionality, perhaps adding more properties. Since Feline as a concept is not really meaningful, since we will want to store DomesticCats, Tigers in the program. For this we can use inheritance.

extends - we can use this keyword to achieve inheritance, in the class declaration, we have to write after the class name, then after the extends keyword we write the name of the parent class.

super - from the child class we can refer to the parent, its data members (which we can see) and its methods, these can be accessed with super.parentMethodName().

public class HouseCat extends Feline {
    private boolean friendly;
    private Collar collar;
    private int numberOfPets = 0;

    public HouseCat(String name, double weight, boolean friendly) {
        super(name, weight, "Felis silvestris catus");

    }

    public void meows() {
        String meow = "Me";
        for (int i = 0; i < getWeight(); i++) {
            if (i % 2 == 0) {
                meow += "o";
            } else {
                meow += "o";
            }
        }
        meow += "w";
        System.out.println(getName() + " meows: " + meow);
    }

    public void roams(double distance) {
        setWeight(getWeight() - 1);
        if (nyakorv == null || !collar.isFleaRepellent()) {
            numberOfFleas += (distance / 20) + 1;
        }
    }

    public boolean stray() {
        return collar == null;
    }

    public void isPet(Human who) {
        numberOfPets++;
        if (!friendly) {
            who.karmolasNovel();
            System.out.println(this.getName() + " scratched " + who.getName() + "-t!");

            if (numberOfPets > 30) {
                friendly = true;
            }
        }

    }
}

And the parent constructor can be accessed simply by using the super keyword as a method, for example:

super(name, weight, "Felis silvestris catus");

This initializes the properties of the parent class, calling the String, double, boolean headed constructor of the `Feline' class, which has protected visibility in the parent class.

If you want to call the constructor of the parent class without parameters, you can omit the super(); call in the constructor of the child class. However, if you do not use the default constructor of the parent class (or it has no default constructor), then it is obligatory to call super(arg1,arg2...argn); in the child class constructor!

In the child class, you can see that the meows() method inherited from the parent has been overridden, giving it a more special function. There is one small difference compared to the previous Cat class: since the weight and name data members have private visibility, we can access these data members via the getter/setter functions.

Of course, we can create additional classes, for example, for tigers, which are special cats, and then for a special kind of tiger, the sabertooth tiger.

public class Tiger extends Feline {
    public Tiger(String name, double weight) {
        super(name, weight, "Panthera tigris");
    }

    @Override
    public void meows() {
        String meow = "Ra";
        for (int i = 0; i < getWeight(); i++) {
            if (i % 2 == 0) {
                meow += "a";
            } else {
                meow += "a";
            }
        }
        meow += "wR";
        System.out.println(getName() + " roars: " + meow);
    }

    public void stronger(Tiger other) {
        if (this.getweight() > other.getWeight()) {
            this.meows();
            System.out.println("I scared the other tiger away with my roar!");
        } else {
            other.meows();
            System.out.println("The other tiger's roar frightened me, I have to run away...");
        }
    }

}

In the Tiger class, we also override the inherited meows method. The Tiger class does not have any special extra properties compared to the Cat class, it inherits everything from the Cat class, of course. However, it does have its own method that is specific to Tiger instances, and that is the stronger method, which can decide whether a given tiger is stronger than the tiger that arrives in the parameter. A tiger is stronger than another tiger if it is heavier and therefore roars (meows) louder.

The SabertoothTiger class represents a special kind of Tiger (which is also a special kind of Feline). The inherited meows method is also overdefined here, as sabertooth tigers can roar even louder than a normal tiger.

public class SabertoothTiger extends Tiger {
    private int canineSize;
    public SabertoothTiger(String name, double weight) {
        super(name, weight);
    }

    @Override
    public void meows() {
        String meow = "Ra";
        for (int i = 0; i < getWeight(); i++) {
            if (i % 2 == 0) {
                meow += "aa";
            } else {
                meow += "AA";
            }
        }
        meow += "wR";
        System.out.println(getName() + " roars: " + meow);
    }
}

Cat inheritance

As you can see, all Tigers (and SabertoothTigers) are also Felines, but not all Felines will be Tigers (or SabertoothTigers). The DomesticCat class is a special case of the Feline class, since domestic cats are special felines. From the other "direction": the Cat class is a generalisation of the Feline class, since felines are the general ancestor of special cats (Tiger, Domestic cat, etc.) with common characteristics and behaviour.

Final

The keyword final means final, which can have different meanings depending on the location.

  • Final data member: a data member whose value cannot be changed after its initialisation. A final data member may be given an initial value in the place of declaration as well as in the constructor, but only once. In UML, these properties are usually annotated with {readOnly}.
public class Feline {
    private String name;
    private double weight;
    protected int numberOfFleas;
    private final String taxonomicClassification;

    public Feline(String name, double weight) {
        this.name = name;
        this.suly = suly;
        this.numberOfFleas = 0;
        this.taxonomicClassification = "Felidae";
    }

    protected Feline(String name, double weight, String taxonomicClassification) {
        this.name = name;
        this.weight = weight;
        this.numberOfFleas = 0;
        this.taxonomicClassification = taxonomicClassification;
    }

    // ...
}

For final data members, it is true that if we have data of primitive type, its value cannot be changed after the declaration. The same is true if we have a non-primitive final data member, the value of the data member itself cannot be changed, so in this case the reference cannot be changed. However, the object itself can be modified, for example, if we have a final variable pointing to a Refrigerator, we can put things in/out of the refrigerator, but we cannot "replace" the refrigerator.

  • Final method: final methods cannot be overridden in the child class. This might make sense if you want to disallow overriding a behaviour in the child class, for example now the fights method of the Tiger class might be like this, since whatever special tigers we have, we don't want their fighting to be implemented differently.
public class Tiger {

    // ...

    public final void fights(Tiger other) {
        if (this.getWeight() > masik.getWeight()) {
            this.meows();
            System.out.println("I scared the other tiger away with my roar!");
        } else {
            masik.meows();
            System.out.println("The other tiger's roar frightened me, I have to run away...");
        }
    }

    // ...
}

After this, you cannot override this method in for example the SabertoothTiger class, if you try you will get an error.

  • Final class: Final classes cannot have child classes. For example, the class DomesticCat can be final if you don't want domestic cats to have more specialized types.

In UML, final methods and classes are usually denoted by <<final>>. You can read more about the use of the final keyword here and here.

Constant

There is no special keyword for it, but from what we have seen so far, it is easy to guess: creating a real constant by using the static and final keywords together, because the resulting variable: will be static, for any object instance we will see/change the same one specific data; and because of the final, the initial value cannot be changed.

Constant variable names are usually written in all capital letters, with underscores at word boundaries.

public class Apple {
 public static final int APPLE_WEIGHT = 10;
}
Accessing this from the outside:

int appleWeight = Apple.APPLE_WEIGHT;
Override

As we have already seen, when the IDE generates a method for us, for example toString, we can see an @Override line above the method header. In practice this has no effect, you can leave it out, but you can also keep it, and it is advisable to keep it. In Java, lines starting with @ above the method are called annotations. The @Override annotation is just an indication to the compiler that we have overridden a method inherited from an ancestor. It is very useful if you accidentally modify the ancestor class (rewrite the header of the function you are overriding) but do not align the header of the method in the child class, you will get a compilation error because you are marking the method as an override but not overriding anything.

Javadoc

Javadoc has been discussed before, but perhaps it will finally make sense for classes. It can be used to generate HTML-based documentation from comments written in source code. An example of a generated HTML page. It is simple to use, instead of the traditional commenting, you write comments between /** and */, and then use various special references in them, which will appear as special references in the generated documentation. All the special references and their descriptions are available at this link. For Javadoc, the general rule is that classes, their data members, and public methods should be properly documented. However, it is of course also advisable to document methods with private, protected visibility.

Summary

Connections:

  • "Has-A": Aggregation (contains, e.g. a house contains a mailbox)
  • "P art-of": Composition (part of, e.g. a room is part of the house)
  • "Is-a": Inheritance (is one, e.g. a bathroom is a room)

UML examples

Dog example

Tasks

  1. Create the example of a Refrigerator-Food-Compressor above. Implement the things in the diagram as appropriate. Use an array for the Refrigerator-Cooler aggregation.

UML design exercises

  1. Model credit card payment. Model the following classes and the relationships between them.

    • Bank
    • Account
    • Person
    • Card
    • Terminal ( two types: automatic and POS )
  2. Model a fairy tale world in which the characters are

    • Dragons
    • Knights and where we know that the Dragons are largely determined by the number of their Heads, which of course greatly influences the HeadChopOff activity of the Knights'.
  3. Make a class diagram with classrooms, machine rooms and related things. There should be at least 7 classes and multiple connections!

Oracle: Controlling Access to Members of a Class

Java access specifiers

Inheritance

Inheritance & Polymorphism

Enum Types