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

Например, такой код валидный:

interface TestInterface {
    void doSomething();
}
TestInterface e;

Взаимоотношения родительского и дочернего класса

Допустим у нас есть родительский и дочерний классы:

class ParentClass {
    public void doSomething() {
        System.out.println("doSomething");
    }
}

class TestClass extends ParentClass {
    int variable;

    public void methodInClass() {
        System.out.println("methodInClass");
    }
}

Мы можем объявить переменную родительского класса, но проинициализировать переменную экземпляром дочернего класса:

ParentClass x = new TestClass();

У нас создался экземпляр дочернего класса, но он как бы замаскировался под экземпляр родительского класса. И мы будем иметь прямой доступ только к методам и переменным, которые есть у родительского класса:

ParentClass x = new TestClass();
x.doSomething(); // Так можно
x.methodInClass(); // Так нельзя

Но переменные и методы дочернего класса всё равно присутствуют в экземпляре класса, поэтому мы можем получить доступ к ним через приведение типов:

ParentClass x = new TestClass();
x.doSomething(); // Так можно
((TestClass) x).methodInClass(); // Так можно

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

TestClass x = new TestClass();

ParentClass y = (ParentClass)x;
y.doSomething();

И напрямую в переменной y будет доступ только к родительским переменным и методам. Но напоминаю, что y ссылается на тот же экземпляр, что и переменная x. Так что обратное преобразование типов «вернет» доступ к дочерним методам:

TestClass x = new TestClass();
x.variable = 5;

ParentClass y = (ParentClass)x;
y.doSomething();
((TestClass)y).methodInClass();
((TestClass)y).variable = 6;
System.out.println(x.variable); // Вывод: 6

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

abstract class Animal {
    abstract void say();
}

class Dog extends Animal {
    @Override
    void say() {
        System.out.println("Гав");
    }
}

class Cat extends Animal {
    @Override
    void say() {
        System.out.println("Мяу");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal [] x = new Animal[2];
        x[0] = new Dog();
        x[1] = new Cat();

        x[0].say(); // Выведет "Гав"
        x[1].say(); // Выведет "Мяу"
    }
}

Взаимоотношения класс и интерфейса

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

interface ExampleInterface {
    void doSomething();
}

class TestClass implements ExampleInterface {
    int variable;

    @Override
    public void doSomething() {
        System.out.println("doSomething");
    }

    public void methodInClass() {
        System.out.println("methodInClass");
    }
}

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

// Обычное применение класса
TestClass x = new TestClass();
x.methodInClass();
x.doSomething();

// Переменная объявлена как «экземпляр» интерфейса, но создается через
// нормальный экземпляр класса
ExampleInterface y = new TestClass();
y.doSomething();

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

ExampleInterface y = new TestClass();
y.doSomething();  // Так работает
y.methodInClass(); // Так не работает

Но мы не создавали экземпляр интерфейса (это не сработает: ExampleInterface z = new ExampleInterface();). Переменная y ссылается на экземпляр класса TestClass, но маскируется под «экземпляр» интерфейса. Поэтому через приведение типа можем получить доступ к «скрытым» методам и переменным:

ExampleInterface y = new TestClass();
y.doSomething();
//y.methodInClass(); // Так не работает
((TestClass) y).methodInClass(); // А через приведение типов работает

Аналогичным способом мы можем объявить переменную интерфейса и преобразовать к интерфейсу экземпляр класса:

TestClass x = new TestClass();

ExampleInterface z = (ExampleInterface) x;
z.doSomething(); // Так работает
z.methodInClass(); // Так не работает

((TestClass)z).methodInClass(); // А так работает

То есть полноценного экземпляра интерфейса не создается: он всё равно ссылается на экземпляр обычного класса.

Еще можно проверить наличие интерфейса у экземпляра класса:

TestClass x = new TestClass();

if (x instanceof ExampleInterface) {
    System.out.println("x является экземпляром класса с интерфейсом ExampleInterface");
}

Дополнительные ситуации

Выше отмечалось, что создать экземпляр интерфейса нельзя. Такой код не сработает:

ExampleInterface x = new ExampleInterface();

Но такой код будет работать и запускаться:

ExampleInterface x = new ExampleInterface() {
    @Override
    public void doSomething() {
        System.out.println("doSomething");
    }
};

x.doSomething();

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

Еще можно привести пример из рефлексии, когда вы можете проверить какой-то класс (не экземпляр): является он интерфейсом или нет:

Class classOfInterface = ExampleInterface.class;
if (classOfInterface.isInterface()) {
    System.out.println("Это интерфейс");
}