views:

196

answers:

6

I'll start by saying I am a Java (/programming) newbie and this is my first question on the website.

Just learnt how to make an ordered list in Java using recursive nodes. Everything was easy until I ran into this exercise asking me to write a method that would double whatever value is contained in each node. Here is the code I tried to write:

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(" ");
    }

}

and here is the error the compiler gives me.

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

Now keep in mind any kind of help would be greatly appreciated anyway here are the questions I would like an answer to.

  1. Why does this happen? Isn't there such a thing as type erasure of raw types during compile time where all info that has to do with parameters or arguments types is ignored?
  2. How do I fix this? The second method ( the one that switches first and last) shows it's actually ok for the compiler to change fields in nodes as long as it is passed by another raw type, while if we try to multiply it by 2 for example it's no longer ok because compiler now knows we are talking about an int/Integer so gives this error in return. Thanks in advance for any answers.

EDIT; sorry had to make it readable should be ok now. EDIT2: almost readable argh!

+3  A: 

Type erasure is not the issue here (generic types are erased so they are not available at runtime, but at compile time, the types very much have to be convertible).

I would probably fix this to not use a type parameter at all. Integer is a final class, so there is no point making it use a more generic type such as E extends Integer.

Simon Nickerson
Well, it happens *during* compilation, so that the class file has no notion of generic types, right?
GregS
Fair point. Edited accordingly.
Simon Nickerson
ok thx again I think I got what you guys mean by not using E extends integer.problem is that you would be able to add any kind of object if I am not mistaken? unless I also change the innerclass and pretty much rebuild the list.anyway is there anyway to get around making it work modifying only the "raddoppia" method?thx again.ps: sorry for being a bit stubborn if it was up to me I would just be using Vector<E> hehe :) it's just that it's a school exercise that has to be done in a certain way.
Karim
+5  A: 

Integer is final, it cannot be extended. Therefore, your use of Generics is pointless (though syntactically valid), and you might as well just use int - the problem will then disappear.

Michael Borgwardt
thx for the answer.first of all keep in mind this is shcool exercises so they actually try to go dig in every little hidden corner they can find just to see if "you can do it this way".2nd Integr is not being extended in this example. that <E extends Integer> merely implies that this collection is only for subtypes of Integer so it's like saying this can only be lista<Integer> lista<Short> lista<Byte> (or otherwise stops you at compile time if you fail to realize it yourself)use int where? and instea of what?
Karim
@Karim: Integer doesn't have any subtypes, as it's a final class. Do you mean E extends Number?
Simon Nickerson
@Karim: Short and Byte are not subtypes of Integer (there *are no* subtypes of Integer). They do have a common supertype, Number. The problem is that you cannot do arithmetic on a Number - it only contains methods to convert its value to numerical primitives. That would allow you to do half of your critical line, but the other half is impossible, because you cannot assing the result to a Number.
Michael Borgwardt
A: 

try :

  temp.dato= new Integer(((Integer)temp.dato).intValue*2);
Kylar
I don't think this will work, because Integer is not E.
Michael Borgwardt
Yeah, you're right. I was on my phone, didn't have a compiler :/
Kylar
A: 

I think it should be

<E extends Number>

then you can say

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

I think this will compile, but if someone uses your data structure for non-integers, this method won't produce the correct answer.

Ron
Won't compile because you cannot assing an int to a variable of type "E extends Number"
Michael Borgwardt
A: 

I tired both of them Ron and Kylar unluckily it's still pretty much the same problem ( same error message) the compiler wont assign it back to the inner classes E field as soon as "it" "notices" it's an Integer.

it's just a bit odd because I found this in a chapter that explains how to write this kind of collections (using recursive nodes) then as exercise you have "write methods that find the average of all numbers in the list, the sum, redefine toString, a method that switches first and last element, all of which can be done without altering the skeleton class built in the explanation. and then this method which requires a completely different approach.

thanks all for the answers anyway. It's no big deal I guess since I can still get around it with the other methods you suggested. thx alot!

karim
A: 

One clarification, just to make sure you understand something: You asked,

Isn't there such a thing as type erasure of raw types during compile time where all info that has to do with parameters or arguments types is ignored?

People talk a lot about "type erasure" in Java, partially because type erasure is one of the many interesting differences between Java and C++ and partially because type erasure can give you strange and unexpected errors when you mix newer (Java 5 and later) with older Java code.

However, type erasure doesn't mean "all info that has to do with parameters or arguments types is ignored" - It means something much narrower than that. Also, as you said, type erasure is applied "during compile time", but it is applied after the compiler checks all the types and declarations in your program to see if there are any type errors.

First question: What is type erasure? Type erasure is something that only happens to generic types. Integer is not a generic type, but ArrayList<T> is a generic type, because you can declare an ArrayList<Integer> or ArrayList<String>. After the compiler is done checking the types in your program to make they all match, then it discards all of the information about generic types, so that an ArrayList<Integer> and an ArrayList<String> both just become ArrayList. This is the reason why you can write if (foo instanceof String) in Java, but you can't write if (foo instanceof ArrayList<String>): When that if statement is evaluated at runtime, there is no way to tell an ArrayList<String> apart from an ArrayList<Integer> or from any other type of ArrayList.

Second question: When is type erasure applied? Type erasure is applied when the compiler generates the compiled code (the "bytecode"), after the code is checked for compile errors. This means that the following code gives a compile error in Java, even though Java uses type erasure:

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

I know I haven't answered your question, but I wanted to make sure you understood type erasure first. :-)

Getting back to your original question: Why does the class need to be declared with <E extends Integer>? Is that specifically required for the homework assignment? I just copied and pasted your code into Eclipse, removed the <E extends Integer> at the top, and replaced all of the E declarations with Integer, and it compiled just fine.

(By the way, there's still a problem with raddoppia as you've written it here, even if you get rid of <E extends Integer> and get it to compile - Have you found it yet? Try testing raddoppia with a list that has more than one element, and you should see the problem...)

Joe Carnahan