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

159

Допустим, у меня есть этот интерфейс:

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();
}

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

Нажмите Upvote
источник
2
Помните, что интерфейсы не являются специфическими для Java. Все языки ООП имеют их в той или иной форме, хотя не всегда так явно определены, как Java.
Herms
2
Технически, все строго типизированные языки ООП имеют их в той или иной форме. Нетипизированные, или утиные, языки не имеют схожей концепции.
Джаред
1
@Jared Разве вы не путаете строгую типизацию со статической, а "нетипизированную" с динамически типизированной?
eljenso
Полиморфизм может быть достигнут и через интерфейсы. Проверьте последний раздел этой страницы codenuggets.com/2014/06/20/java-interface
Джефф

Ответы:

143

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

Ibox myBox=new Rectangle();

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

Ibox myBox=new OtherKindOfBox();

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

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

myBox.close()

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

morgancodes
источник
54
В этом ответе нет ничего исключительного для интерфейсов Java . То же самое относится и к абстрактным классам, или даже к конкретным. Я ожидаю хорошего ответа, упомянув о возможности реализации нескольких интерфейсов, и когда / почему это будет полезно.
Rogério
16
Как это было выбрано в качестве ответа? Это краткое описание того, почему полиморфизм полезен, но, как сказано выше, я ожидал лучшего объяснения нескольких интерфейсов и, что еще более важно, когда уместно использовать интерфейс против абстрактного класса.
trevorkavanaugh
2
Это очень мало связано с объяснением интерфейсов и всего, что связано с основами полиморфизма
A-Developer-Has-No-Name
123

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

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

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

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

Майкл Боргвардт
источник
9
Это довольно хороший ответ, мне нравится, когда вы приводите примеры из классов пакетов Java ...
Owais Qureshi
1
Мне понравилосьyou can write code that operates on well-known interfaces, or interfaces you define
Маниш Кумар
Подождите минуту ... Что делает интерфейсы полезными не [возможность использовать любую реализацию, которая вам нравится], а скорее [возможность использовать любую реализацию, которая вам нравится]? Упоминание противоположного случая - довольно хороший момент.
Powerslave
4
@Powerslave: перефразируйте это больше как то, что делает интерфейсы полезными, это не [возможность писать код, в котором вам нужно изменить только одну строку при изменении имплементации], а скорее [возможность писать код, в котором вы даже не определяете реализацию при все].
Майкл Боргвардт
@MichaelBorgwardt Звучит намного лучше. :) Спасибо за разъяснение!
Powerslave
119

Одно из многих применений, которые я прочитал, это где трудно без интерфейсов с множественным наследованием в 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 
} 

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

interface Chewable {
void chew();
}

и пусть класс Reptile реализует это, а не Birds (поскольку Birds не может жевать):

class Reptile extends Animal implements Chewable { } 

а в случае с птицами просто

class Bird extends Animal { }
peevesy
источник
6
@ЧЕБУРАШКА И плохое наименование. Если Reptile«жует», то не «жует». Соглашение (иногда) об именовании интерфейсов Whateverable должно применяться только там, где это имеет смысл. Наименование интерфейса Predatorбудет более уместным здесь.
Powerslave
6
@Powerslave Это правильно imho, рептилия "умеет жевать" / "жевать". Ястреб - это хищник, но он все еще не способен жевать ... просто придирчивость, но "жевательный" может быть лучше определен в документации интерфейса.
Madmenyo
Превосходно .. объяснил очень хорошо. Спасибо..!
Gurusinghe
1
Я не понимаю Все, что кажется, - это то, что интерфейсы - это хороший способ убедиться, что вы реализуете одни и те же методы в каждом классе, который его реализует (например, так что это мешает мне сделать глупый выбор класса Bird с run () и классом Dog с runn () - они все одинаковые). Но, уделив внимание тому, чтобы у моих классов были одинаковые форматы / структуры методов, разве я не смог бы добиться того же? Похоже, что интерфейсы просто гарантируют, что программист не забывчив. Кроме того, интерфейсы, кажется, не экономят мое время; Мне все еще нужно определить метод в каждом классе, который его реализует.
Алекс Г
@AlexG - я сказал, что один из многих использует чувак :) Более того, мы едва поцарапали поверхность, чтобы ответить на вопрос простым способом!
peevesy
47

