Java - изменение значения переменной необработанного типа. Возможный?

Начну с того, что я новичок в Java (/программировании), и это мой первый вопрос на сайте.

Только что узнал, как сделать упорядоченный список в Java, используя рекурсивные узлы. Все было просто, пока я не столкнулся с этим упражнением, в котором меня попросили написать метод, который удваивал бы любое значение, содержащееся в каждом узле. Вот код, который я пытался написать:

public class ListaInteri<E extends Integer>
{
    private NodoLista inizio;

    // Private inner class each instance of these has a raw type variable and a
    // ref
    // to next node in the list
    private class NodoLista
    {
        E dato;
        NodoLista pros;
    }

    // method that adds whatever is meant by x to the begging of the list

    public void aggiungi(E x)
    {
        NodoLista nodo = new NodoLista();
        nodo.dato = x;
        if (inizio != null)
            nodo.pros = inizio;
        inizio = nodo;
    }

    // a method that switches last and first elements in the list
    public void scambia()
    {
        E datoFine;

        if (inizio != null && inizio.pros != null) {
            E datoInizio = inizio.dato;
            NodoLista nl = inizio;

            while (nl.pros != null)
                nl = nl.pros;

            datoFine = nl.dato;
            inizio.dato = datoFine;
            nl.dato = datoInizio;
        }
    }

    // and here is the problem
    // this method is supposed to double the value of the raw type variable dato
    // of each node
    public void raddoppia()
    {

        if (inizio != null) {
            NodoLista temp = inizio;
            while (temp != null) {
                temp.dato *= 2;
            }
        }
    }

    // Overriding toString from object (ignore this)
    public String toString(String separatore)
    {
        String stringa = "";
        if (inizio != null) {
            stringa += inizio.dato.toString();
            for (NodoLista nl = inizio.pros; nl != null; nl = nl.pros) {
                stringa += separatore + nl.dato.toString();
            }
        }
        return stringa;
    }

    public String toString()
    {
        return this.toString(" ");
    }

}

и вот какую ошибку мне выдает компилятор.

    ListaInteri.java:39: inconvertible types
found   : int
required: E
  temp.dato*=2;         
             ^
1 error

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

  1. Почему это происходит? Разве не существует такой вещи, как стирание необработанных типов во время компиляции, когда вся информация, имеющая отношение к типам параметров или аргументов, игнорируется?
  2. Как это исправить? Второй метод (тот, который переключается первым и последним) показывает, что на самом деле компилятор может изменять поля в узлах, если он передается другим необработанным типом, а если мы попытаемся умножить его, например, на 2, это больше не нормально. потому что теперь компилятор знает, что мы говорим о int/Integer, поэтому возвращает эту ошибку. Заранее спасибо за любые ответы.

РЕДАКТИРОВАТЬ; извините, пришлось сделать его читаемым, теперь все в порядке. EDIT2: почти читаемо аргх!


person Karim    schedule 19.02.2010    source источник
comment
ненавижу форматирование, люблю итальянские идентификаторы.   -  person President James K. Polk    schedule 20.02.2010


Ответы (5)


Integer является окончательным, его нельзя расширить. Следовательно, ваше использование Generics бессмысленно (хотя синтаксически правильно), и вы можете просто использовать int - тогда проблема исчезнет.

person Michael Borgwardt    schedule 19.02.2010
comment
Спасибо за ответ. во-первых, имейте в виду, что это школьные упражнения, поэтому они на самом деле пытаются копаться в каждом маленьком скрытом уголке, который могут найти, просто чтобы посмотреть, сможете ли вы сделать это таким образом. 2nd Integr в этом примере не расширяется. что ‹E extends Integer› просто подразумевает, что эта коллекция предназначена только для подтипов Integer, поэтому это все равно, что сказать, что это может быть только lista‹Integer› lista‹Short› lista‹Byte› (или иным образом останавливает вас во время компиляции, если вы не понимаете это сами) использовать in где? а вместо чего? - person Karim; 20.02.2010
comment
@Karim: Integer не имеет подтипов, так как это окончательный класс. Вы имеете в виду, что E расширяет число? - person Simon Nickerson; 20.02.2010
comment
@Karim: Short и Byte не являются подтипами Integer (у нет подтипов Integer). У них есть общий супертип, Число. Проблема в том, что вы не можете выполнять арифметические действия над числом — оно содержит только методы для преобразования его значения в числовые примитивы. Это позволит вам выполнить половину вашей критической строки, но другая половина невозможна, потому что вы не можете сопоставить результат с числом. - person Michael Borgwardt; 20.02.2010

