Передача в качестве параметра List
Подскажите пожалуйста почему я не могу сделать следующее
public class A implements IA {
static void test(List<IA> ia) {
}
public static void main(String[] args) {
List<A> aList = new ArrayList<>();
test(aList);
}
}
Метод тест принимает в качестве параметра список объектов типа IA. Почему я не могу передать в метод список List<A>?
Ответы (3 шт):
Проблема в том что вы пытаетесь передать объект класса A в метод который принимает на вход объект интерфейса IA. Из-за этого возникает несовместимость типов. Рекомендую вам почитать про преобразование типов. В частности восходящее и нисходящее преобразование. На самом деле проблем может быть много, к примеру интерфейс может быть приватным и находится в другом классе, либо в другом package, либо могут быть другие синтаксические ошибки при которых вы попросту не сможете от него наследоваться.
(Попытаюсь объяснить проще, у всех классов по-дефолту один родитель - класс Object. У интерфейсов нету общего базового класса-родителя, и интерфейсы могут наследоваться (extends) только от других интерфейсов, а классы только от одного другого класса, но классы могут имплементировать (implements) несколько интерфейсов. На примере:
class A extends (class) B - Можно
сlass A extends (class) A, B - Множественное наследование классов запрещено
class A extends (interface) B - Нельзя, классы наследуются только от классов
class A implements (interface) B, C - Можно
interface A extends (class) B - Запрещено, поскольку класс изначально содержит в себе детали реализации (являясь потомком Object)
interface A extends (interface) A, B - наследоваться от нескольких интерфейсов можно.
interface A implements (interface/class) B - интерфейсы не могут имплементировать другие интерфейсы и тем более классы.
Дабы вам было понятнее почему так происходит, попытаюсь объяснить простыми словами. Классы придуманы для описания объектов и могут содержать в себе детали реализации. Интерфейсы придуманы для описания поведений, к примеру есть у нас класс собака - Dog. Этот класс описывает характеристики (цвет, рост, порода, выполняемые действия (методы внутри класса)). И есть интерфейсы - Move, Bark, Eat - которые описывают поведение (двигаться, гавкать, кушать) которое могут имплементировать другие классы. Из этого так же логически возникает факт того, что нужно хорошо подумать, прежде чем имплементировать какой-либо интерфейс. Поскольку к примеру класс Dog может имплементировать поведение гавкать, но если мы сделаем тоже с классом Cat (кошка) то это уже будет немного странно, не правда ли?
Советую в следующий раз вам прикрепить скриншот с структурой вашего проекта а так же содержимое IA, а так же информацию которую выводит компилятор, так вы сможете получить более детальный и точный ответ, поскольку ошибок/проблем может быть сразу несколько.
Не такой это простой вопрос, особенно для начинающих разработчиков. Никакое чтиво про "преобразование" типов, полагаю, вам не поможет, потому что очень скоро вы упретесь в то, что между супер классом и классом - наследником отношение is a, т.е. класс-наследник является супер классом, что снова поставит вас в тот же тупик, в котором вы находитесь. К сожалению, объяснит все подробности происходящего в рамках форума невозможно, однако, если не углубляться в детали, то прояснит ситуацию вполне реально.
Весь фокус в том, что вы имеете дело не просто с наследованием, а с дженериками. И понимание происходящего зависит от того, насколько хорошо вы понимаете работу дженериков (а это довольно непросто). Вы должны понимать, что между классом А и интерфейсом IA действительно отношение is a, но вы имеете дело не с этими классами, а с коллекциями, типизированными указанными классом и интерфейсом, а это совсем не одно и тоже. Теперь определение того, является ли коллекция, объявленная в сигнатуре метода, подходящей по типу для коллекции, которую мы пытаемся передать в этот метод, возложено именно на дженерики(коллекции)!
Для понимания происходящего (реализации полиморфизма) вам следует знать, что для дженериков существует своя система наследования. Рассмотрите этот пример:
import java.util.ArrayList;
import java.util.List;
public class A implements IA {
static void test(List<? extends IA> ia) {
}
public static void main(String[] args) {
List<IA> aList = new ArrayList<>();
aList.add(new A());
aList.add(new B());
test(aList);
}
}
class B implements IA {
public static void main(String[] args) {
List<B> aList = new ArrayList<>();
aList.add(new B());
test(aList);
}
}
В случае с классом А нам необходимо создать единую коллекцию для наполнения ее объектами различных классов-наследников. Именно это происходит в методе main класса А. В этом случае коллекция типизируется интерфейсом. Если бы мы типизировали нашу коллекцию конкретным классом, например А, то не смогли бы добавить туда экземпляры класса В, а именно это нам необходимо в данном примере.
В случае с классом В мы хотим иметь коллекцию, типизированную только классом В (возможно для В существуют методы, которых нет в интерфейсе и на этом уровне - в методе main класса В , работая с коллекцией, мы вызываем именно эти специфические для В методы). В этом случае мы не сможем добавить в коллекцию экземпляры класса А.
Что касается метода test, нам достаточно изменить дженерик коллекции лист на List<? extends IA>, теперь этот метод в состоянии принять и коллекцию, типизированную интерфейсом IA, и коллекцию, типизированную наследниками указанного интерфейса - классами A,B.
В том случае, когда вы объявляете метод с сигнатурой test(List<IA> ia) вы указываете компилятору, что хотите принимать исключительно коллекцию, типизированную интерфейсом. Таким образом, вам предоставляется контроль над тем, что именно принимает ваш метод: исключительно коллекции , типизированные интерфейсом, или как коллекции, типизированные интерфейсом, так и коллекции , типизированные наследниками данного интерфейса.
Если есть вопросы - задавайте
Ответ на самом деле прост - дженерики в Java являются инвариантными. Чтобы ответить на свой вопрос, вам достаточно загуглить что означают следующие термины: инвариантность, контравариантность, ковариантность.