Назначение интерфейсов - полиморфизм , или замена типа . Например, дан следующий метод:

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

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

Apocalisp
источник
8
Почему цель полиморфизма интерфейсов, если я уже могу достичь этого в Java с помощью подклассов и переопределения методов?
eljenso
1
Это то же самое, за исключением того, что интерфейсы должны пропускать любую реализацию. Поэтому классы могут реализовывать более одного интерфейса.
Apocalisp
4
Эй, я никогда не говорил, что у Java была какая-то концептуальная целостность. Подстановка типов является целью всех подтипов. У Java бывает более одного механизма подтипов, но ни один из них не особенно хорош.
Apocalisp
1
Я никогда ничего не говорил о концептуальной целостности. Но давайте двигаться дальше. Если вы можете масштабировать каждый IBox с помощью вашего метода, разве это не должна быть операция, объявленная в IBox: IBox.scale (int)?
eljenso
1
Мы не хотим связывать Integer с IBox, поэтому мы не делаем его методом для Integer. И число методов интерфейса определяется последовательностью и связностью выражаемой им абстракции, а не тем, насколько громоздким было бы его реализовать. В любом случае, спасибо за ваши ответы Апо.
eljenso
33

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

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

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

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

anAnimal := Cow new.
anAnimal makeNoise.

Этот код:

  1. Объявляет локальную переменную с именем anAnimal (обратите внимание, что мы НЕ УКАЗЫВАЕМ ТИП переменной - все переменные являются ссылками на объект, не больше и не меньше.)
  2. Создает новый экземпляр класса с именем "Свинья"
  3. Назначает этот новый экземпляр Pig переменной anAnimal.
  4. Посылает сообщение makeNoiseсвинье.
  5. Повторяет все это, используя корову, но назначая ее той же самой переменной, что и Свинья.

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

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

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

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

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

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

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

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

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

Вдоль интерфейсов ...

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

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(); 

Итак .... подведем итог

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

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

Джаред
источник
2
Это текст моего ответа на другой вопрос: stackoverflow.com/questions/379282/… . Но они связаны ответы.
Джаред
2
Итак, могу я спросить, как язык, напечатанный на утке, различает Animal.water () (который, как говорил хамский фермер, говорит, что для этого нужна утечка), и Plant.water (), который он использует для полива растений. Двусмысленность - враг. Любое количество многословия, необходимое для преодоления двусмысленности, является приемлемым IMO.
Билл К
1
Да ... неоднозначность - это название игры с языками, написанными на утке. Работая профессионально на языке утки, нередко можно видеть элементы (методы и переменные) с именами длиной от 50 до 100 символов.
Джаред
1
Другим большим недостатком языков с утиной типизацией является невозможность программного рефакторинга на основе статического анализа - попробуйте запросить изображение Smalltalk для списка всех вызывающих методов вашего метода printString ... вы получите список всех вызывающих элементов для всех методов printString. ...
Джаред
... потому что вызывающая сторона Automobile # printString не может быть программно отделена от вызывающей стороны NearEarthOrbit # printString.
Джаред
9

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


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

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

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


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

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

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

Йоханнес Вайс
источник
1
«Список» должен быть членом интерфейса?
Нажмите Upvote
1
Список - это интерфейс в библиотеке коллекций Java.
rmeador
Список - это интерфейс в стандартной библиотеке Java ( java.sun.com/javase/6/docs/api/java/util/List.html ). Он просто использует это, чтобы проиллюстрировать свою точку зрения.
Майкл Майерс
6

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

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

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

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

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

Ickster
источник
6

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

Ibox myBox = new Rectangle();

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

IAdapter
источник
Это значит, что мы могли бы написать так ?! > Rectangle inst = new Rectangle ();
Dr.jacky
@ Mr.Hyde Если позже вы захотите добавить, у Squareвас возникнет проблема .... если вы попытаетесь сделать это без интерфейсов, вы не сможете гарантировать это Squareи Rectangleиспользовать те же методы ... это может привести к кошмару, когда у вас большая база кода ... Помните, что интерфейсы определяют шаблон.
Каньон Колоб
6

ПОЧЕМУ ИНТЕРФЕЙС ??????

Начинается с собаки. В частности, мопс .

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

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 } }

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

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

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

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

Ниранджан Курамбхатти
источник
@niranjan kurambhatti Я могу сделать все классы, чтобы расширить собаку, но все же почему интерфейс?
Джива
3

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

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

койка
источник
3

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

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

Кристофер Смит
источник
1
Вы путаете строгую типизацию со статической.
eljenso
3

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

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

public interface Saveable {
....

public interface Printable {
....

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

Брайан Агнью
источник
3

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

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

Со страницы документации оракула на резюме интерфейса :

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

  1. подписи метода
  2. методы по умолчанию
  3. статические методы
  4. постоянные определения.

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

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

  1. Определить договор
  2. Связать несвязанные классы с возможностями (например, классы, реализующие Serializableинтерфейс, могут иметь или не иметь какое-либо отношение между ними, кроме реализации этого интерфейса
  3. Обеспечить взаимозаменяемую реализацию, например, шаблон стратегии
  4. Методы по умолчанию позволяют добавлять новые функциональные возможности к интерфейсам ваших библиотек и обеспечивать двоичную совместимость с кодом, написанным для более старых версий этих интерфейсов.
  5. Организуйте вспомогательные методы в своих библиотеках с помощью статических методов (вы можете хранить статические методы, специфичные для интерфейса, в одном интерфейсе, а не в отдельном классе)

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

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

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

Взгляните на страницу документации, чтобы понять новые функции, добавленные в Java 8: стандартные методы и статические методы .

Равиндра Бабу
источник
Я удалил тег java-8, так как вопрос ничего не задавал о java-8 (и фактически задавался задолго до java-8). Теги для вопросов, а не для ответов.
Тагир Валеев
2

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

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

eljenso
источник
Целью всего структурного программирования является абстракция. Почему вы говорите, что целью интерфейсов является абстракция, так как я могу достичь того же самого, используя обобщенные элементы и композицию классов?
Apocalisp
1
Если все структурированное программирование является абстракцией (ваша заявка), то интерфейсы являются абстракциями в этой абстракции.
eljenso
1

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

Тодд Р
источник
1

Интерфейсы, где в 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(); 

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

Мандель
источник
Возможно, вы захотите провести различие между множественным наследованием реализации и множественным наследованием интерфейса в своем ответе, иначе это может привести к путанице.
eljenso
0

Вот мое понимание преимуществ интерфейса. Поправь меня, если я ошибаюсь. Представьте, что мы разрабатываем ОС, а другая команда разрабатывает драйверы для некоторых устройств. Поэтому мы разработали интерфейс StorageDevice. У нас есть две реализации (FDD и HDD), предоставленные другими разработчиками.

Затем у нас есть класс OperatingSystem, который может вызывать методы интерфейса, такие как saveData, просто передавая экземпляр класса, реализованный интерфейс StorageDevice.

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

package mypack;

interface StorageDevice {
    void saveData (String data);
}


class FDD implements StorageDevice {
    public void saveData (String data) {
        System.out.println("Save to floppy drive! Data: "+data);
    }
}

class HDD implements StorageDevice {
    public void saveData (String data) {
        System.out.println("Save to hard disk drive! Data: "+data);
    }
}

class OperatingSystem {
    public String name;
    StorageDevice[] devices;
    public OperatingSystem(String name, StorageDevice[] devices) {

        this.name = name;
        this.devices = devices.clone();

        System.out.println("Running OS " + this.name);
        System.out.println("List with storage devices available:");
        for (StorageDevice s: devices) {
            System.out.println(s);
        }

    }

    public void saveSomeDataToStorageDevice (StorageDevice storage, String data) {
        storage.saveData(data);
    }
}

public class Main {

    public static void main(String[] args) {

        StorageDevice fdd0 = new FDD();
        StorageDevice hdd0 = new HDD();     
        StorageDevice[] devs = {fdd0, hdd0};        
        OperatingSystem os = new OperatingSystem("Linux", devs);
        os.saveSomeDataToStorageDevice(fdd0, "blah, blah, blah...");    
    }
}
Владимир Георгиев
источник
То же самое можно сделать с абстрактным классом StorageDevice и классами FDD и HDD, расширяющими класс StorageDevice. но если мы используем абстрактный класс, мы не можем воспользоваться множественным наследованием.
Владимир Георгиев