Ciao a tutti! Questo post è stato scritto, piuttosto, per coloro che hanno già visto e anche risolto diversi problemi sulle interfacce al livello 12 (2° livello di Java Core), ma si chiedono ancora: “Allora perché sono necessari se è ancora necessario implementare metodi in esse ?" dichiarato nell'interfaccia??? Cioè non riducono la quantità di codice!!!" . Non ho dubbi che questo argomento verrà trattato nelle lezioni successive e verranno rivelati significati più profondi e opzioni per l'utilizzo delle interfacce, ma se sei completamente a disagio, allora sei il benvenuto. Inizialmente volevo scrivere un commento, ma mi sono reso conto che sarebbe stato un commento molto lungo, quindi eccoci qui. Questo è il mio primo articolo, accetto critiche ragionevoli. Se sbaglio qualcosa, sentiti libero di correggermi. Quindi, come sappiamo, quando creiamo la nostra classe, possiamo ereditare solo da una classe astratta o non astratta . Ma allo stesso tempo nella nostra classe possiamo implementare un numero abbastanza elevato di interfacce . Quando erediti, devi essere guidato dal buon senso e comunque annotare le entità correlate nei tuoi discendenti (non erediteremo un gatto da un bulldozer e viceversa). Con le interfacce , la logica è leggermente diversa: ciò che è importante qui non sono le “entità” stesse, ma le loro “abilità” o cosa si può fare con esse . Ad esempio, sia un gatto che un bulldozer possono muoversi nello spazio. Cioè, in linea di principio, possono implementare l'interfaccia CanMove o Moveable. O un uomo e un gatto. Entrambi possono bere, ma lo fanno in modi diversi: una persona beve tè o caffè da una tazza e un gatto sorseggia dell'acqua o del latte da una ciotola. Ma in generale bevono entrambi, quindi ognuno di loro può realizzare la propria implementazione dell'interfaccia CanDrink. Che vantaggio ha questo per noi? Immagina di creare un gioco. Supponiamo che tu abbia un luogo: un fiume, con sponde su entrambi i lati, e poi una foresta e montagne. Vari tipi di creature viventi riposano sulla riva. All'improvviso si avvicina un'alluvione. Chiunque sappia volare vola via. Coloro che non possono volare, ma possono correre, corrono. Qualcuno sa nuotare, quindi in linea di principio non gli importa della tua alluvione (beh, oppure possono nuotare a riva), anche se alcuni di loro potrebbero prima provare a scappare (se sanno come farlo). Il resto, per quanto triste possa essere, morirà. Proviamo a implementarlo. (Non farti spaventare dalla grande quantità di codice contemporaneamente =) La maggior parte sono commenti) Di quali classi potremmo aver bisogno? Cominciamo con una classe astratta di diversi personaggi o unità (scusate, non sono esperto nella terminologia dei giochi, correggetemi se sbaglio). Sia questa la classe Unit . Assumeremo che assolutamente tutte le unità possano muoversi nello spazio ed emettere suoni. Unità di classe astratta :
// Абстрактный класс для всех юнитов
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();
    }
}
Di quali interfacce (“competenze”) hanno bisogno le nostre unità? Possono correre ( CanRun ), nuotare ( CanSwim ) o volare ( CanFly ). Alcune persone possono avere più abilità contemporaneamente, mentre alcune persone sfortunate potrebbero non averne nessuna.
// Интерфейсы. КАЖДЫЙ КЛАСС, "наследующий" Howой-то интерфейс,
// ДОЛЖЕН РЕАЛИЗОВАТЬ его у себя.
interface CanRun {
    void run(String action);
}
interface CanSwim {
    void swim();
}
interface CanFly {
    void fly();
}
Successivamente creiamo classi che ereditano dalla classe astratta Unit. Il percorso sarà la classe Umana :
// Человек НАСЛЕДУЕТ абстрактный класс 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() + " ) " +
                           "с двумя руками и двумя ногами " +
                           "хорошо плаваю");
    }
}
Classe di uccelli . Sì, capisco che non esistono uccelli semplici, ma per semplicità lasciamo che sia solo un uccello, non rendiamolo astratto:
// Птица НАСЛЕДУЕТ абстрактный класс 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() + " ) улетел отсюда");
    }
}
Ora creiamo un'altra classe astratta Animals , che verrà ereditata da Unit :
// Абстрактный класс Животных, НАСЛЕДУЕТ абстрактный класс Unit
public static abstract class Animal extends Unit {
    // тут могут быть Howие-то данные и/or методы
}
E già i suoi eredi Agnello ( Pecora ):
// Баран НАСЛЕДУЕТ класс 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 +
                           " отсюда на четырёх копытах");
    }
}
e Accidia ( Accidia ):
// Ленивец НАСЛЕДУЕТ класс 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) + " вперед");
    }
}
Il metodo move(int x, int y) della classe abstract Unit non è abstract , quindi non dobbiamo sovrascriverlo, ma abbiamo rallentato un po' le cose per Sloth. Ora passiamo all'azione.
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() + " ) умер");
}
I letterali numerici 12, 50, 400 e 1000 sono presi a mano; se ne possono specificare altri, ma spero che la logica sia chiara. Quindi, come possiamo vedere, avendo una classe genitore astratta con metodi generali, non dobbiamo pensare a quale classe specifica sia questa o quella unità, ma semplicemente chiamare questi metodi ( makeSound() e move ( ) ) . Dopo il primo passaggio nel ciclo, quando il metodo move() viene chiamato su tutte le unità , sullo schermo verrà visualizzato quanto segue: Interfacce per chi è “molto interessato, ma non capisce niente” - 1 Ovviamente, tutti gli oggetti tranne lo sloth visualizzano un messaggio standard dal metodo move() del genitore astratto la classe Unit e il bradipo ha un metodo move (metodo) è stato ridefinito . Tuttavia, una classe astratta non ci aiuterà a scoprire cosa “può fare” una particolare unità. È qui che entrano in gioco le interfacce . Utilizzando exampleof , scopriamo se questa unità può eseguire determinate azioni ( se supporta l'interfaccia di cui abbiamo bisogno ) e, in tal caso, utilizziamo il casting del tipo che ci è già familiare, ad esempio utilizzando ((CanFly) unit). fly() trasformiamo il nostro oggetto di tipo Unit nel tipo di interfaccia CanFly e chiamiamo il suo metodo fly() . E non importa quale classe abbia il nostro oggetto, l'unica cosa importante è che abbia indicato la capacità di volare nel suo “curriculum”. Gli diciamo: "Sai come, quindi vola! Non ci interessa come lo fai . " Per noi sviluppatori ciò significa che le classi che implementano l' interfaccia CanFly possono modificare l'implementazione del metodo fly() in qualsiasi momento e in qualsiasi modo , possono apparire nuove classi che lo implementano o, al contrario, gli sviluppatori possono rimuovere alcune delle vecchie classi. Ma finché questo metodo svolge le sue funzioni dichiarate e l'oggetto vola, non ci interessa. Il nostro codice che funziona con oggetti che implementano questa interfaccia rimarrà lo stesso; non dovremo cambiare nulla. Dopo il secondo ciclo, quando tutti cercheranno di scappare, a seconda dell'entità dell'alluvione, vedremo sullo schermo o Interfacce per chi è “molto interessato, ma non capisce niente” - 2 oppure Interfacce per chi è “molto interessato, ma non capisce niente” - 3 Senza un'interfaccia, dovremmo controllare che ogni oggetto sia conforme ad una determinata classe (e avremmo per controllare tutto) e tenere a mente le abilità di ogni classe specifica. Cioè, davanti a noi adesso c'è un agnello e sembra che sappia correre. Cosa succede se hai diverse dozzine o centinaia di tipi (classi) diversi di tali personaggi? E se non fossi stato tu a scriverli, ma un altro programmatore, quindi non hai idea di chi può fare cosa? Sarebbe molto più difficile... E una piccola aggiunta dopo la pubblicazione: Nella vita reale, non sei l'unico a lavorare su un progetto. Diciamo che fai solo logica. Tutti gli oggetti con cui interagisci sono scritti da altri programmatori. Potresti anche non conoscere tutte le classi con cui funziona il tuo codice. Tutto ciò di cui hai bisogno da loro è che facciano ciò di cui hai bisogno. Tuttavia, tutti possono farlo in modi completamente diversi. Ma, diciamo, nel tuo codice crei un metodo che funziona solo con oggetti di classi che supportano una determinata interfaccia
void doSomething(CanFly f)
ovvero, imposti l'interfaccia utilizzando il parametro del metodo. Non una classe concreta, ma un'interfaccia. E tutti gli altri programmatori, quando chiamano questo tuo metodo void doSomething(CanFly), devono passare come argomenti un oggetto esplicitamente di una classe che implementa CanFly, o qualche oggetto di qualche classe che può essere lanciato su di esso:
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
Ecco come le interfacce possono essere utili. E queste non sono tutte le loro capacità e metodi di applicazione. Probabilmente impareremo di più più avanti nel corso =) Grazie per aver letto fino alla fine =)