Статьи

Захват подстановочных знаков Java Generics — полезная информация

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

// Copy Factory
public static Field getNewInstance(Field field)
// Copy Constructor
public Field Field(Field field)

Я выбираю фабрику статических копий, потому что

  • У меня не было исходного кода для класса
  • С фабрикой копий я могу кодировать интерфейс вместо класса.

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

public class Field<T> {
private String name;
private T value;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public T getValue() {
return value;
}

public void setValue(T value) {
this.value = value;
}

}

Первая реализация класса FieldUtils, которая пришла мне в голову, была такой, как показано ниже

public static Field<?> copy(Field<?> field) {
Field<?> objField = new Field<?>();//1
objField.setName(field.getName());//2
objField.setValue(field.getValue());//3
return objField;//4
}

Приведенный выше код не будет компилироваться из-за двух ошибок компиляции. Первая ошибка компиляции в строке 1, потому что вы не можете создать экземпляр Field <?>. Поле <?> Означает Поле <? расширяет объект> а когда у вас есть ? extends Что- то, из чего вы можете только извлечь значения, вы не можете устанавливать значения в нем, кроме нуля. Для того чтобы объект был полезен, вы должны уметь делать и то, и другое, поэтому компилятор не позволяет вам создавать объект. Вторая ошибка компиляции будет в строке номер 3, и вы получите загадочное сообщение об ошибке, подобное этому  

Метод setValue (capture # 3-of?) В поле типа <capture # 3-of?> Не применим для аргументов (capture # 4-of?)

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

public static Field<?> copy(Field<?> field) {
field.setName(field.getName());
field.setValue(field.getValue());
return field;
}

Скомпилируется ли приведенный выше код? Нет. Вы снова получите сообщение об ошибке, подобное упомянутому выше. Чтобы исправить эту ошибку, мы напишем приватный вспомогательный метод, который захватит подстановочный знак и назначит его переменной типа. Эта техника называется захватом по шаблону. Я прочитал об этом в книге по обобщению и сборке Java,  которую необходимо прочитать для понимания обобщений Java. Подстановочный знак работает по типу логического вывода . 

public static Field<?> copy(Field<?> field) {
return copyHelper(field);
}

private static <T> Field<T> copyHelper(Field<T> field) {
Field<T> objField = new Field<T>();
objField.setName(field.getName());
objField.setValue(field.getValue());
return objField ;
}

Захват подстановочных знаков очень полезен, когда вы работаете с подстановочными знаками, и зная, что это сэкономит вам много времени.