Что такое имплементация в java
Перейти к содержимому

Что такое имплементация в java

  • автор:

Что такое имплементация в java

Механизм наследования очень удобен, но он имеет свои ограничения. В частности мы можем наследовать только от одного класса, в отличие, например, от языка С++, где имеется множественное наследование.

В языке Java подобную проблему частично позволяют решить интерфейсы. Интерфейсы определяют некоторый функционал, не имеющий конкретной реализации, который затем реализуют классы, применяющие эти интерфейсы. И один класс может применить множество интерфейсов.

Чтобы определить интерфейс, используется ключевое слово interface . Например:

interface Printable

Данный интерфейс называется Printable. Интерфейс может определять константы и методы, которые могут иметь, а могут и не иметь реализации. Методы без реализации похожи на абстрактные методы абстрактных классов. Так, в данном случае объявлен один метод, который не имеет реализации.

Все методы интерфейса не имеют модификаторов доступа, но фактически по умолчанию доступ public , так как цель интерфейса — определение функционала для реализации его классом. Поэтому весь функционал должен быть открыт для реализации.

Чтобы класс применил интерфейс, надо использовать ключевое слово implements :

public class Program < public static void main(String[] args) < Book b1 = new Book("Java. Complete Referense.", "H. Shildt"); b1.print(); >> interface Printable < void print(); >class Book implements Printable < String name; String author; Book(String name, String author)< this.name = name; this.author = author; >public void print() < System.out.printf("%s (%s) \n", name, author); >>

В данном случае класс Book реализует интерфейс Printable. При этом надо учитывать, что если класс применяет интерфейс, то он должен реализовать все методы интерфейса, как в случае выше реализован метод print . Потом в методе main мы можем создать объект класса Book и вызвать его метод print. Если класс не реализует какие-то методы интерфейса, то такой класс должен быть определен как абстрактный, а его неабстрактные классы-наследники затем должны будут реализовать эти методы.

В тоже время мы не можем напрямую создавать объекты интерфейсов, поэтому следующий код не будет работать:

Printable pr = new Printable(); pr.print();

Одним из преимуществ использования интерфейсов является то, что они позволяют добавить в приложение гибкости. Например, в дополнение к классу Book определим еще один класс, который будет реализовывать интерфейс Printable:

class Journal implements Printable < private String name; String getName()< return name; >Journal(String name) < this.name = name; >public void print() < System.out.println(name); >>

Класс Book и класс Journal связаны тем, что они реализуют интерфейс Printable. Поэтому мы динамически в программе можем создавать объекты Printable как экземпляры обоих классов:

public class Program < public static void main(String[] args) < Printable printable = new Book("Java. Complete Reference", "H. Shildt"); printable.print(); // Java. Complete Reference (H. Shildt) printable = new Journal("Foreign Policy"); printable.print(); // Foreign Policy >> interface Printable < void print(); >class Book implements Printable < String name; String author; Book(String name, String author)< this.name = name; this.author = author; >public void print() < System.out.printf("%s (%s) \n", name, author); >> class Journal implements Printable < private String name; String getName()< return name; >Journal(String name) < this.name = name; >public void print() < System.out.println(name); >>

Интерфейсы в преобразованиях типов

Все сказанное в отношении преобразования типов характерно и для интерфейсов. Например, так как класс Journal реализует интерфейс Printable, то переменная типа Printable может хранить ссылку на объект типа Journal:

Printable p =new Journal("Foreign Affairs"); p.print(); // Интерфейс не имеет метода getName, необходимо явное приведение String name = ((Journal)p).getName(); System.out.println(name);

И если мы хотим обратиться к методам класса Journal, которые определены не в интерфейсе Printable, а в самом классе Journal, то нам надо явным образом выполнить преобразование типов: ((Journal)p).getName();

Методы по умолчанию

Ранее до JDK 8 при реализации интерфейса мы должны были обязательно реализовать все его методы в классе. А сам интерфейс мог содержать только определения методов без конкретной реализации. В JDK 8 была добавлена такая функциональность как методы по умолчанию . И теперь интерфейсы кроме определения методов могут иметь их реализацию по умолчанию, которая используется, если класс, реализующий данный интерфейс, не реализует метод. Например, создадим метод по умолчанию в интерфейсе Printable:

interface Printable < default void print()< System.out.println("Undefined printable"); >>

Метод по умолчанию — это обычный метод без модификаторов, который помечается ключевым словом default . Затем в классе Journal нам необязательно этот метод реализовать, хотя мы можем его и переопределить:

class Journal implements Printable < private String name; String getName()< return name; >Journal(String name) < this.name = name; >>

Статические методы

Начиная с JDK 8 в интерфейсах доступны статические методы — они аналогичны методам класса:

interface Printable < void print(); static void read()< System.out.println("Read printable"); >>

Чтобы обратиться к статическому методу интерфейса также, как и в случае с классами, пишут название интерфейса и метод:

public static void main(String[] args)

Приватные методы

По умолчанию все методы в интерфейсе фактически имеют модификатор public. Однако начиная с Java 9 мы также можем определять в интерфейсе методы с модификатором private . Они могут быть статическими и нестатическими, но они не могут иметь реализации по умолчанию.

Подобные методы могут использоваться только внутри самого интерфейса, в котором они определены. То есть к примеру нам надо выполнять в интерфейсе некоторые повторяющиеся действия, и в этом случае такие действия можно выделить в приватные методы:

public class Program < public static void main(String[] args) < Calculatable c = new Calculation(); System.out.println(c.sum(1, 2)); System.out.println(c.sum(1, 2, 4)); >> class Calculation implements Calculatable < >interface Calculatable < default int sum(int a, int b)< return sumAll(a, b); >default int sum(int a, int b, int c) < return sumAll(a, b, c); >private int sumAll(int. values) < int result = 0; for(int n : values)< result += n; >return result; > >

Константы в интерфейсах

Кроме методов в интерфейсах могут быть определены статические константы:

interface Stateable

Хотя такие константы также не имеют модификаторов, но по умолчанию они имеют модификатор доступа public static final , и поэтому их значение доступно из любого места программы.

public class Program < public static void main(String[] args) < WaterPipe pipe = new WaterPipe(); pipe.printState(1); >> class WaterPipe implements Stateable < public void printState(int n)< if(n==OPEN) System.out.println("Water is opened"); else if(n==CLOSED) System.out.println("Water is closed"); else System.out.println("State is invalid"); >> interface Stateable

Множественная реализация интерфейсов

Если нам надо применить в классе несколько интерфейсов, то они все перечисляются через запятую после слова implements:

interface Printable < // методы интерфейса >interface Searchable < // методы интерфейса >class Book implements Printable, Searchable < // реализация класса >

Наследование интерфейсов

Интерфейсы, как и классы, могут наследоваться:

interface BookPrintable extends Printable

При применении этого интерфейса класс Book должен будет реализовать как методы интерфейса BookPrintable, так и методы базового интерфейса Printable.

Вложенные интерфейсы

Как и классы, интерфейсы могут быть вложенными, то есть могут быть определены в классах или других интерфейсах. Например:

class Printer < interface Printable < void print(); >>

При применении такого интерфейса нам надо указывать его полное имя вместе с именем класса:

public class Journal implements Printer.Printable < String name; Journal(String name)< this.name = name; >public void print() < System.out.println(name); >>

Использование интерфейса будет аналогично предыдущим случаям:

Printer.Printable p =new Journal("Foreign Affairs"); p.print();

Интерфейсы как параметры и результаты методов

И также как и в случае с классами, интерфейсы могут использоваться в качестве типа параметров метода или в качестве возвращаемого типа:

public class Program < public static void main(String[] args) < Printable printable = createPrintable("Foreign Affairs",false); printable.print(); read(new Book("Java for impatients", "Cay Horstmann")); read(new Journal("Java Dayly News")); >static void read(Printable p) < p.print(); >static Printable createPrintable(String name, boolean option) < if(option) return new Book(name, "Undefined"); else return new Journal(name); >> interface Printable < void print(); >class Book implements Printable < String name; String author; Book(String name, String author)< this.name = name; this.author = author; >public void print() < System.out.printf("%s (%s) \n", name, author); >> class Journal implements Printable < private String name; String getName()< return name; >Journal(String name) < this.name = name; >public void print() < System.out.println(name); >>

Метод read() в качестве параметра принимает объект интерфейса Printable, поэтому в этот метод мы можем передать как объект Book, так и объект Journal.

Метод createPrintable() возвращает объект Printable, поэтому также мы можем возвратить как объект Book, так и Journal.

Foreign Affairs Java for impatients (Cay Horstmann) Java Dayly News

Интерфейс

Интерфейс это конструкция языка Java, в рамках которой принято описывать абстрактные публичные ( abstract public ) методы и статические константы ( final static ).

С помощью интерфейса можно указать, что именно должен выполнять класс его реализующий, но не как это делать. Способ реализации выбирает сам класс. Интерфейсы не способны сохранять данные состояния. Интерфейсы — это один из механизмов реализации принципа полиморфизма «один интерфейс, несколько методов».

Рассмотрим следующую картинку. У нас есть контракт (интерфейс), в котором описано какие действия должна выполнять мышка. Это например, клик по правой клавише и клик по левой. Разные производители мышки (классы), реализующие данный контракт (интерфейс), обязаны спроектировать мышки, у которых будут эти действия. Но как выглядят мышки, какие дополнительные опции будут иметь — все это решает сам производитель.

Что такое интерфейс фото

Интерфейсы, как и классы могут быть объявлены с уровнем доступа public или default .

Переменные интерфейса являются public static final по умолчанию и эти модификаторы необязательны при их объявлении. Например, в следующем примере объявлены переменные RIGHT , LEFT , UP , DOWN без каких-либо модификаторов. Но они будут public static final .

Все методы интерфейса являются public abstract и эти модификаторы тоже необязательны. Объявляемые методы не содержат тел, их объявления завершаются точкой с запятой:

public interface Moveable

Чтобы указать, что данный класс реализует интерфейс, в строке объявления класса указываем ключевое слово implements и имя интерфейса. Класс реализующий интерфейс должен содержать полный набор методов, определенных в этом интерфейсе. Но в каждом классе могут быть определены и свои методы. Например, следующий класс Transport реализует интерфейс Moveable . В нем реализованы методы moveRight() и moveLeft() интерфейса Moveable , и добавлены свои методы stop() , start():

public class Transport implements Moveable < public void moveRight() < System.out.println("Транспорт поворачивает вправо."); >public void moveLeft() < System.out.println("Транспорт поворачивает влево."); >public void stop() < System.out.println("Транспорт останавливается."); >public void start() < System.out.println("Транспорт стартует."); >>

Один интерфейс может быть реализован любым количеством классов. Например, в следующей схеме добавлены еще два класса Robot и Device , которые тоже реализуют интерфейс Moveable .

Один интерфейс может быть реализован любым количеством классов фото

Класс Robot из вышеуказанной схемы:

public class Robot implements Moveable < public void moveRight() < System.out.println("Робот поворачивает вправо."); >public void moveLeft() < System.out.println("Робот поворачивает влево."); >>

Если класс реализует интерфейс, но не полностью реализует определенные в нем методы, он должен быть объявлен как abstract .

Например, класс Device реализует только один метод интерфейса Moveable , поэтому он абстрактный:

public abstract class Device implements Moveable < public void moveRight() < System.out.println("Девайс поворачивает вправо."); >>

Тип интерфейса можно указывать при объявлении переменных, содержащих ссылки на объекты, классы которых реализуют этот интерфейс. Например, в следующем примере переменная moveable имеет тип Moveable , и указывает она на объект Transport . Но на основе интерфейсов нельзя порождать объекты. Например, в строке Moveable moveable1 = new Moveable() будет ошибка компиляции. При использовании переменной типа интерфейс, доступны только те члены класса, которые определены в этом интерфейсе. Например, нельзя вызвать метод start() , используя переменную moveable . А для переменной transport можно:

public class TransportDemo < public static void main(String[] args) < Moveable moveable = new Transport(); Transport transport = new Transport(); Moveable robot = new Robot(); //Moveable moveable1 = new Moveable(); // moveable.start(); moveable.moveRight(); moveable.moveLeft(); System.out.println(); transport.start(); transport.moveRight(); transport.moveLeft(); transport.stop(); System.out.println(); robot.moveLeft(); robot.moveRight(); >>

Один класс может реализовать любое количество интерфейсов. На следующей схеме показан класс Pickup , который реализует два интерфейса CargoAuto и PassengersAuto :

Один класс может реализовать любое количество интерфейсов фото

public interface CargoAuto
public interface PassengersAuto

Для указания того, что класс реализует несколько интерфейсов, после ключевого слова implements через запятую перечисляются нужные интерфейсы. Класс Pickup должен определить все методы реализуемых интерфейсов:

public class Pickup implements CargoAuto, PassengersAuto < public void transportCargo() < System.out.println("Везу груз"); >public void transportPassengers() < System.out.println("Везу пассажиров"); >>

2. Внутренние интерфейсы

Интерфейсы объявленные в классах или в других интерфейсах называются внутренние или вложенные. Например, интерфейс NestedIf определен внутри класса A:

public class A < public interface NestedIf < boolean isNotNegative(int x); >>

При обращении к интерфейсу NestedIf требуется указывать имя его внешнего класса A.NestedIf :

public class B implements A.NestedIf < public boolean isNotNegative(int x) < return x >= 0; > >
public class NestedIfDemo < public static void main(String[] args) < A.NestedIf nif = new B(); if (nif.isNotNegative(10)) < System.out.println("Число 10 не отрицательное."); >if (nif.isNotNegative(-12)) < System.out.println("Этo не будет выведено."); >> >

3. Расширение интерфейсов

Интерфейс может наследоваться от другого интерфейса через ключевое слово extends . Один интерфейс, в отличие от классов, может расширять несколько интерфейсов.

Например, интерфейс Football расширяет интерфейсы TVProgram и Sport . Класс FootballImpl , реализующий интерфейс Football , должен переопределить методы всех трех интерфейсов Football , TVProgram и Sport :

Расширение интерфейсов фото

public interface Sport
public interface Hockey extends Sport
public interface TVProgram
public interface Football extends Sport, TVProgram
public class FootballImpl implements Football < @Override public void setHomeTeam(String name) < System.out.println("Setting Home Team"); >@Override public void switchToChannel() < System.out.println("Switching to channel"); >@Override public void homeTeamScored(int points) < System.out.println("Scored"); >@Override public void setVisitingTeam(String name) < System.out.println("Setting visiting team"); >@Override public void visitingTeamScored(int points) < System.out.println("Visiting Team Scored"); >@Override public void endOfQuarter(int quarter) < System.out.println("End of quarter"); >>

4. Интерфейсы маркеры

Интерфейсы маркеры — это интерфейсы, у которых не определены ни методы, ни переменные. Реализация этих интерфейсов придает классу определенные свойства. Например, интерфейсы Cloneable и Serializable , отвечающие за клонирование и сохранение объекта в информационном потоке, являются интерфейсами маркерами. Если класс реализует интерфейс Cloneable , это говорит о том, что объекты этого класса могут быть клонированы.

5. Методы по умолчанию в интерфейсах

В JDK 8 в интерфейсы ввели методы по умолчанию — это методы, у которых есть реализация. Другое их название — методы расширения. Классы, реализующие интерфейсы, не обязаны переопределять такие методы, но могут если это необходимо. Методы по умолчанию определяются с ключевым словом default .

Интерфейс SomeInterface объявляет метод по умолчанию defaultMethod() с базовой реализацией:

public interface SomeInterface < default String defaultMethod() < return "Объект типа String по умолчанию"; >>

Класс SomeInterfaceImpl1 , реализующий этот интерфейс, не переопределяет метод defaultMethod() так можно.

public class SomeInterfaceImpl1 implements SomeInterface

А если класс SomeInterfaceImpl2 не устраивает реализация по умолчанию, он переопределяет этот метод:

public class SomeInterfaceImpl2 implements SomeInterface < @Override public String defaultMethod() < return "Другая символьная строка"; >>

Создаем два объекта классов SomeInterfaceImpl1 и SomeInterfaceImpl2 , и вызываем для каждого метод defaultMethod() . Для объекта класса SomeInterfaceImpl1 вызовется метод, реализованный в интерфейсе, а для объекта класса SomeInterfaceImpl2 его собственная реализация:

public class DefaultMethodDemo < public static void main(String[] args) < SomeInterface obj1 = new SomeInterfaceImpl1(); SomeInterface obj2 = new SomeInterfaceImpl2(); System.out.println(obj1.defaultMethod()); System.out.println(obj2.defaultMethod()); >>
Объект типа String по умолчанию Другая символьная строка

6. Статические методы интерфейса

В версии JDK 8, в интерфейсы добавлена еще одна возможность — определять в нем статические методы. Статические методы интерфейса, как и класса, можно вызывать независимо от любого объекта. Для вызова статического метода достаточно указать имя интерфейса и через точку имя самого метода.

public interface MyIf < int getNumber(); static int staticMethod() < return 0; >>
public class StaticMethodDemo < public static void main(String[] args) < MyIf obj1 = new MyIfImp(); System.out.println(obj1.getNumber()); System.out.println(MyIf.staticMethod()); >>

Что такое интерфейсы

Интерфейс — это публичный общепринятый контракт (соглашение), описывающий некоторое поведение. Для чего они нужны? Например, пусть у нас есть ссылка указывающая на список строк. Предположим этот в этот список в начале добавляется множество элементов, а затем, весь список обрабатывается один раз. Допустим мы решили последовать известной рекомендации и использовать класс ArrayList :

ArrayList list = new ArrayList<>();

Мы написали поведение программы в 100500 строк кода, где использовался этот список строк, и для оптимизации работы использовали методы специфичные для класса ArrayList . Например ensureCapacity(). Пока строки добавляются в конец списка, у нас всё прекрасно и быстро работает. Но вот у нас возникла потребность переориентировать нашу программу на немного другой вид работ, где строки добавляются преимущественно в начало списка. Для такого характера нагрузки гораздо больше подходит LinkedList . Но если мы захотим перевести свою программу из 100500 строк кода на рельсы LinkedList , то нам понадобится отыскать и убрать использование специфичных для ArrayList методов, возможно местами сильно меняя логику отдельных участков программы. Если бы мы использовали только те методы, которые имеются и в ArrayList , и в LinkedList , то нам бы этого делать не пришлось. Мы бы могли всего лишь изменить одну строчку кода — объявление списка:

LinkedList list = new LinkedList<>();

Мы можем заметить, что было бы удобней вынести объявление методов общих, для этих классов, в класс-предок, возможно абстрактный, например AbstractList . В этом случае мы могли бы объявить наш список так:

AbstractList list = new ArrayList<>();

И смогли бы быстро переключить реализацию вот так:

AbstractList list = new LinkedList<>();

Но в этом случае, классы, которые мы можем использовать в нашей программе, ограничены только потомками класса AbstractList , даже если если есть более подходящие под задачу классы, не являющиеся потомками AbstractList , но имеющие те же методы с тем же поведением. Как быть? Именно поэтому были изобретены интерфейсы. Интерфейс — это такое соглашение о наборе методов и их поведении, которое могут обязаться соблюдать совершенно не связанные классы, позволяя ссылаться на любой из них с помощью единой ссылки. Например так:

List list; list = new ArrayList<>(); list = new LinkedList<>(); list = new AnotherListClass<>();
  • Интерфейс это контракт (соглашение) о поведении.
  • Множество классов, даже не связанных отношением наследования, могут объявить, что они обязуются соблюдать этот контракт (имплементация интерфейса).
  • Вынесение описания поведения в отдельный интерфейс очень удобно, так как увеличивает гибкость кода, позволяя переключать реализацию (класс имплементирующий интерфейс) интерфейса, на классы не связанные отношением наследования.

Зачем нужны интерфейсы?

Зачем нужны интерфейсы? Зачем нужно наследование интерфейсов? Зачем нужен полиморфизм? Для тех, кто почитал и понял как делать интерфейсы, но не понял зачем.

Дисклеймер: В этой статье и последующих, нейминги классов и методов будут приведены на русском языке.

Когда речь заходит о принципах объектно-ориентированного программирования (ООП): полиморфизм, наследование и инкапсуляция, полезно приводить аналогии из реального мира. Большой плюс ООП в том, что мы в программе можем смоделировать часть реальной вселенной. Смоделируем семью Ивановых: Папа, Мама и мальчик Петя. От Папы Петя унаследовал привычку прихлюпывать когда пьет чай, а от Мамы он унаследовал привычку поджимать губы во время чтения. Если попытаться реализовать эту ситуацию в программу, то у нас получиться три класса:

 class Папа class Мама class Петя 

У Папы и Мамы есть привычки, которые нужно передать Пете. Привычки — это какие-то действия — так что лучше всего реализовать их в программном мире как методы: Сначала Папа:

 class Папа < public void прихлюпывать() < System.out.println("Хлюп"); >> 

Теперь Мама:

 class Мама < public void поджимать() < System.out.println("Поджать губки"); >> 

Если речь идет о наследовании, то логично написать код так:

 class Петя extends Папа, Мама < @Override public void прихлюпывать() < System.out.println("Хлюп"); >@Override public void поджимать() < System.out.println("Поджать губки"); >> 

Зачем нужны интерфейсы? - 1

То есть унаследовать Петю от Папы и Мамы одновременно. Если так написать, то компилятор будет ругаться, потому что в Java нельзя реализовать множественное наследование классов. К слову, в С++ можно, а вот в Java нельзя, потому что с множественным наследованием могут возникнуть большие проблемы: подробно пишут в интернете. Что бы обойти это «нельзя», в Java есть интерфейсы. И для привычек мы придумаем свой интерфейс. Даже два: Выглядеть они будут так:

 public interface ПривычкиПапы < public void прихлюпывать(); >public interface ПривычкиМамы

В интерфейсе мы только описали привычки, но не описали что они конкретно делают, потому что конкретную реализацию мы пропишем в классах. Сначала отдадим Папе и Маме их законные привычки.

 class Папа implements ПривычкиПапы < @Override public void прихлюпывать() < System.out.println("Хлюп"); >> class Мама implements ПривычкиМамы < @Override public void поджимать() < System.out.println("Поджать губки"); >> 

И теперь, абсолютно легально можно передать Пете привычки от Папы и Мамы одновременно

 class Петя implements ПривычкиПапы, ПривычкиМамы < @Override public void прихлюпывать() < System.out.println("Хлюп"); >@Override public void поджимать() < System.out.println("Поджать губки"); >> 

Зачем нужны интерфейсы? - 2

То есть множественная реализация (чаще говорят имплементация) в Java вполне возможна. Смысл интерфейсов теперь должен быть понятен – в Java с помощью интерфейсов можно реализовать множественное наследование. Если развивать ситуацию дальше, например: ведь у Папы и Мамы наверняка есть привычки, которые они не передали Пете, да и у Пети могут быть свои личные привычки. Как эту жизненную Санта-Барбару перенести в плоскость Java вы узнаете в следующих сериях. Это не единственный пример для понимания интерфейсов.Если не читали следующие статьи, то обязательно прочтите: Интерфейсы в Java (если не открыто, можно выйти из профиля или прочитать в режиме — инкогнито) Для чего в Java нужны интерфейсы — тут реализуйте все примеры из статьи и поизменяйте методы и в интерфейсах и в калассах: наименования методов, сигнатуры (то что метод принимает на вход), типы вывода методов. Разберитесь самостоятельно: — с разницей при имплементации интерфейса с классом и абстрактным классом; — дефолтными методами.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *