Сегодня мы рассмотрим: Настоящие ценители музыки знают, что для качественного...
An interface is just like Java Class, but it only has static constants and abstract method. Java uses Interface to implement multiple inheritance. A Java class can implement multiple Java Interfaces. .
Syntax for Declaring Interface
Interface { //methods }
To use an interface in your class, append the keyword "implements" after your class name followed by the interface name.
Example for Implementing Interface
Class Dog implements Pet interface RidableAnimal extends Animal, Vehicle
Why is an Interface required?
To understand the concept of Java Interface better, let see an example. The class "Media Player" has two subclasses: CD player and DVD player. Each having its unique implementation method to play music.
Another class "Combo drive" is inheriting both CD and DVD (see image below). Which play method should it inherit? This may cause serious design issues. And hence, Java does not allow multiple inheritance.
Now let"s take another example of Dog.
Suppose you have a requirement where class "dog" inheriting class "animal" and "Pet" (see image below). But you cannot extend two classes in Java. So what would you do? The solution is Interface.
The rulebook for interface says,
- An interface is 100% abstract class and has only abstract methods.
- Class can implement any number of interfaces.
Class Dog can extend to class "Animal" and implement interface as "Pet".
Java Interface Example:
Step 1) Copy following code into an editor.
Interface Pet{ public void test(); } class Dog implements Pet{ public void test(){ System.out.println("Interface Method Implemented"); } public static void main(String args){ Pet p = new Dog(); p.test(); } }
Step 2) Save , Compile & Run the code. Observe the Output.
Difference between Class and Interface
Class | Interface |
---|---|
In class, you can instantiate variable and create an object. | In an interface, you can"t instantiate variable and create an object. |
Class can contain concrete(with implementation) methods | The interface cannot contain concrete(with implementation) methods |
The access specifiers used with classes are private, protected and public. | In Interface only one specifier is used- Public. |
When to use Interface and Abstract Class?
- Use an abstract class when a template needs to be defined for a group of subclasses
- Use an interface when a role needs to be defined for other classes, regardless of the inheritance tree of these classes
Must know facts about Interface
- A Java class can implement multiple Java Interfaces. It is necessary that the class must implement all the methods declared in the interfaces.
- Class should override all the abstract methods declared in the interface
- The interface allows sending a message to an object without concerning which classes it belongs.
- Class needs to provide functionality for the methods declared in the interface.
- All methods in an interface are implicitly public and abstract
- An interface can extend from one or many interfaces. Class can extend only one class but implement any number of interfaces
- An interface cannot implement another Interface. It has to extend another interface if needed.
- An interface which is declared inside another interface is referred as nested interface
- At the time of declaration, interface variable must be initialized. Otherwise, the compiler will throw an error.
- The class cannot implement two interfaces in java that have methods with same name but different return type.
Summary :
- The class which implements the interface needs to provide functionality for the methods declared in the interface
- All methods in an interface are implicitly public and abstract
- An interface cannot be instantiated
- An interface reference can point to objects of its implementing classes
- An interface can extend from one or many interfaces. A class can extend only one class but implement any number of interfaces
Интерфейсы Java созданы для поддержки динамического выбора (resolution) методов во время выполнения программы. Интерфейсы похожи на классы, но в отличие от последних у интерфейсов нет переменных представителей, а в объявлениях методов отсутствует реализация. Класс может иметь любое количество интерфейсов. Все, что нужно сделать - это реализовать в классе полный набор методов всех интерфейсов. Сигнатуры таких методов класса должны точно совпадать с сигнатурами методов реализуемого в этом классе интерфейса. Интерфейсы обладают своей собственной иерархией, не пересекающейся с классовой иерархией наследования. Это дает возможность реализовать один и тот же интерфейс в различных классах, никак не связанных по линии иерархии классового наследования. Именно в этом и проявляется главная сила интерфейсов.
8.2.1. Оператор interface
Определение интерфейса сходно с определением класса, отличие состоит в том, что в интерфейсе отсутствуют объявления данных и конструкторов. Общая форма интерфейса приведена ниже:
interface
имя {
тип_результата имя_метода1 (список
параметров);
тип имя_
finall
_переменной =
значение;
}
Обратите внимание - у объявляемых в интерфейсе методов отсутствуют операторы тела. Объявление методов завершается символом «;» (точка с запятой). В интерфейсе можно объявлять и переменные, при этом они неявно объявляются переменными типа final. Это означает, что класс реализации не может изменять их значения. Кроме того, при объявлении переменных в интерфейсе их обязательно нужно инициализировать константными значениями. Ниже приведен пример определения интерфейса, содержащего единственный метод с именем callback и одним параметром типа int.
interface Callback {
void callback(int param);
}
8.2.2. Оператор implements
Оператор implements - это дополнение к определению
класса, реализующего некоторый интерфейс.
class
имякласса [
extends
суперкласс]
[
implements
интерфейс0 [, интерфейс 1...]]
{тело класса}
Если в классе реализуется несколько интерфейсов, то их имена разделяются запятыми. Ниже приведен пример класса, в котором реализуется определенный нами интерфейс:
class Client implements Callback {
void callback(int p) {
System.out.println("callback
вызван
с
" + p);
}
}
В очередном примере метод callback интерфейса, определенного ранее, вызывается через переменную-ссылку на интерфейс:
class Testlface {
public static void main(String args)
{
switch(result) {
case NO:
System.out.println("He
т
");
break;
case YES:
System.out.println("
Д
a");
break;
case MAYBE:
System.out.println("Mo
ж
e
т
быть
");
break;
case LATER:
System.out.println("
Позже
");
break;
case SOON:
System.out.priniln("C
к
opo");
break;
case NEVER:
System.out.println("
Никогда
");
break;
}
}
public static
void main(String args) {
Question q = new Question();
answer(q.ask());
answer(q.ask());
answer(q.ask());
answer
(q
.
ask
());
}
}
Обратите внимание на то, что результаты при разных запусках программы отличаются, поскольку в ней используется класс генерации случайных чисел Random пакета java.util.
Позже
Скоро
Нет
Да
Пакет (package ) - это некий контейнер, который используется для того, чтобы изолировать имена классов. Например, вы можете создать класс List, заключить его в пакет и не думать после этого о возможных конфликтах, которые могли бы возникнуть если бы кто-нибудь еще создал класс с именем List.
Интерфейс - это явно указанная спецификация набора методов, которые должны быть представлены в классе, который реализует эту спецификацию. Реализация же этих методов в интерфейсе отсутствует. Подобно абстрактным классам интерфейсы обладают замечательным дополнительным свойством - их можно многократно наследовать. Конкретный класс может быть наследником лишь одного суперкласса, но зато в нем может быть реализовано неограниченное число интерфейсов.
Пакеты
Все идентификаторы, которые мы до сих пор использовали в наших примерах, располагались в одном и том же пространстве имен (name space). Это означает, что нам во избежание конфликтных ситуаций приходилось заботиться о том, чтобы у каждого класса было свое уникальное имя. П акеты - это механизм, который служит как для работы с пространством имен, так и для ограничения видимости. У каждого файла.java есть 4 одинаковых внутренних части, из которых мы до сих пор в наших примерах использовали только одну. Ниже приведена общая форма исходного файла Java.
одиночный оператор package (необязателен)
любое количество операторов import (необязательны)
одиночное объявление открытого (public) класса
любое количество закрытых (private) классов пакета (необязательны)
Оператор package
Первое, что может появиться в исходном файле Java - это оператор package, который сообщает транслятору, в каком пакете должны определяться содержащиеся в данном файле классы. Пакеты задают набор раздельных пространств имен, в которых хранятся имена классов. Если оператор package не указан, классы попадают в безымянное пространство имен, используемое по умолчанию. Если вы объявляете класс, как принадлежащий определенному пакету, например,
package java.awt.image;
то и исходный код этого класса должен храниться в каталогеjava/awt/image.
ЗАМЕЧАНИЕ
Каталог, который транслятор Java будет рассматривать, как корневой для иерархии пакетов, можно задавать с помощью переменной окружения СLASSPATH. С помощью этой переменной можно задать несколько корневых каталогов для иерархии пакетов (через ; как в обычном PATH).
Трансляция классов в пакетах
При попытке поместить класс в пакет, вы сразу натолкнетесь на жесткое требование точного совпадения иерархии каталогов с иерархией пакетов. Вы не можете переименовать пакет, не переименовав каталог, в котором хранятся его классы. Эта трудность видна сразу, но есть и менее очевидная проблема.
Представьте себе, что вы написали класс с именем PackTest в пакете test. Вы создаете каталог test, помещаете в этот каталог файл PackTest.Java и транслируете. Пока - все в порядке. Однако при попытке запустить его вы получаете от интерпретатора сообщение «can"t find class PackTest» («He могу найти класс PackTest»). Ваш новый класс теперь хранится в пакете с именем test, так что т еперь надо указывать всю иерархию пакетов, разделяя их имена точками - test.PackTest. Кроме того Вам надо либо подняться на уровень выше в иерархии каталогов и снова набрать «java test.PackTest», либо внести в переменную CLASSPATH каталог, который является вершиной иерархии разрабатываемых вами классов.
Оператор import
После оператора package, но до любого определения классов в исходном Java-файле, может присутствовать список операторов import. Пакеты являются хорошим механизмом для отделения классов друг от друга, поэтому все встроенные в Java классы хранятся в пакетах. Общая форма оператора import такова:
import пакет1 [.пакет2].(имякласса|*);
Здесь пакет1 - имя пакета верхнего уровня, пакет2 - это необязательное имя пакета, вложенного в первый пакет и отделенное точкой. И, наконец, после указания пути в иерархии пакетов, указывается либо имя класса, либо метасимвол звездочка. Звездочка означает, что, если Java-транслятору потребуется какой-либо класс, для которого пакет не указан явно, он должен просмотреть все содержимое пакета со звездочкой вместо имени класса. В приведенном ниже фрагменте кода показаны обе формы использования оператора import:
import java.util.Date
import java.io.*;
ЗАМЕЧАНИЕ
Но использовать без нужды ф орму записи оператора import с использованием звездочки не рекомендуется, т.к. это может значительно увеличить время трансляции кода (на скорость работы и размер программы это не влияет).
Все встроенные в Java классы, которые входят в комплект поставки, хранятся в пакете с именем java. Базовые функции языка хранятся во вложенном пакете java.lang. В есь этот пакетавтоматически импортируется транслятором во все программы. Это эквивалентно размещению в начале каждой программы оператора
import java.lang.*;
Если в двух пакетах, подключаемых с помощью формы оператора import со звездочкой, есть классы с одинаковыми именами, однако вы их не используете, транслятор не отреагирует. А вот при попытке использовать такой класс, вы сразу получите сообщение об ошибке, и вам придется переписать операторы import, чтобы явно указать, класс какого пакета вы имеете ввиду.
class MyDate extends Java.util.Date { }
Ограничение доступа
Java предоставляет несколько уровней защиты, обеспечивающих возможность тонкой настройки области видимости данных и методов. Из-за наличия пакетов Java должна уметь работать еще с четырьмя категориями видимости между элементами классов:
Подклассы в том же пакете.
Не подклассы в том же пакете.
Подклассы в различных пакетах.
Классы, которые не являются подклассами и не входят в тот же пакет.
В языке Java имеется три уровня доступа, определяемых ключевыми словами: private (закрытый), public (открытый) и protected (защищенный), которые употребляются в различных комбинациях. Содержимое ячеек таблицы определяет доступность переменной с данной комбинацией модификаторов (столбец) из указанного места (строка).
модификатор отсутствует |
private protected |
||||
тот же класс |
|||||
подкласс в том же пакете |
|||||
независимый класс в том же пакете |
|||||
подкласс в другом пакете |
|||||
независимый класс в другом пакете |
На первый взгляд все это может показаться чрезмерно сложным, но есть несколько правил, которые помогут вам разобраться. Элемент, объявленный public, доступен из любого места. Все, что объявлено private, доступно только внутри класса, и нигде больше. Если у элемента вообще не указан модификатор уровня доступа, то такой элемент будет виден из подклассов и классов того же пакета. Именно такой уровень доступа используется в языке Java по умолчанию. Если же вы хотите, чтобы элемент был доступен извне пакета, но только подклассам того класса, которому он принадлежит, вам нужно объявить такой элемент protected. И наконец, если вы хотите, чтобы элемент был доступен только подклассам, причем независимо от того, находятся ли они в данном пакете или нет - используйте комбинацию private protected.
Ниже приведен довольно длинный пример, в котором представлены все допустимые комбинации модификаторов уровня доступа. В исходном коде первого пакета определяется три класса: Protection, Derived и SamePackage. В первом из этих классов определено пять целых переменных - по одной на каждую из возможных комбинаций уровня доступа. Переменной n приписан уровень доступа по умолчанию, n_pri - уровень private, n_pro - protected, n_pripro - private protected и n_pub - public. Во всех остальных классах мы пытаемся использовать переменные первого класса. Те строки кода, которые из-за ограничения доступа привели бы к ошибкам при трансляции, закомментированы с помощью однострочных комментариев (//) - перед каждой указано, откуда доступ при такой комбинации модификаторов был бы возможен. Второй класс - Derived - является подклассом класса Protection и расположен в том же пакете р1. Поэтому ему доступны все перечисленные переменные за исключением n_pri. Третий класс, SamePackage, расположен в том же пакете, но при этом не является подклассом Protection. По этой причине для него недоступна не только переменная n_pri, но и n_pripro, уровень доступа которой - private protected.
package р1;
public class Protection {
int n = 1;
private int n_pri = 2;
protected int n_pro = 3;
private protected int n_pripro = 4;
public int n_pub = 5;
public Protection() {
System.out.println("base constructor");
System.out.println("n = " + n);
System.out.println("n_pri = " + n_pri);
System.out.println("n_pro = " + n_pro);
System.out.println("n_pripro = " + n_pripro);
System.out.println("n_pub = " + n_pub);
} }
class Derived extends Protection {
Derived() {
System.out.println("derived constructor");
System.out.println("n = " + n);
// только в классе
// System.out.println("n_pri = " + n_pri);
System.out.println("n_pro = "+ n_pro);
System.out.println("n_pripro = " + n_pripro);
System.out.println("n_pub = "+ n_pub);
} }
classSamePackage {
SamePackage() {
Protection p = new Protection();
System.out.println("same package constructor");
System.out.println("n = " + p.n);
// только в классе
// System.out.println("n_pri = " + p.n_pri);
System.out.println("n_pro = " + p.n_pro);
// только в классе и подклассе
// System.out.println("n_pripro = " + p.n_pripro):
System.out.println("n_pub = " + p.n_pub):
} }
Интерфейсы
Интерфейсы Java созданы для поддержки динамического выбора (resolution) методов во время выполнения программы. Интерфейсы похожи на классы, но в отличие от последних у интерфейсов нет переменных представителей, а в объявлениях методов отсутствует реализация. Класс может иметь любое количество интерфейсов. Все, что нужно сделать - это реализовать в классе полный набор методов всех интерфейсов. Сигнатуры таких методов класса должны точно совпадать с сигнатурами методов реализуемого в этом классе интерфейса. Интерфейсы обладают своей собственной иерархией, не пересекающейся с классовой иерархией наследования. Это дает возможность реализовать один и тот же интерфейс в различных классах, никак не связанных по линии иерархии классового наследования. Именно в этом и проявляется главная сила интерфейсов. Интерфейсы являются аналогом механизма множественного наследования в C++, но использовать их намного легче.
Оператор interface
Определение интерфейса сходно с определением класса,отличие состоит в том, что в интерфейсе отсутствуют объявления данных и конструкторов. Общая форма интерфейса приведена ниже:
interface имя {
тип_результата имя_метода1(список параметров);
тип имя_final1-переменной = значение;
}
Обратите внимание - у объявляемых в интерфейсе методов отсутствуют операторы тела. Объявление методов завершается символом; (точка с запятой). В интерфейсе можно объявлять и переменные, при этом они неявно объявляются final - переменными. Это означает, что класс реализации не может изменять их значения. Кроме того, при объявлении переменных в интерфейсе их обязательно нужно инициализировать константными значениями. Ниже приведен пример определения интерфейса, содержащего единственный метод с именем callback и одним параметром типа int.
interface Callback {
void callback(int param);
}
Оператор implements
Оператор implements - это дополнение к определению класса, реализующего некоторый интерфейс(ы).
class имя_класса
] { тело класса }
Если в классе реализуется несколько интерфейсов, то их имена разделяются запятыми. Ниже приведен пример класса, в котором реализуется определенный нами интерфейс:
class Client implements Callback {
void callback(int p) {
System.out.println("callback called with " + p);
} }
В очередном примере метод callback интерфейса, определенного ранее, вызывается через переменную - ссылку на интерфейс:
class TestIface {
public static void main(String args) { Callback с = new client();
c.callback(42);
} }
Ниже приведен результат работы программы:
С:\> Java TestIface
callback called with 42
Переменные в интерфейсах
Интерфейсы можно использовать для импорта в различные классы совместно используемых констант. В том случае, когда вы реализуете в классе какой-либо интерфейс, все имена переменных этого интерфейса будут видимы в классе как константы. Это аналогично использованию файлов-заголовков для задания в С и C++ констант с помощью директив #define или ключевого слова const в Pascal / Delphi.
Если интерфейс не включает в себя методы, то любой класс, объявляемый реализацией этого интерфейса, может вообще ничего не реализовывать. Для импорта констант в пространство имен класса предпочтительнее использовать переменные с модификатором final. В приведенном ниже примере проиллюстрировано использование интерфейса для совместно используемых констант.
import java.util.Random;
interface SharedConstants { int NO = 0;
int YES = 1;
int MAYBE = 2;
int LATER = 3;
int SOON = 4;
int NEVER = 5; }
class Question implements SharedConstants {
Random rand = new Random();
int ask() {
int prob = (int) (100 * rand.nextDouble());
if (prob < 30)
return NO; // 30% else if (prob < 60)
return YES; // 30% else if (prob < 75)
return LATER; // 15% else if (prob < 98)
return SOON; // 13% else
return NEVER; // 2% } }
class AskMe implements SharedConstants {
static void answer(int result) {
switch(result) {
case NO:
System.out.println("No");
break;
case YES:
System.out.println("Yes");
break;
case MAYBE:
System.out.println("Maybe");
break;
case LATER:
System.out.println("Later");
break;
case SOON:
System.out.priniln("Soon");
break;
case NEVER:
System.out.println("Never");
break;
} }
public static void main(String args) {
Question q = new Question();
answer(q.ask());
answer(q.ask());
answer(q.askO);
answer(q.ask());
} }
Обратите внимание на то, что результаты при разных запусках программы отличаются, поскольку в ней используется класс генерации случайных чисел Random пакета java.util. Описание этого пакета приведено в главе 12 .
С:\> Java AskMe
Later
Scon
No
Yes
Использование пакетов
Теперь вы обладаете полной информацией для создания собственных пакетов классов. Легко понимаемые интерфейсы позволят другим программистам использовать ваш код для самых различных целей. Инструменты, которые вы приобрели, изучив эту и предыдущую главы, должны вам помочь при разработке любых объектно-ориентированных приложений. В дальнейшем вы познакомитесь с некоторыми важными специфическими свойствами Java, которые представлены в виде классов в пакете java.lang. В трех последующих главах вы освоите работу с
Чтобы было проще понять интерфейсы в Java, можно их представлять как полностью абстрактные классы, то есть классы все методы которых не имеют реализации, а только лишь объявлены. Но так было до Java 8, в которой появилась возможность создавать в интерфейсе реализацию метода по умолчанию. Интерфейсы так же могут содержать поля, но все они будут объявлены как final и static , то есть по существу являются константами. Кроме того, интерфейсы служат для реализации подобия множественного наследования в Java, которое в чистом виде в Java не поддерживается.
Как уже говорилось, унаследоваться (extends ) в Java можно только от одного класса, каждый класс В или С происходит из неполной семьи, как показано на рисунке а ниже. Все классы происходят только от "Адама", от класса Object. Но часто возникает необходимость породить класс D от двух классов B и С, как показано на рисунке б . Это называется множественным наследованием (multiple inheritance ). В множественном наследовании нет ничего плохого. Трудности возникают, если классы B и C сами порождены от одного класса А, как показано на рисунке в . Это так называемое "ромбовидное" наследование.
В самом деле, пусть в классе А определен метод f(), к которому мы обращаемся из некоторого метода класса D. Можем мы быть уверены, что метод f() выполняет то, что написано в классе А, т. е. это метод A.f()? Может, он переопределен в классах B и С? Если так, то каким вариантом мы пользуемся: В.f() или C.f()? Конечно, допустимо определить экземпляры классов и обращаться к методам этих экземпляров, но это совсем другая ситуация.
В различных языках программирования этот вопрос решается по-разному, главным образом уточнением имени метода f(). Но при этом всегда нарушается принцип KISS. Вокруг множественного наследования всегда много споров, есть его ярые приверженцы и столь же ярые противники. Не будем встревать в эти споры, наше дело — наилучшим образом использовать средства языка для решения своих задач.
Создатели языка Java после долгих споров и размышлений поступили радикально — запретили множественное наследование классов вообще. При расширении класса после слова extends можно написать только одно имя суперкласса. С помощью уточнения super можно обратиться только к членам непосредственного суперкласса.
Но что делать, если все-таки при порождении надо использовать несколько предков? Например, у нас есть общий класс автомобилей Automobile, от которого можно породить класс грузовиков Truck и класс легковых автомобилей Car. Но вот надо описать пикап Pickup. Этот класс должен наследовать свойства и грузовых, и легковых автомобилей.
В таких случаях используется еще одна конструкция языка Java — интерфейс. Внимательно проанализировав ромбовидное наследование, теоретики ООП выяснили, что проблему создает только реализация методов, а не их описание.
Интерфейс (interface ), в отличие от класса, содержит только константы, заголовки методов, и с Java 8 может содержать реализацию методов по умолчанию.
Интерфейсы тоже размещаются в файлах с расширением.java, которые в свою очередь могут находится в пакетах и подпакетах, часто в тех же самых, что и классы, и тоже компилируются в class-файлы.
Описание интерфейса начинается со слова interface , перед которым может стоять модификатор public , означающий, как и для класса, что интерфейс доступен всюду. Если же модификатора public нет, интерфейс будет виден только в своем пакете.
После слова interface записывается имя интерфейса, потом может стоять слово extends и список интерфейсов-предков через запятую. Таким образом, одни интерфейсы могут порождаться от других интерфейсов, образуя свою, независимую от классов, иерархию, причем в ней допускается множественное наследование интерфейсов. В этой иерархии нет корня, общего предка.
Затем в фигурных скобках записываются в любом порядке константы, заголовки методов и реализации методов по умолчанию. Можно сказать, что в интерфейсе все методы абстрактные (те у которых нет реализации по умолчанию), но слово abstract писать не надо. Константы всегда статические, но слова static и final указывать не нужно. Все эти модификаторы принимаются по умолчанию. Все константы и методы в интерфейсах всегда открыты, не обязательно даже указывать модификатор public .
Вот какую схему можно предложить для иерархии автомобилей:
interface
Automobile{ . . . }
interface
Car extends
Automobile{ . . . }
interface
Truck extends
Automobile{ . . . }
interface
Pickup extends
Car, Truck{ . . . }
Таким образом, интерфейс — это только набросок, эскиз. В нем указано, что делать, но не указано, как это делать (если нет реализации по умолчанию).
Поскольку интерфейсы – это как-бы полностью абстрактные классы, то невозможно создать экземпляр интерфейса, но можно создать объектную ссылку интерфейсного типа . Ссылочная переменная интерфейса располагает сведениями только о тех методах, которые объявлены в этом интерфейсе .
Как же тогда использовать интерфейсы, если невозможно создать объекты на их основе?
Использовать нужно не интерфейс, а его реализацию (implementation ). Реализация интерфейса — это класс, в котором расписываются методы одного или нескольких интерфейсов. В заголовке класса после его имени или после имени его суперкласса, если он есть, записывается слово implements и, через запятую, перечисляются имена интерфейсов.
Вот как можно реализовать иерархию автомобилей:
interface
Automobile{ . . . }
interface
Car extends
Automobile{ . . . }
class
Truck implements
Automobile{ . . . }
class
Pickup extends
Truck implements
Car{ . . . }
interface
Automobile{ . . . }
interface
Car extends
Automobile{ . . . }
interface
Truck extends
Automobile{ . . . }
class
Pickup implements
Car, Truck{ . . . }
Реализация интерфейса может быть неполной, некоторые методы интерфейса могут быть реализованы, а другие — нет. Такая реализация — абстрактный класс, его обязательно надо пометить модификатором abstract .
Если класс реализует более одного интерфейса, то он должен предоставить реализацию каждого метода каждого интерфейса либо он должен быть объявлен как abstract .
Методы, которые реализуют интерфейс, должны быть объявлены как public .
Теперь немного попрактикуемся, чтобы понять все получше на примере со звуками животных из прошлого поста. Переделаем ту программу чтобы она использовала интерфейс.
Слева представлены основной класс с методом main() и интерфейс в котором мы опеделили метод getSound() с реализацией по умолчанию, которая будет срабатывать если в классе имплементирующем этот интерфейс не будет описана реализация данного метода.
Стоит обратить внимание что в классе AnimalSound мы создали массив объектных ссылок типа Soud (интерфейсный тип) и поместили туда ссылки на объекты, реализующие данный интерфейс.
Программа генерирует следующий вывод:
И теперь посмотрим на классы Cow, Cat и Dog:
Как видно наши классы Cow, Cat и Dog которые имплементируют интерфейс Sound имеют каждый свою реализацию метода getSound().
И приведу еще один простой пример, того что класс может реализовывать несколько интерфейсов.\
Как видно из примера класс MaxMin реализует два интрефейса.
Интерфейс – это контракт, в рамках которого части программы, зачастую написанные разными людьми, взаимодействуют между собой и со внешними приложениями. Интерфейсы работают со слоями сервисов, безопасности, DAO и т.д. Это позволяет создавать модульные конструкции, в которых для изменения одного элемента не нужно трогать остальные.
Новички часто спрашивают, чем интерфейс отличается от абстрактного класса . Интерфейсы в Java компенсируют отсутствие множественного наследования классов. У класса-потомка может быть только один абстрактный класс-родитель, а вот интерфейсов класс может применять (имплементировать) сколько угодно.
Интерфейс на Java объявляют примерно так же, как и класс:
public interface MyInterface { void do_something (){ // ... } default void say_goodbye (String userName ) { System . out. println("Пока, " + userName+ "! Заходи ещё." ); } }В имплементирующем интерфейс классе должны быть реализованы все предусмотренные интерфейсом методы, за исключением методов по умолчанию.
Методы по умолчанию впервые появились в Java 8. Их обозначают модификатором default. В нашем примере это метод say_goodbye, реализация которого прописана прямо в интерфейсе. Дефолтные методы изначально готовы к использованию, но при необходимости их можно переопределять в применяющих интерфейс классах.
Функциональный интерфейс Java
Если у интерфейса только один абстрактный метод, перед нами функциональный интерфейс. Его принято помечать аннотацией @FunctionalInterface, которая указывает компилятору, что при обнаружении второго абстрактного метода в этом интерфейсе нужно сообщить об ошибке. Стандартных (default) методов у интерфейса может быть множество – в том числе принадлежащих классу java.lang.Object.
Как выглядит функциональный интерфейс на Java:
@FunctionalInterface public interface GaMechanic { void new_deck(); default int deal_cards(int num_of_players) { // тело метода } default int check_your_cards(int hand) { //... } default int battle(Card player1_Card, Card player2_Card) { //... } }
Функциональные интерфейсы появились в Java 8. Они обеспечили поддержку лямбда-выражений, использование которых делает код лаконичным и понятным:
Button. setOnAction(event - > // если происходит событие System . out. println("Обрабатываем нажатие кнопки." ));
В той же версии появились пакеты встроенных интерфейсов: java.util.function и java.util.stream.
Реализация интерфейсов классами Java
Допустим, есть интерфейс Edible, которым пользуются классы Fruit, Vegetable, Fish. Экземпляры этих классов можно создавать так:
Edible a1 = new Fruit ("Яблоко" , “Антоновка”); Edible a2 = new Fish ("Нерка слабосолёная" , “Тихий океан”, 150 );Хорошим тоном считается давать интерфейсам названия с окончанием -able/-ible - это показывает, что с объектами, имплементирующими интерфейс, можно что-то делать: Edible (можно есть), Moveable (можно двигать), Clickable (реагирует на клик) и т.д.
Обратите внимание на разницу в конструкторах: для фруктов задаём название и сорт, для рыбы – название, район вылова и вес порции в граммах. Но ссылки на оба объекта храним в переменных одного типа – «Съестное».
Интерфейсы и полиморфизм
Пример выше иллюстрирует один из трех основополагающих принципов ООП - полиморфизм. Мы раскрыли одно и то же явление - съедобность - через несколько классов, свойства и методы которых частично отличаются. Представление разных форм одного явления - это и есть полиморфизм. Если нужно, такую систему всегда можно расширить и скорректировать. В нашем случае - добавить новые виды съестного и методы их приготовления.
В Java полиморфизм можно реализовать через:
- наследование - с переопределением параметров и методов базового класса;
- абстрактные классы - шаблоны для раздельной реализации в разных классах;
- интерфейсы - для имплементации классами.
Интерфейс выручает в ситуации, когда при создании переменной мы не знаем, объект какого класса ей будет присвоен.