Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic screws up non-related collection

Why do collections that are not related to the template class drop their type? Here is an example: (Sorry, it will not compile because of the error I'm confused about.)

package test;

import java.util.ArrayList;
import java.util.List;

public class TemplateTest {

    public static class A { }

    public static class B<T extends Comparable> {
        List<A> aList = new ArrayList<A>();

        public List<A> getAList() {
            return aList;
        }

        public int compare(T t, T t1) {
            return t.compareTo(t1);
        }
    }

    public static void main(String[] args) {
        B b = new B();
        for (A a : b.getAList()) { //THIS DOES NOT WORK

        }
        List<A> aList = b.getAList(); //THIS WORKS
        for (A a : aList) {

        }
    }
}

This code throws an error upon compilation:

test/TemplateTest.java:24: incompatible types
    found   : java.lang.Object
    required: test.TemplateTest.A
        for (A a : b.getAList()) {

If I specify the template of B like B<String>, or if I remove the template from B completely, then everything is ok.

What's going on?

EDIT: people pointed out there was no need to make B generic so I added to B

like image 752
naugler Avatar asked Feb 14 '13 18:02

naugler


2 Answers

Yes, it is known behaviour that if you use a raw type, then all type parameters on the class are lost, not just the type-level parameter that you failed to declare.

The issue is partly here:

If I specify the template of B like B<String>, or if I remove the template from B completely, then everything is ok.

That's not an option, you aren't to choose if you want to specify the type parameter or not. The only reason it compiles at all with no parameter specified is for backward compatibility. Writing new code with missing type parameters is a programming error.

List<A> list = b.getList() does not successfully interpret the type, it is just effectively sticking in an arbitrary cast and trusting you that the assignment is correct. If you look at the compiler warnings it is in fact generating a warning for an unsafe conversion.

for(A a : b.getList()) {} upgrades that warning to an error because the inserted cast would be inside compiler generated code, so it refuses to auto-generate unsafe code at all, rather than just give a warning.

From the java language specification:

The use of raw types is allowed only as a concession to compatibility of legacy code. The use of raw types in code written after the introduction of genericity into the Java programming language is strongly discouraged. It is possible that future versions of the Java programming language will disallow the use of raw types.

Bottom line really is that the only significant thing java generics share with C++ templates is the <> syntax :)

More details: What is a raw type and why shouldn't we use it?

like image 99
Affe Avatar answered Sep 20 '22 01:09

Affe


First, in Java, it's Generics, not Templates like it is in C++.

You are declaring a generic type parameter T in your class B but you aren't using it. You should use T instead of A throughout your B class defintion.

public static class B<T> {
    List<T> aList = new ArrayList<T>();

    public List<T> getAList() {
        return aList;
    }
}

Then you should use a type parameter when you declare your instance of class B.

B<A> b = new B<A>();

But if you know that your aList variable will always hold objects of type A as the method name getAList suggests, then there would be no reason to make class B generic.

like image 22
rgettman Avatar answered Sep 21 '22 01:09

rgettman