Обобщенные классы и Подстановочные типы (generics and wildcards)

 
 
 
Сообщения:2
Всем добрый день.
Возник вопрос по дженерикам. Очень мучает меня уже в течение недели.
Никак не могу разобраться. Перелопатил кучу информации, перечитал статьи на хабре, quizfull'e..
Смотрел StackOwerflow - ничего. Поэтому решил написать. Надеюсь на ваше знание и помощь! =)

Собственно сама проблема:

Вот простенький код


// Есть обобщенный класс Box
class Box<T> {

    private T element;

    // Два метода: 

   // 1) достать элемент 
    public T get() {
        return element;
    }

   // 2) и положить элемент
    public void put(T element) {
        this.element = element;
    }
}

// Далее создаю обычный класс..
class Employee {

    @Override
    public String toString() {
        return this.getClass().getName();
    }
}

public class Main {
 
   // Два статических метода:

   // 1) Обобщенный метод reboxHelper, который просто кладёт в нашу коробку
   //      строку "123123", преобразовывая её к параметру типа V

    static private<V> void reboxHelper(Box<V> box) {

        box.put((V) "123123");
    }

  // 2) Обычный метод rebox,
  //     который принимает подстановочный тип Box<?> в качестве параметра
  //     и вызывает метод reboxHelper, передавая ему подстановочный тип
    static public void rebox(Box<?> box) {

        reboxHelper(box);
    }

   // И сам метод main ...

    public static void main(String[] args)  {

       // Создаю обьект обобщенного класса  Box с параметром типа, заметьте !!! -   Employee
        Box<Employee> employeeBox = new Box<>();

       // Далее вызываю метод rebox, передавая ему в качестве параметра employeBox!!, ( параметр типа у неё Employee !!)
        rebox(employeeBox);

     // И к моему удивлению, этот код прекрасно компилируется и выполняется.
     // И дальше выводится строка "123123" !!!!
        System.out.println(employeeBox.get());

    }
}
}


Я вообще не понимаю, в чем дело.
В метод rebox я передаю emploeeBox c параметром типа Employee
    static public void rebox(Box<?> box) {

        reboxHelper(box);
    }


В методе reboxHelper строка "123123" должна преобразоваться к типу Employee
       static private<V> void reboxHelper(Box<V> box) {

        box.put((V) "123123");
    }

По хорошему, на этом моменте должна вылетать ошибка:
"Error:(25, 39) java: incompatible types: java.lang.String cannot be converted to Employee"
Но такого не просходит.
Я, видимо, не понимаю какой-то важный и тонкий момент с wildcard'ами и дженериками.
Можете, пожалуйста, объяснить?

Write nice code and your world will be more comfortable.
Изменен:01 окт 2016 14:42
 
 
Сообщения:48
static private<V> void reboxHelper(Box<V> box) { 
        box.put((V) "123123");
    }

Здесь никаких проблем на момент компиляции нет - неизвестно актуальное значение типа V и поэтому нельзя сделать вывод о невозможности приведения типа.
На момент исполнения опять-таки никаких проблем тоже нет в силу того, что в рантайме дженериков не существует. Фактически, приведенный выше код в рантайме равносилен такому
static private void reboxHelper(Box<Object> box) { 
        box.put((Object) "123123");
    }
 
 
Сообщения:75
Интересная тема, т.е. получается, что getclass возвращает значение строкового литерала чтоль? =)

А что если в Employee добавить конструктор с параметрами и поля какие-нибудь?

Видимо дело вот в этом

Quote:
The Diamond

In Java SE 7 and later, you can replace the type arguments required to invoke the constructor of a generic class with an empty set of type arguments (<>) as long as the compiler can determine, or infer, the type arguments from the context. This pair of angle brackets, <>, is informally called the diamond. For example, you can create an instance of Box<Integer> with the following statement


Box<Employee> employeeBox = new Box<>();


вот тут компилятор сам подставляет String

"Мы же профессионалы! Мы всегда делаем чуть больше, чем требуется!" (с)
Изменен:01 окт 2016 19:48
 
 
Сообщения:48
Даймонды здесь не причем - это синтаксический сахар, по сути. Не совсем понял, что значит
MuH3gPaB:
Box<Employee> employeeBox = new Box<>();

вот тут компилятор сам подставляет String

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

В качестве упражнения сделайте из класса Box обертку над какой-нибудь коллекцией, а через методы get и put осуществляйте с ней работу. И посмотрите на работу этого класса в различных сценариях (помещение объектов неродственных классов в коллекцию; получение объектов из коллекции etc.). Возможно, это поможет лучше понять, что делают и для чего нужны дженерики.
 
 
Сообщения:2
Agny:
static private<V> void reboxHelper(Box<V> box) { 
        box.put((V) "123123");
    }

Здесь никаких проблем на момент компиляции нет - неизвестно актуальное значение типа V и поэтому нельзя сделать вывод о невозможности приведения типа.
На момент исполнения опять-таки никаких проблем тоже нет в силу того, что в рантайме дженериков не существует. Фактически, приведенный выше код в рантайме равносилен такому
static private void reboxHelper(Box<Object> box) { 
        box.put((Object) "123123");
    }


Да, видимо, так оно и есть.
Спасибо большое за ответ!

Write nice code and your world will be more comfortable.
 
 
Сообщения:75
Вот, нашел статью по теме

"Мы же профессионалы! Мы всегда делаем чуть больше, чем требуется!" (с)
 
Модераторы:Нет
Сейчас эту тему просматривают:Нет