Я думаю, что концепция, которую вы пытаетесь понять, использует интерфейс как тип. В java-программе переменная может быть объявлена как тип некоторого определенного интерфейса. Затем классы, реализующие этот интерфейс, могут быть созданы для переменных типа интерфейса. Однако могут использоваться только те методы, которые указаны на интерфейсе. Во время компиляции интерфейс используется для проверки типов. Однако во время выполнения байт-код, который фактически выполняет эту работу, исходит от разработчика интерфейса. Пример:
public interface foo {
public void bar();
}
public class A implements foo {
public void bar() {
// some code
}
}
public class Example {
public static void main(String[] args) {
foo aFoo = new A();
aFoo.bar();
}
}
В классе Пример: переменная с именем aFoo объявлена как тип foo, интерфейс. A, который реализует интерфейс foo, будет содержать код для выполнения функции bar(). В классе Example переменная aFoo получает экземпляр класса A, поэтому любой код в методе bar() для A будет выполняться при вызове aFoo.bar(), хотя aFoo объявлен как тип foo.
Итак, мы установили, что вся работа выполняется в классе A. Два класса и один интерфейс выше могут быть определены в собственном файле. Все три файла .class могут быть упакованы в банку и отправлены клиентам в качестве версии 1.0 программы. Спустя некоторое время ошибка ошибки может быть обнаружена при реализации bar() в классе A. После того как исправление будет разработано, если все изменения содержатся внутри метода bar(), только файл .class для A должен быть отправлен в клиентов. Обновленный A.class можно вставить в файл .jar программы (файлы .jar - это всего лишь .zip-файлы), перезаписывая предыдущую сломанную версию A.class. Когда программа перезапускается, JVM загрузит новую реализацию A.bar(), и класс Example получит новое поведение.
Как и почти все, более сложные программы могут стать, к тому же, более сложными. Но принципы одинаковы.