1. Введение
В этой статье мы пройдемся по некоторым примерам Java дженериков интервью вопросы и ответы.
Общие сведения являются основной концепцией Java, впервые представленной в Java 5. Из-за этого, почти все Java-базы кода будут использовать их, почти гарантируя, что разработчик будет работать в них в какой-то момент. Вот почему очень важно, чтобы понять их правильно, и именно поэтому они, скорее всего, будет предложено о во время интервью процесса.
2. Вопросы
No 1. Что такое общий параметр типа?
Тип это имя класс или интерфейс . Как следует из названия, общий параметр типа, когда тип может быть использован в качестве параметра в классе, методе или объявлении интерфейса.
Начнем с простого примера, без дженериков, чтобы продемонстрировать это:
public interface Consumer { public void consume(String parameter) }
В этом случае тип параметра метода потреблять () метод струна. Он не параметризирован и не настраивается.
Теперь давайте заменим наш Струнные тип с общим типом, который мы будем называть Т. Он назван так по конвенции:
public interface Consumer{ public void consume(T parameter) }
Когда мы реализуем нашего потребителя, мы можем предоставить тип что мы хотим, чтобы потреблять в качестве аргумента. Это общий параметр типа:
public class IntegerConsumer implements Consumer{ public void consume(Integer parameter) }
В этом случае, теперь мы можем потреблять integers. Мы можем поменять эту тип за все, что мы требуем.
No 2. Каковы некоторые преимущества использования общих типов?
Одним из преимуществ использования дженериков является избегание слепок и обеспечение безопасности типа. Это особенно полезно при работе с коллекциями. Давайте продемонстрируем это:
List list = new ArrayList(); list.add("foo"); Object o = list.get(0); String foo = (String) o;
В нашем примере тип элемента в нашем списке неизвестен компилятору. Это означает, что единственное, что может быть гарантировано, это то, что это объект. Поэтому, когда мы извлекаем наш элемент, Объект это то, что мы получаем обратно. Как авторы кода, мы знаем, что это Струна, но мы должны бросить наш объект на один, чтобы исправить проблему явно. Это производит много шума и шаблонов.
Далее, если мы начинаем думать о возможности ручной ошибки, проблема литья становится все хуже. Что, если бы у нас случайно Интегер в нашем списке?
list.add(1) Object o = list.get(0); String foo = (String) o;
В этом случае, мы хотели бы получить ClassCastException во время выполнения, как Интегер не могут быть брошены на струна.
Теперь давайте попробуем повториться, на этот раз с помощью дженериков:
Listlist = new ArrayList<>(); list.add("foo"); String o = list.get(0); // No cast Integer foo = list.get(0); // Compilation error
Как мы видим, с помощью дженериков у нас есть проверка типа компиляции, которая предотвращает КлассCastExceptions и устраняет необходимость литья.
Другое преимущество заключается в том, чтобы избежать дублирования кода . Без дженериков мы должны копировать и вставлять один и тот же код, но для разных типов. С дженериками, мы не должны делать это. Мы можем даже реализовать алгоритмы, которые применяются к общим типам.
В3. Что такое стирание типа?
Важно понимать, что общая информация о типе доступна только компилятору, а не СПМ. Другими словами, , стирание типа означает, что общая информация о типе недоступна для JVM во время выполнения, только компиляция времени .
Аргументация, лежащая в основе основного выбора реализации, проста – сохранение обратной совместимости с более старыми версиями Java. Когда общий код компилируется в карт-код, это будет, как если бы общий тип никогда не существовало. Это означает, что компиляция будет:
- Замена общих типов объектами
- Замена ограниченных типов (Подробнее об этом в более позднем вопросе) с классом первой границы
- Вставьте эквивалент слепки при извлечении общих объектов.
Важно понимать стирание типа. В противном случае разработчик может запутаться и думать, что он сможет получить тип во время выполнения:
public foo(Consumerconsumer) { Type type = consumer.getGenericTypeParameter() }
Вышеупомянутый пример является псевдокод эквивалент того, что вещи могут выглядеть без стирания типа, но, к сожалению, это невозможно. Еще раз, общая информация о типе недоступна во время выполнения.
No 4. Если общий тип опущен при мгновенной дифференциации объекта, будет ли код по-прежнему компилироваться?
Поскольку дженериков не существовало до Java 5, их можно вообще не использовать. Например, дженерики были переоборудованы под большинство стандартных классов Java, таких как коллекции. Если мы посмотрим на наш список из вопроса один, то мы увидим, что у нас уже есть пример опуская общий тип:
List list = new ArrayList();
Несмотря на возможность компиляции, все еще вероятно, что будет предупреждение от компилятора. Это связано с тем, что мы теряем дополнительную проверку времени компиляции, которую мы получаем от использования дженериков.
Важно помнить, что в то время как обратная совместимость и стирание типов делают возможным опустить общие типы, это плохая практика.
В5 евро. Чем общий метод отличается от общего типа?
Общий метод, где параметр типа вводится в метод, в рамках этого метода. Давайте попробуем это на примере:
public staticT returnType(T argument) { return argument; }
Мы использовали статический метод, но могли бы также использовать не статичных один, если мы хотим. Используя вывод типа (охватываемый в следующем вопросе), мы можем вызвать это как любой обычный метод, без необходимости указывать какие-либо аргументы типа, когда мы делаем это.
No 6. Что такое вывод типа?
Вывод типа , когда компилятор может посмотреть на тип аргумента метода, чтобы сделать вывод об общем типе. Например, если мы прошли в T к методу, который возвращает Т, затем компилятор может выяснить тип возврата. Давайте попробуем это, ссылаясь на наш общий метод из предыдущего вопроса:
Integer inferredInteger = returnType(1); String inferredString = returnType("String");
Как мы видим, нет необходимости в гипсе, и нет необходимости проходить в какой-либо общий аргумент типа. Тип аргумента только высовывовыв возвращает тип.
No7. Что такое параметр связанного типа?
До сих пор все наши вопросы охватывали общие типы аргументов, которые не связаны. Это означает, что наши общие аргументы типа может быть любой тип, который мы хотим.
Когда мы используем ограничивающие параметры, мы ограничиваем типы, которые могут быть использованы в качестве общих аргументов типа.
Например, предположим, что мы хотим, чтобы наш общий тип всегда был подклассом животных:
public abstract class Cage{ abstract void addAnimal(T animal) }
С помощью расширений , мы заставляем T быть подклассом животных . Мы могли бы иметь клетку кошек:
CagecatCage;
Но мы не могли иметь клетку объектов, так как объект не является подклассом животного:
Cage
Одним из преимуществ этого является то, что все методы животного доступны для компилятора. Мы знаем, что наш тип расширяет его, чтобы мы могли написать общий алгоритм, который работает на любом животном. Это означает, что мы не должны воспроизводить наш метод для различных подклассов животных:
public void firstAnimalJump() { T animal = animals.get(0); animal.jump(); }
No 8. Можно ли задекларировать параметр множественного связанного типа?
Возможно объявление нескольких границ для наших общих типов. В нашем предыдущем примере мы указали одну границу, но мы могли бы также указать больше, если мы хотим:
public abstract class Cage
В нашем примере животное является классом и сравнимым является интерфейсом. Теперь наш тип должен уважать обе эти верхние границы. Если бы наш тип был подклассом животных, но не реампилировать сопоставимый, то код не будет компилироваться. Также стоит помнить, что если одной из верхних границ является класс, то это должен быть первый аргумент.
No9. Что такое тип wildcard?
Тип подстановочного знака представляет собой неизвестную тип . Он взорван с вопросительный знак следующим образом:
public static void consumeListOfWildcardType(List> list)
Здесь мы уточняем список, который может быть любого тип . Мы могли бы передать список чего угодно в этот метод.
No10. Что такое верхний связанный wildcard?
Верхний предел подстановочный знак, когда тип подстановочных карт наследует от конкретного типа . Это особенно полезно при работе с коллекциями и наследством.
Давайте попробуем продемонстрировать это с фермы класса, который будет хранить животных, во-первых, без подстановочного знака типа:
public class Farm { private Listanimals; public void addAnimals(Collection newAnimals) { animals.addAll(newAnimals); } }
Если бы у нас было несколько подклассов животных , такие как кошки и собаки , мы можем сделать неверное предположение, что мы можем добавить их все в нашу ферму:
farm.addAnimals(cats); // Compilation error farm.addAnimals(dogs); // Compilation error
Это потому, что компилятор ожидает коллекцию конкретного типа животных , не один он подклассов.
Теперь давайте введем верхнюю границу подстановочный знак для нашего метода добавления животных:
public void addAnimals(Collection extends Animal> newAnimals)
Теперь, если мы попробуем еще раз, наш код будет компилироваться. Это потому, что мы сейчас говорим компилятор принять коллекцию любого подтипа животных.
No11. Что такое неограниченный Wildcard?
Неограниченный подстановочный знак – это подстановочный знак без верхней или нижней границы, который может представлять любой тип.
Важно также знать, что тип подстановочного знака не является синонимом объекта. Это связано с тем, что подстановочный знак может быть любого типа, в то время как тип объекта является конкретно объектом (и не может быть подклассом объекта). Давайте продемонстрируем это на примере:
List> wildcardList = new ArrayList(); List
Опять же, причина, по которой вторая строка не компилируется, заключается в том, что требуется список объектов, а не список строк. Первая строка компилирует, потому что список любого неизвестного типа является приемлемым.
No12. Что такое нижняя ограниченная wildcard?
Нижняя ограниченная подстановочный знак, когда вместо того, чтобы обеспечить верхнюю границу, мы предоставляем нижнюю границу с помощью супер ключевое слово. Другими словами, нижняя ограниченная подстановочный знак означает, что мы заставляем тип быть суперклассом нашего ограниченного типа . Давайте попробуем это на примере:
public static void addDogs(List super Animal> list) { list.add(new Dog("tom")) }
Используя супер, мы могли бы назвать addDogs в списке объектов:
ArrayList
Это имеет смысл, так как объект является суперклассом животного. Если бы мы не использовали нижнюю ограниченную подстановочный знак, код бы не компилировать, так как список объектов не является списком животных.
Если мы подумаем об этом, мы не сможем добавить собаку в список любого подкласса животных, таких как кошки, или даже собаки. Только суперкласс животных. Например, это не будет компиляция:
ArrayListobjects = new ArrayList<>(); addDogs(objects);
No13. Когда вы решите использовать нижний связанный тип по сравнению с верхним связанным типом?
При работе с коллекциями общим правилом выбора между верхними или нижними подстановочные знаки является PECS. PECS означает производитель расширяет, потребительские супер.
Это можно легко продемонстрировать с помощью некоторых стандартных интерфейсов и классов Java.
Производитель расширяет просто означает, что если вы создаете производителя общего типа, а затем использовать расширяет ключевое слово. Давайте попробуем применить этот принцип к коллекции, чтобы понять, почему это имеет смысл:
public static void makeLotsOfNoise(List extends Animal> animals) { animals.forEach(Animal::makeNoise); }
Вот, мы хотим позвонить makeNoise() на каждое животное в нашей коллекции. Это означает, что наша коллекция является производителем , как все, что мы делаем с ним становится его вернуть животных для нас, чтобы выполнить нашу операцию на. Если мы избавимся от расширяет , мы не смогли бы пройти в списки кошек , собак или любых других подклассов животных. Применяя принцип расширения производителя, мы применяем как можно большую гибкость.
Потребительские супер означает обратное производитель расширяется. Все это означает, что если мы имеем дело с чем-то, что потребляет элементы, то мы должны использовать супер ключевое слово. Мы можем продемонстрировать это, повторив наш предыдущий пример:
public static void addCats(List super Animal> animals) { animals.add(new Cat()); }
Мы только добавляем к нашему списку животных, так что наш список животных является потребителем. Вот почему мы используем супер ключевое слово. Это означает, что мы могли бы пройти в список любого суперкласса животных, но не подкласса. Например, если бы мы попытались пройти в список собак или кошек, то код не будет компилировать.
Последнее, что нужно рассмотреть, что делать, если коллекция является одновременно потребителем и производителем. Примером этого может быть коллекция, в которой элементы добавляются и удаляются. В этом случае следует использовать неохведенный подстановочный знак.
No14. Существуют ли ситуации, когда общая информация о типе доступна во время выполнения?
Существует одна ситуация, когда общий тип доступен во время выполнения. Это когда общий тип является частью подписи класса, как это:
public class CatCage implements Cage
Используя отражение, мы получаем параметр этого типа:
(Class) ((ParameterizedType) getClass() .getGenericSuperclass()).getActualTypeArguments()[0];
Этот код несколько хрупкий. Например, это зависит от параметра типа, определяемого на немедленном суперклассе. Но, это показывает, ЧТО JVM имеет информацию такого типа.