Интервьюер: Десять вопросов о дженериках, справитесь?

интервью

Вопрос 1: Зачем нужны дженерики?

отвечать:

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

Вопрос 2: С точки зрения ArrayList, зачем использовать дженерики?

отвечать:

До того, как Java добавила дженерики, был одинArrayListкласс, этоArrayListОбщая концепция класса заключается в использованиинаследоватьбыть реализованным.

public class ArrayList {
    private Object[] elementData;
    public Object get(int i) {....}
    public void add(Object o) {....}
}

У этого класса есть две проблемы:

  1. Кастинг требуется при получении значения
  2. Нет проверки ошибок, в массив можно добавлять объекты любого класса
ArrayList files = new ArrayList();
files.add(new File(""));
String filename = (String)files.get(0);

Для этого вызова скомпилируйте и запустите без ошибок, но когда мы используем метод get в другом месте, чтобы получить только что сохраненныйFileобъект, приведенный кStringтип вызовет ошибку.

Дженерики решают эту проблему, предоставляятип параметра.

ArrayList<String> files = new ArrayList<>();

Это делает код более читаемым, мы можем сразу увидеть, что этот список данных содержитStringобъект. Компилятор также может эффективно использовать эту информацию, когда мы вызываемgetКогда нет необходимости использовать приведение, компилятор знает, что тип возвращаемого значенияString, вместоObject:

String filename = files.get(0);

Компилятор тоже знаетArrayList<String>серединаaddметод имеет видStringпараметр. Это будет лучше, чем использоватьObjectПараметры типа стали безопаснее, теперь компилятор может проверять, чтобы не вставлять объекты неправильного типа:

files.add(new File(""));

Такой код не может быть скомпилирован,Гораздо лучше иметь ошибку компиляции, чем исключение приведения класса во время выполнения..

Вопрос 3: Давайте поговорим об общих классах

Общий классимеет одну или несколько переменных типаКласс, для которого мы фокусируемся только на дженериках и не беспокоимся о деталях хранения данных.

public class Couple<T> {
   private T one;
   private T two;
}

SingerКласс вводит переменную типа T, заключенную в угловые скобки и помещаемую после имени класса. Общий класс может иметь несколько переменных типа:

public class Couple<T, U> {...}

Переменная типа в определении классаУказывает возвращаемый тип метода и типы полей и локальных переменных.

//域
private T one;
//返回类型
public T getOne() { return one; }
//局部变量
public void setOne(T newValue) { one = newValue; }

Общие типы могут быть созданы с использованием конкретных типов вместо переменных типа:

Couple<Rapper>

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

Вопрос 4. Расскажите об определении и использовании универсальных методов.

отвечать:

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

Давайте рассмотрим пример универсального метода:

class ArrayUtil {

    public static <T> T getMiddle(T...a){
        return a[a.length / 2];
    }
}

При вызове универсального метода укажите конкретный тип в угловых скобках перед именем метода:

String middle = ArrayUtil.<String>getMiddle("a","b","c");

В этом случае вызов метода можно опустить.<String>параметр типа, компилятор будет использоватьвывод типаЧтобы вывести вызванный метод, то есть вы можете написать:

String middle = ArrayAlg.getMiddle("a","b","c");

Вопрос пятый:E V T K ?что это

отвечать:

  • E- Элемент представляет собой элемент. Атрибут является перечислением.
  • T—— Класс типа, который относится к типу Java
  • K- ключ ключ
  • V——Значение значения
  • - Указывает на неопределенный тип в использовании

Вопрос 6: Вы поняли ограничения переменных типа?

отвечать:

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

<T extends Serializable & Cloneable>

Использование нескольких уточненных типов одной переменной типа&разделены, в то время как,Используется для разделения переменных нескольких типов.

<T extends Serializable,Cloneable>

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

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

Вопрос 7: Что вы знаете о дженериках и наследовании?

отвечать:

Во-первых, давайте посмотрим на класс и его подклассы, такие какSingerиRapper. ноCouple<Rapper>но нетCouple<Singer>подкласс .

Какую бы связь ни имели S и T,Couple<S>иCouple<T>Нет подключения.

Здесь нам нужно обратить внимание на разницу между дженериками и массивами Java, вы можете использоватьRapper[]Массиву присваивается типSinger[]Переменные:

Rapper[] rappers = ...;
Singer[] singer = rappers;

Однако массивы имеют специальную защиту: если вы попытаетесь сохранить суперкласс в массиве подклассов, виртуальная машина выброситArrayStoreExceptionаномальный.

Вопрос 8: Давайте поговорим о подстановочных знаках

отвечать:

В подстановочных типах параметры типа могут изменяться. Например, подстановочные знаки:

Couple<? extends Singer>

представляет любой универсальный тип, параметр типа которогоSingerподклассы, такие какCouple<Rapper>, но не будетCouple<Dancer>.

Предположим, теперь нам нужно написать метод для вывода чего-либо:

public static void printCps(Couple<Rapper> cps) {
      Rapper one = cp.getOne();
      Rapper two = cp.getTwo();
      System.out.println(one.getName() + " & " + two.getName() + " are cps.");
}

Как упоминалось ранее, невозможноCouple<Rapper>перешел к этому методу, который очень ограничительный. Решение простое, используйте подстановочные знаки:

public static void printCps(Couple< ? extends Singer> cps) 

Couple<Rapper>даCouple< ? extends Singer>подтип .

Далее рассмотрим другую проблему, использование подстановочных знаков пройдет.Couple< ? extends Singer>Неработающие ссылкиCouple<Rapper>?

Couple<Rapper> rapper = new Couple<>(rapper1, rapper2);
Couple<? extends Singer> singer = rapper;
player.setOne(reader);

Это может привести к повреждению, но когда мы звонимsetOne, если звонок неSingerподклассRapperобъект класса, но другойSingerобъект подкласса, возникает ошибка. Давайте взглянемCouple<? extends Singer>Методы:

? extends Singer getOne();
void setOne(? extends Singer);

Это будет очевидно, потому что если мы позвонимsetOne()метод, компилятор может знать, что определенныйSingerподтип , не имея возможности точно определить, какой это тип, он отказывается передавать какой-либо конкретный тип, потому что ? не может использоваться для сопоставления. но использоватьgetOneТакой проблемы нет, потому что нам не нужно заботиться о том, какой тип он получит, но он должен бытьSingerподкласс .

Квалификация с подстановочными знаками очень похожа на квалификацию переменной типа, но типы с подстановочными знаками имеют дополнительную возможность указывать квалификацию супертипа:

? super Rapper

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

Компилятор не может знатьsetOneКонкретный тип метода, поэтому вызов этого метода не может получить типSingerилиObjectпараметр. только пройтиRapperобъект типа или некоторого подтипа (Reader) объект. Также, если звонитьgetOne, тип возвращаемого объекта не гарантируется.

в заключении:

Подстановочные знаки, уточненные супертипами, могут быть записаны в универсальные объекты, а подстановочные знаки, уточненные подтипами, могут быть прочитаны из универсальных объектов.

Вопрос 9: Как выглядят дженерики в виртуальной машине?

отвечать:

  1. Виртуальные машины не имеют объектов универсального типа, все объекты относятся к обычным классам.Всякий раз, когда определяется универсальный тип, автоматически предоставляется соответствующий примитивный тип. Имя исходного типа — это имя универсального типа с удаленными параметрами типа.Удалить переменную типа и заменить ее квалифицированным типом(Неквалифицированные переменные используются дляObject). Цель этого состоит в том, чтобы разрешить неуниверсальныеJavaПозже программа поддерживает общиеjvmвсе еще работает (обратно совместим)

  2. Когда программа вызывает универсальный метод, компилятор вставляет приведение, если возвращаемый тип стирается.

Couple<Singer> cps = ...;
Singer one = cp.getOne();

стеретьcp.getOneвернет возвращаемый типObjectтип. Компилятор автоматически вставляетSingerпреобразование типа принуждения. То есть компилятор компилирует этот вызов метода в две инструкции виртуальной машины:

к оригинальному методуcp.getOneвызов Приведите возвращаемый тип объекта кSingerтип.

  1. Приведения также вставляются при доступе к общедоступному универсальному полю.
//我们写的代码
Singer one = cps.one;
//编译器做的事情
Singer one = (Singer)cps.one;

Вопрос 10: Что вы знаете об универсальном стирании?

отвечать:

Стирание типа происходит в универсальных методах, программисты обычно думают о следующих универсальных методах.

public static <T extends Comparable> T min(T[] a)

представляет собой полное семейство методов, и после стирания типа остается только один метод:

public static Comparable min(Comparable[] a)

В это время параметр типа T был стерт, оставив только ограниченный типComparable.

Но стирание метода приносит некоторые проблемы:

class Coupling extends Couple<People> {
    public void setTwo(People people) {
            super.setTwo(people);
    }
}

После стирания:

class Coupling extends Couple {
    public void setTwo(People People) {...}
}

В это время возникает проблема, что есть еще одинCoupleкласс унаследовалsetTwoметод, а именно:

public void setTwo(Object two)

Очевидно, это другой метод, потому что он принимает другой тип параметра (Object) вместоPeople.

Coupling coupling = new Coupling(...);
Couple<People> cp = interval;
cp.setTwo(people);

Вот, надеюсьsetTwoВызов является полиморфным и вызывает наиболее подходящий метод. так какcpЦитироватьCouplingобъект, поэтому следует называтьCoupling.setTwo. Проблема в том, что стирание типов конфликтует с полиморфизмом. Чтобы решить эту проблему, компилятор долженCouplingСоздайте метод моста в классе:

public void setTwo(Object second) {
    setTwo((People)second);
}

Переменнаяcpбыл объявлен как типCouple<LocalDate>, и этот тип имеет только один простой метод с именемsetTwo,СейчасsetTwo(Object). Для виртуальной машиныcpСсылочный объект вызывает этот метод. Этот объектCouplingтип, поэтому он будет вызыватьCoupling.setTwo(Object)метод. Этот метод является методом синтетического моста. это позвонитCoupling.setTwo(Date), чего мы и ожидали.

Итак, мы должны помнить несколько моментов об общих преобразованиях Java:

  1. В виртуальной машине нет дженериков, только обычные классы и методы
  2. Все параметры типа заменяются их уточненными типами.
  3. Мостовые методы синтезированы для сохранения полиморфизма
  4. Для обеспечения безопасности типов вставляйте приведения типов, если это необходимо.

Если вы что-то узнали, пожалуйста, дайте мне лайк 👍 + подпишитесь, это лучшая поддержка для ✊ настаивать на оригинальном авторе! Я Шанхэ, обычная кожа, душа, которая одна на тысячу, и писатель, который не тот.