Подтвердить что ты не робот

Присвоение списка целых чисел в список строк

Я изучал Generics в Java, и я приблизился к очень интересному фрагменту кода. Я знаю, что в Java запрещено добавлять список одного типа в другой.

List<Integer> integerList = new ArrayList<Integer>();
List<String> stringList=integerList;

Итак, во второй строке я получаю ошибку времени компиляции.
Но если я создам общий метод внутри такого класса,

class  GenericClass <E>{
    void genericFunction(List<String> stringList) {
        stringList.add("foo");
    }
    // some other code
}

И в главном классе вызовите метод со списком Integer. Я не получаю никаких ошибок.

public class Main {
  public static void main(String args[]) {

     GenericClass genericClass=new GenericClass();
     List<Integer> integerList= new ArrayList<Integer>();
     integerList.add(100);
     genericClass.genericFunction(integerList);
     System.out.println(integerList.get(0));
     System.out.println(integerList.get(1));
  }
}

Вывод
         100
         Foo

Почему я не получаю никаких ошибок?

4b9b3361

Ответ 1

У вас смешанный Generic с raw type. Он будет компилироваться отлично, но во время выполнения он может выйти из строя, потому что общая информация теряется во время выполнения.

Generic следует использовать для отслеживания таких ошибок во время компиляции.

Это лучше объяснено в Что такое необработанный тип и почему мы не должны его использовать? подробно.


Предупреждение: Тип безопасности: метод genericFunction(List) относится к необработанному типу GenericClass. Ссылки на общий тип GenericClass<E> должны быть  параметризованных.

Если у вас есть два метода с одинаковым именем с другим общим типом списка, он приводит к ошибке времени компиляции. Компилятор не может разрешить тип Generic в случае аргументов метода, которые могут быть подтверждены ниже примерным кодом.

Пример кода: (ошибка компилятора - не действительный перегруженный метод)

void genericFunction(List<String> stringList){...}
void genericFunction(List<Integer> stringList){...}

Внесите некоторые изменения и повторите попытку:

class  GenericClass <E>{
    void genericFunction(List<E> stringList) {
        ...
    }
    // some other code
}

...

GenericClass<String> genericClass=new GenericClass<String>(); // Genreric object
List<Integer> integerList= new ArrayList<Integer>();
integerList.add(100);
genericClass.genericFunction(integerList); // compile time error

Создать методы таким образом

class GenericClass<E> {
    private List<E> list = new ArrayList<E>();

    public void addAll(List<E> newList) {
        list.addAll(newList);
    }

    public void add(E e) {
        list.add(e);
    }

    public E get(int index) {
        return list.get(index);
    }
    // some other code
}

Ответ 2

Вы не получаете ошибку времени компиляции, потому что используя GenericClass<E> необработанным способом:

GenericClass genericClass = new GenericClass();,

вы практически говорите компилятору об отключении проверки типа общего типа, потому что вам все равно.

Итак:

void genericFunction(List<String> stringList)

становится

void genericFunction(List stringList) для компилятора.

Вы можете попробовать следующее: GenericClass<?> genericClass, и вы сразу заметите, что компилятор узнает о дурном использовании дженериков, и он покажет вам ошибку:

The method genericFunction(List<String>) in the type GenericClass<capture#1-of ?> is not applicable for the arguments (List<Integer>)

Кроме того, если вы попытаетесь получить класс объекта 2-й позиции во время выполнения:

System.out.println(integerList.get(1).getClass());

вы получите сообщение об ошибке: java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer.

Ответ 3

Это происходит, потому что (что удивительно для меня) вы отключили проверку общего типа для класса целиком GenericClass.

Вам нужно знать, что вы, прежде всего, создаете общий класс без аргумента типа здесь:

GenericClass genericClass = new GenericClass();

И, из-за этого, ваш следующий код:

class GenericClass<E> {
    void genericFunction(List<String> stringList) {
        stringList.add("foo");
    }
    // some other code
}

получил уточнение:

class GenericClass {
    void genericFunction(List stringList) {
        stringList.add("foo");
    }
    // some other code
}

Обратите внимание, что List также стал сырым типом, что довольно удивительно для меня.

Здесь вы можете найти полный ответ, ссылаясь на JLS: fooobar.com/questions/144368/..., как объяснил Джон Скит.

Я считаю справедливым, что это происходит (хотя и не то, что вы ожидаете), потому что, если вы решите использовать необработанные типы, предполагается, что вы используете Java 4 или ниже и вообще не имеете доступа к дженерикам в любом случае, поэтому он также может не предоставлять их для методов, не связанных с общим типом, из класса, который был удален.

Ответ 4

Чтобы добавить к другим ответам.

Действительно, вы смешиваете сырой (непараметрированный) тип с параметризованными типами, который вызывает потерю типа и, по-видимому, правильный аргумент, проходящий до genericFunction, когда проверки безопасности типа не позволяют вам это делать.

Вопрос остается на , почему тип List<String> теряется в непараметрированном GenericClass объекте. Причина, по которой проверка вашего компилятора "отключает" в вашем случае - это механизм типа erasure, который говорит (простым языком), что ваш необработанный объект принадлежит классу со следующим содержимым:

class  GenericClass {
    void genericFunction(List stringList) {
        stringList.add("foo");
    }
    // some other code
}

Что, как вы видите, не делает никаких проверок какого-либо типа в содержимом передаваемого списка (он стирает параметры типа везде). Более того, стирание типа также стирает ваш параметризованный тип из переменной integerList, что делает его полностью подходящим в качестве аргумента для вашего метода genericFunction.

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

     GenericClass<?> genericClass=new GenericClass<Integer>();
     List<Integer> integerList= new ArrayList<Integer>();
     integerList.add(100);
     // no-no here
     genericClass.genericFunction(integerList);
     System.out.println(integerList.get(0));
     System.out.println(integerList.get(1));

Но опять же, если вы это сделаете, компилятор выполнит свою работу и отправится на вас в bazooka.

Подробнее о стирании типа здесь. Это приятная функция, которая позволяет проверять общий тип, сохраняя при этом обратную совместимость с предиповыми версиями Java.

Ответ 5

class  GenericClass <E>{
    void genericFunction(List<String> stringList) {
        stringList.add("foo");
    }
    // some other code
}

когда вы пишете сразу после имени класса. Он задает параметры типа. Он представляет собой универсальную функциональность. Это вводит переменную типа E, которая может использоваться в любом месте внутри класса. Тип переменной может быть любым не примитивным типом, который вы указываете: любым типом класса, любым типом интерфейса, любым типом массива или даже другой переменной типа.