JavaRush /Java Blog /Random EN /Interfaces for those who are “very interested, but don’t ...
Lilly
Level 25
Москва

Interfaces for those who are “very interested, but don’t understand anything”

Published in the Random EN group
Hi all! This post was written, rather, for those who have already seen and even solved several problems on Interfaces at level 12 (2nd level of Java Core), but still wonder: “So why are they needed if you still need to implement methods in them?” declared in the interface??? That is, they do not reduce the amount of code!!!" . I have no doubt that this topic will be covered in subsequent lectures, and deeper meanings and options for using interfaces will be revealed, but if you are completely uneasy, then you are welcome. Initially I wanted to write a comment, but I realized that it would be a very long comment, so here we are. This is my first article, I welcome reasonable criticism. If I'm wrong somewhere, feel free to correct me. So, as we know, when creating our own class, we can inherit only from one abstract or non-abstract class . But at the same time, in our class we can implement a fairly large number of interfaces . When inheriting, you need to be guided by common sense and still write down related entities into your descendants (we won’t inherit a cat from a bulldozer and vice versa). With interfaces , the logic is a little different: what is important here is not the “entities” themselves, but their “skills” or what can be done with them . For example, both a cat and a bulldozer can move in space. That is, in principle, they can implement the CanMove or Moveable interface. Or a man and a cat. They both can drink, but they do it in different ways: a person drinks tea or coffee from a cup, and a cat laps up some water or milk from a bowl. But in general, they both drink, so each of them can make their own implementation of the CanDrink interface. What benefit does this have for us? Imagine that you are making a game. Let’s say you have a location: a river, with banks on both sides of it, and then a forest and mountains. Various types of living creatures rest on the shore. Suddenly a flood approaches. Everyone who can fly flies away. Those who cannot fly, but can run, run. Someone knows how to swim, so in principle they don’t care about your flood (well, or they can swim ashore), although some of them may first try to escape (if they know how). The rest, sad as it may be, will die. Let's try to implement this. (Don’t be scared by the large amount of code at once =) Most of it is comments) What classes might we need? Let's start with an abstract class of different characters or units (sorry, I'm not strong in gaming terminology, correct me if I'm wrong). Let this be the Unit class . We will assume that absolutely all units can move in space and make sounds. Abstract class Unit :
// Абстрактный класс для всех юнитов
public static abstract class Unit {
    // Базовый метод движения.
    // По умолчанию (если не переопределено) будет у всех наследников.
    public void move (int x, int y) {
        System.out.println("Я ( " + getClassName() + " ) просто брожу по полю на " +
                           x + " метров вправо и " + y + " метров вперед");
    }

    // Абстрактный метод, который ДОЛЖЕН ПЕРЕОПРЕДЕЛИТЬ у себя
    // КАЖДЫЙ КЛАСС, унаследованный от Unit.
    public abstract void makeSound();

    // Вспомогательный метод получения имени класса
    // без всей лишней информации.
    public String getClassName() {
        return this.getClass().getSimpleName();
    }
}
What interfaces (“skills”) do our units need? They can run ( CanRun ), swim ( CanSwim ) or fly ( CanFly ). Some people may have several skills at once, while some unfortunate people may have none.
// Интерфейсы. КАЖДЫЙ КЛАСС, "наследующий" Howой-то интерфейс,
// ДОЛЖЕН РЕАЛИЗОВАТЬ его у себя.
interface CanRun {
    void run(String action);
}
interface CanSwim {
    void swim();
}
interface CanFly {
    void fly();
}
Next we create classes that inherit from the abstract Unit class. The path will be the Human class :
// Человек НАСЛЕДУЕТ абстрактный класс Unit,
// а также РЕАЛИЗУЕТ интерфейсы CanRun, CanSwim
public static class Human extends Unit implements CanRun, CanSwim {
    // Переопределяем метод public void makeSound()
    // родительского абстрактного класса Unit
    @Override
    public void makeSound() {
        System.out.print("Йу-хуу! ");
    }

    // РЕАЛИЗУЕМ метод public void run(String action) интерфейса CanRun
    @Override
    public void run(String action) {
        System.out.println("Я ( " + this.getClassName() + " ) " + action +
                           " отсюда на двух ногах ");
    }

