简体   繁体   中英

Java generic method returns different type - no exception

I have found some strange code, where I said "This is never called, because it would throw a class cast Exception". Well the code gets called and its working.

Can anybody explain me: Why is this working ?

The method getZipList() is defined to return a List of Strings, but the internal Logic is returning a List with different objects. Also inside the main method a List of Strings is expected as 'list' but the list contains something different.

public class GenericMethodList{
    public static void main(String [] args)
    {
        GenericMethodList o = new GenericMethodList();
        List<String> list = o.getZipList(true);

        Iterator<?> iter = list.iterator();
        while (iter.hasNext()){
            ZipCode zipplace = (ZipCode) iter.next();
            System.out.println(zipplace.value);
        }
    }

    public List<String> getZipList(boolean someParameter){
        //why is this not throwing an exception at runtime?
        List list;
        if(someParameter){
            list = getZipCodes();//List<ZipCode>
        } else {
            list = getZipStreets();//List<ZipStreet>
        }
        return list;
    }

    private List<ZipCode> getZipCodes(){
        List<ZipCode> list = new ArrayList<ZipCode>();
        list.add(new ZipCode("code1"));
        list.add(new ZipCode("code1"));
        return list;
    }

    private List<ZipStreet> getZipStreets(){
        List<ZipStreet> list = new ArrayList<ZipStreet>();
        list.add(new ZipStreet("street1"));
        list.add(new ZipStreet("street2"));
        return list;
    }

    public class ZipCode{
        public String value;
        public ZipCode(String value){
            this.value = value;
        }
    }

    public class ZipStreet {
        public String value;
        public ZipStreet(String value){
            this.value = value;
        }
    }
}

Thank you very much for your explanations.

You must be getting an "unchecked cast" compiler warning for the return list line because there you are returning a raw type as a parameterized type. At this position an "unchecked cast" is performed from the raw type (for which there is simply no info on the type of elements) into the parameterized type which is declared for the method. The type parameter could be any arbitrary type—the compiler simply has no idea what to allow and what to prevent.

This cast is unchecked in the sense that it cannot be performed at runtime: at runtime the type parameters of the list instance are erased anyway, so the JVM has no idea you are doing a bad thing there.

Because you are using a raw type 'List' (ie a generic type with no type parameter specified). Raw types are there for legacy purposes, but should be avoided in new code because it loses type safety, as you have seen :

http://docs.oracle.com/javase/tutorial/java/generics/rawTypes.html

You should be getting a warning. Because of type erasure (the .class file doesn't actually list the parameterized type for backwards compatibility), the runtime type of a List<E> is just List , and the JVM doesn't seen any difference. The error will come in a distant location when somebody tries to pull a String out of that List and gets a ZipCode instead.

You must get below warring at compile time.

 Note: Some input files use unchecked or unsafe operations.
 Note: Recompile with -Xlint:unchecked for details.

Since Generics are based on the implementation of type erasure . So At run time it do not have information about Generic type(generaly called Non-Reifiable Types )

The issue is that you combine in your solution RawType with generic. You perform this in method getZipList(boolean) . As List there has none type you brake the type assurance. Next place where you cheat the compiler is the way you declare the Iterator<?> , as you do not declare the generic parameter i will store object. So next time you avoid type assurance that you are taking advantage in look where you cast to expected type. That is why its work. Normally the casting is performed by compiler but you have implement it by your self in correct way.

IF your code would look like this

 Iterator<String> iter = list.iterator();

 while (iter.hasNext()){
     ZipCode zipplace = (ZipCode) iter.next();// Compilation error
     System.out.println(zipplace.value);
  }

IF your main method will look like this:

while(String s : list) {
 System.out.wirteln(s);
}

An ClassCastException will be thrown. Because the code "will look like"

    Iterator<Object> iter = list.iterator();
    while (iter.hasNext()){
        ZipCode zipplace = (String) iter.next();
        System.out.println(zipplace.value);
    }

As in Java you can not cast this way. A exception is thrown.

To summarize you have create a code that brakes the type assurance but you have implemented the correct usage of it. That is why its work.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM