Есть ли больше для интерфейса, чем правильные методы

Итак, скажем, у меня есть этот интерфейс:

public interface IBox
{
   public void setSize(int size);
   public int getSize();
   public int getArea();
  //...and so on
}

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

public class Rectangle implements IBox
{
   private int size;
   //Methods here
}

Если бы я хотел использовать интерфейс IBox, я не могу создать его экземпляр, способ:

public static void main(String args[])
{
    Ibox myBox=new Ibox();
}

правильно? Поэтому я действительно должен был сделать это:

public static void main(String args[])
{
    Rectangle myBox=new Rectangle();
}

Если это правда, то единственной целью интерфейсов является убедиться, что класс, реализующий интерфейс, получил в нем правильные методы, как описано в интерфейсе? Или есть ли другое использование интерфейсов?

Ответ 1

Интерфейсы - это способ сделать ваш код более гибким. Что вы делаете, так это:

Ibox myBox=new Rectangle();

Затем, позже, если вы решите, что хотите использовать другой вид коробки (может быть, есть еще одна библиотека, с лучшим видом коробки), вы переключите свой код на:

Ibox myBox=new OtherKindOfBox();

Как только вы привыкнете к этому, вы найдете его отличным (на самом деле необходимым) способом работы.

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

myBox.close()

(предполагая, что IBox имеет метод close()), даже если фактический класс myBox изменяется в зависимости от того, в каком поле вы находитесь на итерации.

Ответ 2

Что делает интерфейсы полезными, не тот факт, что "вы можете изменить свое мнение и использовать другую реализацию позже и только изменить одно место, где создается объект". Это не проблема.

Реальная точка уже находится в имени: они определяют интерфейс, который любой может реализовать, чтобы использовать весь код, который работает на этом интерфейсе. Лучшим примером является java.util.Collections, который предоставляет всевозможные полезные методы, которые работают исключительно на интерфейсах, таких как sort() или reverse() для List. Дело в том, что теперь этот код можно использовать для сортировки или реверсии любого класса, который реализует интерфейсы List, а не только ArrayList и LinkedList, а также классы, которые вы пишете сами, которые могут быть реализованы так, как люди, которые пишут java.util.Collections, никогда не представляли себе.

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

Еще одно распространенное использование интерфейсов - для обратных вызовов. Например, java.swing.table.TableCellRenderer, который позволяет вам влиять на то, как таблица Swing отображает данные в определенном столбце. Вы реализуете этот интерфейс, передаете экземпляр в JTable, и в какой-то момент во время рендеринга таблицы ваш код будет вызван, чтобы сделать свой материал.

Ответ 3

Одно из многих применений, которое я прочитал, - это то, где его сложно использовать без использования нескольких наследований-интерфейсов в Java:

class Animal
{
void walk() { } 
....
.... //other methods and finally
void chew() { } //concentrate on this
} 

Теперь представьте случай, когда:

class Reptile extends Animal 
{ 
//reptile specific code here
} //not a problem here

но

class Bird extends Animal
{
...... //other Bird specific code
} //now Birds cannot chew so this would a problem in the sense Bird classes can also call chew() method which is unwanted

Лучше спроектировать будет:

class Animal
{
void walk() { } 
....
.... //other methods 
} 

Животные не имеют метода chew() и вместо этого помещаются в интерфейс как:

interface Chewable {
void chew();
}

и класс Рептилий реализует это, а не Птицы (так как птицы не могут пережевывать):

class Reptile extends Animal implements Chewable { } 

и индейка птиц просто:

class Bird extends Animal { }

Ответ 4

Целью интерфейсов является полиморфизм, a.k.a. замена типа. Например, учитывая следующий метод:

public void scale(IBox b, int i) {
   b.setSize(b.getSize() * i);
}

При вызове метода scale вы можете указать любое значение, имеющее тип, реализующий интерфейс IBox. Другими словами, если Rectangle и Square реализуют IBox, вы можете указать либо Rectangle, либо Square везде, где ожидается IBox.

Ответ 5

Интерфейсы позволяют статически типизированные языки поддерживать полиморфизм. Объектно-ориентированный пурист настаивал бы на том, что язык должен обеспечивать наследование, инкапсуляцию, модульность и полиморфизм, чтобы быть полнофункциональным объектно-ориентированным языком. В динамически типизированных или утиных типизированных языках (например, Smalltalk) полиморфизм тривиален; однако в статически типизированных языках (например, Java или С#) полиморфизм далек от тривиального (на самом деле, на первый взгляд, он не согласен с понятием сильной типизации).

Позвольте мне продемонстрировать:

На динамически типизированном (или утином) языке (например, Smalltalk) все переменные являются ссылками на объекты (не что иное и не более.) Итак, в Smalltalk я могу это сделать:

|anAnimal|    
anAnimal := Pig new.
anAnimal makeNoise.

anAnimal := Cow new.
anAnimal makeNoise.

Этот код:

  • Объявляет локальную переменную, называемую anAnimal (обратите внимание, что мы НЕ указываем TYPE переменной - все переменные являются ссылками на объект, не более и не менее.)
  • Создает новый экземпляр класса с именем "Pig"
  • Назначает новый экземпляр Pig для переменной anAnimal.
  • Отправляет сообщение makeNoise свиньи.
  • Повторяет все это с помощью коровы, но присваивает ей ту же самую точную переменную, что и Pig.

Тот же Java-код будет выглядеть примерно так (делая предположение, что Duck and Cow являются подклассами Animal:

Animal anAnimal = new Pig();
duck.makeNoise();

anAnimal = new Cow();
cow.makeNoise();

Это все хорошо и хорошо, пока мы не представим класс Vegetable. Овощи имеют то же поведение, что и животное, но не все. Например, как животные, так и овощи могут расти, но, очевидно, овощи не производят шума, и животные не могут быть собраны.

В Smalltalk мы можем написать это:

|aFarmObject|
aFarmObject := Cow new.
aFarmObject grow.
aFarmObject makeNoise.

aFarmObject := Corn new.
aFarmObject grow.
aFarmObject harvest.

Это хорошо работает в Smalltalk, потому что он утиный (если он ходит как утка, а quacks - как утка - это утка.) В этом случае, когда сообщение отправляется объекту, поиск выполняется в списке методов приемника, и если найден метод сопоставления, он вызывается. Если нет, то возникает какое-то исключение NoSuchMethodError, но все это делается во время выполнения.

Но на Java, статически типизированный язык, какой тип мы можем назначить нашей переменной? Кукуруза должна унаследовать от Vegetable, чтобы поддержать рост, но не может унаследовать от Animal, потому что она не шумит. Корова должна наследовать от Animal для поддержки makeNoise, но не может наследовать от Vegetable, потому что она не должна внедрять урожай. Похоже, нам нужно многократное наследование - способность наследовать более чем один класс. Но это оказывается довольно сложной функцией языка из-за всех появляющихся красных случаев (что происходит, когда несколько параллельных суперклассов реализуют один и тот же метод?) И т.д.)

Вперед интерфейсы...

Если мы создадим классы животных и овощей, каждый из которых будет реализовывать Growable, мы можем заявить, что наша Корова - это Животное, а наша кукуруза - Овощи. Мы также можем заявить, что как животные, так и овощи растут. Это позволяет нам писать это, чтобы вырастить все:

List<Growable> list = new ArrayList<Growable>();
list.add(new Cow());
list.add(new Corn());
list.add(new Pig());

for(Growable g : list) {
   g.grow();
}

И это позволяет нам делать это, чтобы сделать шумы животных:

List<Animal> list = new ArrayList<Animal>();
list.add(new Cow());
list.add(new Pig());
for(Animal a : list) {
  a.makeNoise();
}

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

|aFarmObject|
aFarmObject := Corn new.
aFarmObject makeNoise. // No compiler error - not checked until runtime.

Статически типизированные языки обеспечивают гораздо лучшее "программирование по контракту", потому что они поймают два типа ошибок ниже во время компиляции:

// Compiler error: Corn cannot be cast to Animal.
Animal farmObject = new Corn();  
farmObject makeNoise();

-

// Compiler error: Animal doesn't have the harvest message.
Animal farmObject = new Cow();
farmObject.harvest(); 

Итак... чтобы подвести итог:

  • Реализация интерфейса позволяет вам указать, какие объекты могут выполнять объекты (взаимодействие) и наследование классов позволяет вам указать, как это должно быть сделано (реализация).

  • Интерфейсы дают нам много преимуществ "истинного" полиморфизма, не жертвуя проверкой типа компилятора.

Ответ 6

Обычно интерфейсы определяют интерфейс, который вы должны использовать (как сказано в названии;-)). Пример


public void foo(List l) {
   ... do something
}

Теперь ваша функция foo принимает ArrayList s, LinkedList s,... не только один тип.

Самое важное в Java - это то, что вы можете реализовать несколько интерфейсов, но вы можете расширить только класс ONE! Образец:


class Test extends Foo implements Comparable, Serializable, Formattable {
...
}
возможно, но

class Test extends Foo, Bar, Buz {
...
}
не является!

Ваш код выше также может быть: IBox myBox = new Rectangle();. Важно то, что myBox ТОЛЬКО содержит методы/поля из IBox, а не (возможно существующие) другие методы из Rectangle.

Ответ 7

вы могли бы сделать

Ibox myBox = new Rectangle();

таким образом вы используете этот объект как Ibox, и вам все равно, что его действительно Rectangle.

Ответ 8

Я думаю, вы понимаете все интерфейсы, но вы еще не представляете ситуации, в которых интерфейс полезен.

Если вы создаете экземпляр, используя и освобождая объект в узком пространстве (например, в рамках одного вызова метода), интерфейс действительно ничего не добавляет. Как вы заметили, известен конкретный класс.

В тех случаях, когда интерфейсы полезны, необходимо создать объект в одном месте и вернуть вызывающему абоненту, который может не заботиться о деталях реализации. Позвольте изменить пример IBox на форму. Теперь у нас могут быть реализации Shape, такие как Rectangle, Circle, Triangle и т.д. Реализации методов getArea() и getSize() будут совершенно разными для каждого конкретного класса.

Теперь вы можете использовать factory с различными методами createShape (params), которые возвратят соответствующую форму в зависимости от переданных параметров. Очевидно, что factory будет знать, какой тип Shape создается, но вызывающему абоненту не придется заботиться о том, является ли он кругом, квадратом или так далее.

Теперь представьте, что у вас есть множество операций, которые вы должны выполнять на своих фигурах. Возможно, вам нужно отсортировать их по области, установить их все на новый размер, а затем отобразить их в пользовательском интерфейсе. Все формы создаются с помощью factory, а затем могут быть легко переданы классам Сортировщик, Sizer и Display. Если вам нужно добавить класс hexagon некоторое время в будущем, вам не нужно ничего менять, кроме factory. Без интерфейса добавление другой формы становится очень грязным процессом.

Ответ 9

Интерфейсы

В разработке программного обеспечения существует ряд ситуаций, когда важно, чтобы разрозненные группы программистов согласились на "контракт", в котором говорится, как взаимодействует их программное обеспечение. Каждая группа должна иметь возможность писать свой код без каких-либо знаний о том, как написан другой код группы. Вообще говоря, интерфейсы являются такими контрактами.

Например, представьте себе футуристическое общество, где управляемые компьютером роботизированные автомобили перевозят пассажиров по улицам города без человека. Производители автомобилей пишут программное обеспечение (Java, конечно), которое управляет автомобильным остановом, запускает, ускоряется, поворачивается налево и т.д. Другая промышленная группа, разработчики электронных накладных инструментов, создает компьютерные системы, которые получают данные о местоположении GPS (Global Positioning System) и беспроводную передачу условий движения и используют эту информацию для управления автомобилем.

Автопроизводители должны опубликовать стандартный отраслевой интерфейс, в котором подробно изложены способы, которые можно использовать для перемещения автомобиля (любой автомобиль от любого производителя). Затем производители руководств могут написать программное обеспечение, которое вызывает методы, описанные в интерфейсе, для управления автомобилем. Ни одна промышленная группа не должна знать, как реализуется другое групповое программное обеспечение. На самом деле, каждая группа считает свое программное обеспечение весьма проприетарным и оставляет за собой право изменять его в любое время, если оно продолжает придерживаться опубликованного интерфейса.

Ответ 10

Отличный пример использования интерфейсов в структуре Collections. Если вы пишете функцию, которая принимает List, то не имеет значения, проходит ли пользователь в Vector или ArrayList или HashList или что-то еще. И вы можете передать это List любой функции, требующей интерфейса Collection или Iterable.

Это делает возможными такие функции, как Collections.sort(List list), независимо от того, как реализован List.

Ответ 11

Вот почему Factory Шаблоны и другие шаблоны создания настолько популярны в Java. Вы правы, что без них Java не предоставляет механизм из ящика для простой абстракции инстанцирования. Тем не менее, вы получаете абстрагирование везде, где вы не создаете объект в своем методе, который должен быть большей частью вашего кода.

В целом, я вообще рекомендую людям не следовать механизму "IRealname" для именования интерфейсов. То, что вещь Windows/COM, которая помещает одну ногу в могилу венгерской нотации, и на самом деле не нужна (Java уже строго типизирована, и вся суть наличия интерфейсов заключается в том, чтобы сделать их как можно более неотличимыми от типов классов).

Ответ 12

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

Это становится немного понятнее, если интерфейсы названы -able. например.

public interface Saveable {
....

public interface Printable {
....

и т.д.. (Схемы именования не всегда работают, например, я не уверен, что Boxable здесь подходит)

Ответ 13

единственная цель интерфейсов - убедиться, что класс, реализующий интерфейс, получил в нем правильные методы, как описано в интерфейсе? Или есть ли другое использование интерфейсов?

Я обновляю ответ с новыми функциями интерфейса, которые появились с версией java 8.

На странице документации оракала сводка интерфейса:

Объявление интерфейса может содержать

  • сигнатуры методов
  • методы по умолчанию
  • статические методы
  • постоянные определения.

Единственными методами, которые имеют реализации, являются стандартные и статические методы.

Использование интерфейса:

  • Чтобы определить контракт
  • Чтобы связать несвязанные классы с возможностями (например, классы, реализующие интерфейс Serializable, могут иметь или не иметь никакой связи между ними, кроме реализации этого интерфейса
  • Чтобы обеспечить взаимозаменяемую реализацию например. стратегический шаблон
  • Методы по умолчанию позволяют добавлять новые функциональные возможности в интерфейсы ваших библиотек и обеспечивать двоичную совместимость с кодом, написанным для более старых версий этих интерфейсов.
  • Упорядочить вспомогательные методы в ваших библиотеках со статическими методами (вы можете сохранять статические методы, специфичные для интерфейса в одном интерфейсе, а не в отдельном классе)

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

В чем разница между интерфейсом и абстрактным классом?

Как я должен объяснить разницу между интерфейсом и абстрактным классом?

Посмотрите документацию для понимания новых функций, добавленных в java 8: методы по умолчанию и статические методы.

Ответ 14

Цель интерфейсов - абстракция или отключение от реализации.

Если вы вводите абстракцию в своей программе, вы не заботитесь о возможных реализациях. Вас интересует, что он может сделать, а не как, и вы используете interface, чтобы выразить это на Java.

Ответ 15

ПОЧЕМУ ИНТЕРФЕЙС Это начинается с собаки. В частности, мопс.

Мопс имеет различное поведение:

public class Pug { 
private String name;
public Pug(String n) { name = n; } 
public String getName() { return name; }  
public String bark() { return  "Arf!"; } 
public boolean hasCurlyTail() { return true; } }

И у вас есть Лабрадор, у которого также есть набор способов поведения.

public class Lab { 
private String name; 
public Lab(String n) { name = n; } 
public String getName() { return name; } 
public String bark() { return "Woof!"; } 
public boolean hasCurlyTail() { return false; } }

Мы можем сделать некоторые мопсы и лаборатории:

Pug pug = new Pug("Spot"); 
Lab lab = new Lab("Fido");

И мы можем ссылаться на их поведение:

pug.bark() -> "Arf!" 
lab.bark() -> "Woof!" 
pug.hasCurlyTail() -> true 
lab.hasCurlyTail() -> false 
pug.getName() -> "Spot"

Скажем, я управляю собачьим питомником, и мне нужно следить за всеми собаками, которые я живу. Я должен хранить мои мопсы и лабрадоры в отдельных массивах:

public class Kennel { 
Pug[] pugs = new Pug[10]; 
Lab[] labs = new Lab[10];  
public void addPug(Pug p) { ... } 
public void addLab(Lab l) { ... } 
public void printDogs() { // Display names of all the dogs } }

Но это явно не оптимально. Если я хочу разместить несколько пуделей, я должен изменить свое определение Kennel, чтобы добавить массив пуделей. На самом деле, мне нужен отдельный массив для каждого вида собаки.

Insight: оба мопса и лабрадоры (и пудели) - это виды собак, и у них одинаковый набор поведения. То есть, мы можем сказать (для целей этого примера), что все собаки могут лаять, иметь имя и иметь или не иметь кудрявый хвост. Мы можем использовать интерфейс, чтобы определить, что могут делать все собаки, но оставьте его до конкретных типов собак, чтобы реализовать эти конкретные поведения. Интерфейс говорит, что "вот что делают все собаки", но не говорит, как выполняется каждое поведение.

public interface Dog 
{
public String bark(); 
public String getName(); 
public boolean hasCurlyTail(); }

Затем я немного изменяю классы Pug и Lab для реализации поведения Dog. Мы можем сказать, что мопс - это собака, а лаборатория - собака.

public class Pug implements Dog {
// the rest is the same as before } 

public class Lab implements Dog { 
// the rest is the same as before 
}

Я все еще могу создать Pugs и Labs, как я это делал ранее, но теперь у меня также есть новый способ сделать это:

Dog d1 = new Pug("Spot"); 
Dog d2 = new Lab("Fido");

Это говорит о том, что d1 - это не только Собака, это, в частности, Мопс. И d2 также является Собакой, в частности Лабораторией. Мы можем ссылаться на поведение, и они работают по-прежнему:

d1.bark() -> "Arf!" 
d2.bark() -> "Woof!" 
d1.hasCurlyTail() -> true 
d2.hasCurlyTail() -> false 
d1.getName() -> "Spot"

Здесь, где вся дополнительная работа окупается. Класс питомника стал намного проще. Мне нужен только один массив и один метод addDog. Оба будут работать с любым объектом, который является собакой; то есть объекты, которые реализуют интерфейс Dog.

public class Kennel {
Dog[] dogs = new Dog[20]; 
public void addDog(Dog d) { ... } 
public void printDogs() {
// Display names of all the dogs } }

Здесь, как его использовать:

Kennel k = new Kennel(); 
Dog d1 = new Pug("Spot"); 
Dog d2 = new Lab("Fido"); 
k.addDog(d1); 
k.addDog(d2); 
k.printDogs();

Последняя инструкция отобразится: Spot Fido

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

Ответ 16

Если у вас есть CardboardBox и HtmlBox (оба из которых реализуют IBox), вы можете передать оба из них любому методу, который принимает IBox. Несмотря на то, что оба они очень разные и не полностью взаимозаменяемы, методы, которые не заботятся о "открытии" или "изменении размера", могут по-прежнему использовать ваши классы (возможно, потому, что они заботятся о том, сколько пикселей необходимо для отображения чего-либо на экране).

Ответ 17

Интерфейсы, в которых фетатура добавлена ​​в java для разрешения множественного наследования. Разработчики Java хотя и поняли, что наличие множественного наследования было "опасной" особенностью, поэтому возникла идея интерфейса.

множественное наследование опасно, потому что у вас может быть класс вроде следующего:


class Box{
    public int getSize(){
       return 0;
    }
    public int getArea(){
       return 1;
    }

}

class Triangle{
    public int getSize(){
       return 1;
    }
    public int getArea(){
       return 0;
    }

}

class FunckyFigure extends Box, Triable{
   // we do not implement the methods we will used the inherited ones
}

Каким будет метод, который следует вызывать, когда мы используем


   FunckyFigure.GetArea(); 

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