Стирание типов здесь не проблема (универсальные типы стираются, поэтому они недоступны во время выполнения, но во время компиляции типы должны быть конвертируемыми).

Я бы, вероятно, исправил это, чтобы вообще не использовать параметр типа. Integer — это конечный класс, поэтому нет смысла заставлять его использовать более общий тип, такой как E extends Integer.

person Simon Nickerson    schedule 19.02.2010
comment
Ну, это происходит во время компиляции, так что файл класса не имеет представления об общих типах, верно? - person President James K. Polk; 20.02.2010
comment
хорошо, спасибо еще раз, я думаю, что понял, что вы, ребята, имеете в виду, говоря, что не используете E extends integer. проблема в том, что вы могли бы добавить любой объект, если я не ошибаюсь? если я также не изменю внутренний класс и не перестрою список. в любом случае, есть ли способ заставить его работать, изменяя только метод raddoppia? спасибо еще раз. PS: извините за некоторую упрямство, если бы это зависело от меня, я бы просто использовал Vector‹E› хе-хе :) просто это школьное упражнение, которое нужно выполнять определенным образом. - person Karim; 20.02.2010

Одно уточнение, просто чтобы убедиться, что вы что-то поняли: вы спросили,

Разве не существует такой вещи, как стирание необработанных типов во время компиляции, когда вся информация, имеющая отношение к типам параметров или аргументов, игнорируется?

Люди много говорят о «стирании типов» в Java, отчасти потому, что стирание типов является одним из многих интересных различий между Java и C++, а частично потому, что стирание типов может привести к странным и неожиданным ошибкам при смешивании более новых (Java 5 и более поздних версий) с старый код Java.

Однако стирание типа не означает, что «вся информация, имеющая отношение к типам параметров или аргументов, игнорируется» — это означает нечто гораздо более узкое. Кроме того, как вы сказали, стирание типов применяется «во время компиляции», но оно применяется после, когда компилятор проверяет все типы и объявления в вашей программе, чтобы увидеть, есть ли какие-либо ошибки типов.

Первый вопрос: что такое стирание шрифта? Стирание типов — это то, что происходит только с универсальными типами. Integer не является универсальным типом, но ArrayList<T> является универсальным типом, потому что вы можете объявить ArrayList<Integer> или ArrayList<String>. После того, как компилятор завершит проверку типов в вашей программе на соответствие, он отбрасывает всю информацию об общих типах, так что ArrayList<Integer> и ArrayList<String> становятся просто ArrayList. Вот почему вы можете написать if (foo instanceof String) на Java, но вы не можете написать if (foo instanceof ArrayList<String>): когда этот оператор if оценивается во время выполнения, нет никакого способа отличить ArrayList<String> от ArrayList<Integer> или от любого другого типа ArrayList.

Второй вопрос: когда применяется стирание шрифта? Стирание типа применяется, когда компилятор генерирует скомпилированный код («байт-код») после проверки кода на наличие ошибок компиляции. Это означает, что следующий код выдает ошибку компиляции в Java, хотя в Java используется стирание типов:

ArrayList<String> foo = new ArrayList<String>();
foo.add(new Integer(3)); // Error - Compiler knows that String is expected

Я знаю, что не ответил на ваш вопрос, но я хотел убедиться, что вы сначала поняли стирание шрифта. :-)

Возвращаясь к исходному вопросу: почему класс нужно объявлять с помощью <E extends Integer>? Это специально требуется для домашнего задания? Я просто скопировал и вставил ваш код в Eclipse, удалил <E extends Integer> вверху и заменил все объявления E на Integer, и все отлично скомпилировалось.

(Кстати, все еще есть проблема со raddoppia, как вы написали здесь, даже если вы избавитесь от <E extends Integer> и заставите его скомпилироваться. Вы уже нашли это? Попробуйте протестировать raddoppia со списком, который содержит более одного элемента. , и вы должны увидеть проблему...)

person Joe Carnahan    schedule 22.02.2010

пытаться :

  temp.dato= new Integer(((Integer)temp.dato).intValue*2);
person Kylar    schedule 19.02.2010
comment
Да, ты прав. Я был на своем телефоне, компилятора не было :/ - person Kylar; 20.02.2010

Я думаю, это должно быть

<E extends Number>

тогда вы можете сказать

tmp.dato = tmp.dato.intValue() * 2;

Я думаю, что это скомпилируется, но если кто-то использует вашу структуру данных для нецелых чисел, этот метод не даст правильного ответа.

person Ron    schedule 19.02.2010
comment
Не будет компилироваться, потому что вы не можете присвоить int переменной типа E extends Number - person Michael Borgwardt; 20.02.2010