    // РЕАЛИЗУЕМ метод public void swim() интерфейса CanSwim
    @Override
    public void swim() {
        System.out.println("Я ( " + this.getClassName() + " ) " +
                           "с двумя руками и двумя ногами " +
                           "хорошо плаваю");
    }
}
Bird class . Yes, I understand that there are no simple birds, but for the sake of simplicity, let it be just a bird, let’s not make it abstract:
// Птица НАСЛЕДУЕТ абстрактный класс Unit,
// и РЕАЛИЗУЕТ интерфейс CanFly
public static class Bird extends Unit implements CanFly {
    // Переопределяем абстрактный метод public void makeSound()
    // родительского абстрактного класса Unit
    @Override
    public void makeSound() {
        System.out.print("Курлык! ");
    }

    // РЕАЛИЗУЕМ метод public void fly() интерфейса CanFly
    @Override
    public void fly() {
        System.out.println("Я ( " + this.getClassName() + " ) улетел отсюда");
    }
}
Now let’s create another abstract class Animals , which will be inherited from Unit :
// Абстрактный класс Животных, НАСЛЕДУЕТ абстрактный класс Unit
public static abstract class Animal extends Unit {
    // тут могут быть Howие-то данные и/or методы
}
And already his heirs Lamb ( Sheep ):
// Баран НАСЛЕДУЕТ класс Animal,
// и РЕАЛИЗУЕТ интерфейс CanRun
public static class Sheep extends Animal implements CanRun {
    // Переопределяем абстрактный метод public void makeSound()
    // родительского абстрактного класса Unit
    @Override
    public void makeSound() {
        System.out.print("Беееее! ");
    }

    // РЕАЛИЗУЕМ метод public void run(String action) интерфейса CanRun
    @Override
    public void run(String action) {
        System.out.println("Я ( "+ this.getClassName() + " ) " + action +
                           " отсюда на четырёх копытах");
    }
}
and Sloth ( Sloth ):
// Ленивец НАСЛЕДУЕТ класс Animal
// и внутри себя ПЕРЕОПРЕДЕЛЯЕТ метод
// void move(int x, int y) абстрактного класса Unit
public static class Sloth extends Animal {
    // Переопределяем абстрактный метод public void makeSound()
    // родительского абстрактного класса Unit
    @Override
    public void makeSound() {
        System.out.print("Зевает... ");
    }

	// Переопределяем метод public void move(int x, int y)
    // родительского абстрактного класса Unit
    @Override
    public void move(int x, int y) {
        System.out.println("Я ( "+ getClassName() + " ) очень медленный, поэтому " +
                           "переместился на " + (int)(x/12) + " вправо " +
                           "и на " + (int)(y/12) + " вперед");
    }
}
The move(int x, int y) method of the abstract Unit class is not abstract , so we don't have to override it, but we did slow things down a bit for Sloth. Now let's move on to action.
public static void main(String[] args) {
    // создаём список юнитов
    // и добавляем туда представителей разных классов
    List<Unit> units = new ArrayList<unit>();
    units.add(new Human());
    units.add(new Bird());
    units.add(new Sheep());
    units.add(new Sloth());

    // проходим в цикле по всем юнитам и вызываем метод move(int x, int y).
    // У всех, кроме ленивца, будет вызван метод базового класса.
    // У Ленивца будет вызван его переопределенный метод
    for (Unit unit : units) {
		// в самом начале ничего не происходит, юниты просто двигаются.
        unit.move((int)(Math.random()*50), (int)(Math.random()*50));
    }

    System.out.println("\n...Наводнение приближается....");
    int distanceOfFlood = (int)(Math.random()*1000); // Число от 0 до 1000
    System.out.println("...Наводнение на " + distanceOfFlood + " от берега...\n");

    // проходим в цикле по всем юнитам
    for (Unit unit : units) {
        unit.makeSound(); // у каждого юнита свой, переопределенный, звук
        // смотрим, кто что "умеет":
		// если юнит умеет летать
        if(unit instanceof CanFly)
            ((CanFly) unit).fly(); // проблем нет, улетает
        // если юнит не умеет летать, но умеет бегать
        else if(unit instanceof CanRun) {
			// смотрим на Howое расстояние разлилась вода
            if(distanceOfFlood < 400) {
				// если меньше 400 (условно метров)
                ((CanRun) unit).run("убежал"); // юнит убежал
            }
            else {
				// если больше 400, юнит безуспешно пытается убежать
                ((CanRun) unit).run("пытался убежать");
				// если юнит может не только бегать, но и плавать
                if (unit instanceof CanSwim) {
                    ((CanSwim) unit).swim(); // плывёт
                }
				// иначе умирает (он хотя бы пытался)
                else unitIsDead(unit);
            }
        }
		// если юнит не летает, не бегает, но может плавать
        else if (unit instanceof CanSwim)
            ((CanSwim) unit).swim(); // плывёт
        else
			// если юнит не умеет совсем ничего - грустненько :(
            unitIsDead(unit); // умирает

        System.out.println();
    }
}

public static void unitIsDead(Unit unit) {
    System.out.println("Извините, я ( " + unit.getClassName() + " ) умер");
}
The numeric literals 12, 50, 400 and 1000 are taken offhand; others can be specified, but I hope the logic is clear. So, as we can see, having an abstract parent class with general methods, we don’t have to think about what specific class this or that unit is, but simply call these methods ( makeSound() and move() ). After the first pass in the loop, when the move() method is called on all units , the following will be displayed on the screen: Interfaces for those who are “very interested, but don’t understand anything” - 1 Obviously, all objects except the sloth display a standard message from the move() method of the abstract parent class Unit , and the sloth has a move( method ) has been redefined . However, an abstract class will not help us find out what a particular unit “can do”. This is where interfaces come into play . Using instanceof , we find out whether this unit can perform certain actions ( whether it supports the interface we need ), and if so, we use the type casting that is already familiar to us, for example, using ((CanFly) unit).fly() we cast our Unit type object to the CanFly interface type and call its fly() method . And it doesn’t matter what class our object has, the only important thing is that he indicated the ability to fly in his “resume”. We tell him: “You know how, so fly! We don’t care how you do it . ” That is, for us, as developers, this means that classes that implement the CanFly interface can change the implementation of the fly() method at any time and in any way , new classes that implement it can appear, or, conversely, developers can remove some of the old ones classes. But as long as this method performs its stated functions and the object flies, we don't care. Our code that works with objects that implement this interface will remain the same; we won’t have to change anything. After the second cycle, when everyone is trying to escape, depending on the scale of the flood, we will see on the screen either Interfaces for those who are “very interested, but don’t understand anything” - 2 or Interfaces for those who are “very interested, but don’t understand anything” - 3 Without an interface, we would have to check each object for compliance with a certain class (and we would have to check everything) and keep in mind the skills of each specific class . That is, there is a lamb in front of us now and it seems to know how to run. What if you have several dozen or hundreds of different types (classes) of such characters? And what if it wasn’t you who wrote them, but another programmer, so you have no idea who can do what? It would be much more difficult... And a small addition after publication: In real life, you are not the only one working on a project. Let's say you only do logic. All the objects you interact with are written by other programmers. You may not even know all the classes your code works with. All you need from them is that they do what you need them to do. However, they can all do this in completely different ways. But, let’s say, in your code you create a method that works only with objects of classes that support a certain interface
void doSomething(CanFly f)
that is, you set the interface using the method parameter. Not a concrete class, but an interface. And all other programmers, when calling this void doSomething(CanFly) method of yours, must pass as arguments either an explicitly object of a class that implements CanFly, or some object of some class that can be cast to it:
Object obj = new Bird();
doSomething(obj); // ошибка компиляции Object cannot be converted to CanFly
doSomething((CanFly) obj); // нет ошибки, потому что obj у нас класса Bird и реализует CanFly

Bird b = new Bird();
doSomething(b); // нет ошибки

Human h = new Human() ;
doSomething(h); // ошибка компиляции
doSomething((CanFly) h); // ошибка времени выполнения ClassCastException
This is how interfaces can be useful. And this is not all of their capabilities and methods of application. We'll probably learn more later in the course =) Thank you for reading to the end =)
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION