Generics and Casting

M

malcolm.ryan

I've read a number of articles which highlight the reason why you can't
type cast generics in Java. The classic example is casting a
List<Integer> to List<Number> or vice versa. This can't be done because
you can add a Float to a List<Number> but not to a List<Integer>. I
understand this. But why does the analysis stop here? Sure, this is a
problem, but why was it never solved?

It seems to me that the problem lies in the mixing up of input and
output types. A List<Integer> has methods which input Integers and
methods which output Integers. You can't cast it to a List<Number>
because, while it output Numbers, it cannot input them.

So why don't we deliberately distinguish input and output types in our
definitions? Suppose we have a new notation T<+I, -O> which defines I
as an input type and O as an output type. Input types can only be used
for method parameters. Output types can only be used for return types,
eg:

interface List<+I, -O> {

public boolean add(I element);

public O get(int i);

public O set(int i, I element);

// etc

}

Now a List<Integer> becomes a List<+Integer, -Integer>. Which can
happily be cast to a List<+Integer, -Number>, because any Integer the
list outputs can be cast to a Number. So:

List<+Integer, -Integer> lii = new ArrayList<+Integer, -Integer>();
lii.add(new Integer(1));
List<+Integer, -Number> lin = lii;
Number n = lin.get(1); // this is legal
lin.add(new Float(3.14)) // this is illegal and would return a
// compilation error because input type
// Float does not match +Integer

Vice versa, you can cast a List<+Number,-Number> to a
List<+Integer,-Number> because any Integer you input can be be cast to
a Number. So:

List<+Number, -Number> lnn = new ArrayList<+Number, -Number>();
List<+Integer, -Number> lin = lnn;
lin.add(new Integer(1));

And then we could have a kind of "don't care" indicator if we never
want to use a particular mode (input or output). Eg:

List<+Integer, -Integer> lii = new ArrayList<+Integer, -Integer>();
List<_, -Number> lun = lii;
for (Number n : lun) {
System.out.println(n);
}

This would be very useful. Collections are pecular insofar as their
input and output types match. Even so, often you only want to read from
a collection and not write to it. In other cases you have a class that
only inputs or only outputs a given type. Casting these would be type
safe, but Java doesn't allow it because it is afraid of cases that
simply aren't there. For example:

interface Measurer<T> {
public float area(T thing);
}

If Circle extends Shape then there is no good reason why a
Measurer<Shape> should not be cast to a Measurer<Circle>.

In general T<+I,-O> can be safely cast to T<+J,-P> if J can be cast to
I, and O
can be cast to P. This analysis seems simple and straightforward to me.
Am I missing something? Is there an reason why it was not implemented
this way?

Malcolm
 
E

E11

[snip]

Is there an reason why it was not implemented
this way?

A few reasons that i could think of...

- The implementation of generic is not strictly restricted to the
Collections framework. i.e. it is not restricted to container-like
objects where you put in something and take out something (though that
may be the main motivation behind generics). One good example is the
new generified Class object. Of course, you can argue that we can also
implement the Class object as Class<+T, -T>, but semantically, that may
not be logical.

- How then, would the notation for a Map be like?
Map<+(KI, VI), -(KO, VO)> ?
And if you want to declare a new map?
Map<+(String, Object), -(String, Object)> = new Map<+(String, Object),
-(String, Object)>() ?
What about a 2-level Map?
List<+Integer, -Integer> lii = new ArrayList<+Integer, -Integer>();
lii.add(new Integer(1));
List<+Integer, -Number> lin = lii;
Number n = lin.get(1); // this is legal

But you can get a number out even without casting the whole list? i.e.
List<Integer> lii = new ArrayList<Integer>();
lii.add(new Integer(1));
Number n = lii.get(1); // this is legal



Regards,
Edwin
 
R

Roedy Green

So why don't we deliberately distinguish input and output types

It may be clumsy, but is there some way can you do that already with
two types one for input and one for output that you assign either to
results or input parms?

Generics more and more strike me as a extremely complicated way to try
to avoid a very simple error. You are far more likely to make an
error in the generics than in a parameter to an add method. Generics
presume a caste system of programmers -- those that write generics
code and those that use generified classes.
..
 
M

megagurka

(e-mail address removed) skrev:
Am I missing something? Is there an reason why it was not implemented
this way?

Java already supports this through bounded wildcards, which is a more
powerful concept than the one you present. For example, using your
Measurer class:

static <T> void measureEach(List<? extends T> list, Measurer<T>
measurer) {
...
}

If you want more info, read this:

http://www.langer.camelot.de/GenericsFAQ/JavaGenericsFAQ.html

/JN
 
M

Malcolm Ryan

E11 said:
- The implementation of generic is not strictly restricted to the
Collections framework. i.e. it is not restricted to container-like
objects where you put in something and take out something (though that
may be the main motivation behind generics). One good example is the
new generified Class object. Of course, you can argue that we can also
implement the Class object as Class<+T, -T>, but semantically, that may
not be logical.

What I said carries over to any class. By "input" I just mean a type
that is used in the parameters of a method on the class. By "output" I
mean a type that is returned by a method. A brief glance at the API
tells me that Class<T> apparently never uses T as the type for any
method parameters, just for return types. So it could simply be defined
as Class said:
- How then, would the notation for a Map be like?

Lets see. Map<K,V> defines:

V get(Object key)
V put(K key, V value)
Set<K> keySet()

So yes, K and V are both input types (method params) and output typess
(returned by methods, albeit indirectly in the case of K). So yes you
need to define Map said:
And if you want to declare a new map?
Map<+(String, Object), -(String, Object)> = new Map<+(String, Object),
-(String, Object)>() ?

Well obviously this syntax is clumsy. Usually you will want to be able
to simply say Map<String, Object> and know that you are actually
creating a Map<+String/-String, +Object/-Object>. And most of the time
But you can get a number out even without casting the whole list? i.e.
List<Integer> lii = new ArrayList<Integer>();
lii.add(new Integer(1));
Number n = lii.get(1); // this is legal

I realise that. But you can't, for instance, do:

public List<Number> getList() {
List<Integer> list = new ArrayList<Integer>();
return list;
}

It make sense, however, to do:

public List<-Number> getList() {
List<+Integer/-Integer> list = new ArrayList<+Integer/-Integer>();
return list;
}

Or even better, you could have an interface:

interface Foo {
public List<-Foo> list();

}

And a class:

class Bar implements Foo {
public List<-Bar> list() {
List<+Bar/-Bar> list = new ArrayList<+Bar/-Bar>();
// add stuff to the list
return list;
}
}

Now Bar implements Foo, as List<-Bar> is a proper subtype of
List<-Foo>. So if you have a Foo f you can do:

for (Foo x : f.list()) {
x.frobFoo();
}

and if you have something you know explicitly to be a Bar b, you can
do:

for (Bar x : b.list()) {
x.frobBar();
}

As things stand in JDK 1.5, you have to do:

for (Foo x : b.list()) {
Bar y = (Bar)x;
y.frobBar();
}

Which relies on the programmer's knowledge that the cast is type-safe.

Malcolm
 
E

E11

Malcolm said:
It make sense, however, to do:

public List<-Number> getList() {
List<+Integer/-Integer> list = new ArrayList<+Integer/-Integer>();
return list;
}

But you see, then you would have to ask why such a method is needed?
With the above method, i would imagine what you need is a List whereby
you can insert Integers and extract Numbers (or any supertype of
Integers). In that case,

public List<Integer> getList() {
List<Integer> list = new ArrayList<Integer>();
return list;
}

would allow you to:
List<Integer> list = getList();
list.add(new Integer(1));
Number n = list.get(0);

which is also what the List returned by your method could do. And also,
similar to what you propose, you can't insert a Number.

What i think you want, is the ability to cast, say
List<+TypeA, -TypeA>
into
List<+AnySUBTypeOfTypeA, -AnySUPERTypeOfTypeA>

(Your example in the earlier post was
List<+Integer, -Integer> to List<+Integer, -Number> and
List<+Number, -Number> to List<+Integer, -Numher>)

Then you have to ask, why do you want to do such a cast? I would
imagine so that you can:

List<+TypeA, -TypeA> listA = new List<+TypeA, -TypeA>();
List<+SubTypeOfTypeA, -SuperTypeOfTypeA> listB = listA
// Whereby you can then do
listB.add(new SubTypeOfTypeA());
SuperTypeOfTypeA yetAnotherVar = listB.get(0);

But you see, you can do this presently, without the casting:
e.g.
List<Number> list = new List<Number>()
list.add(new Integer(1)); // Input a SubType of Number
Object o = list.get(0); // Output a SuperType of Number
interface Foo {
public List<-Foo> list();

}

And a class:

class Bar implements Foo {
public List<-Bar> list() {
List<+Bar/-Bar> list = new ArrayList<+Bar/-Bar>();
// add stuff to the list
return list;
}
}

Now Bar implements Foo, as List<-Bar> is a proper subtype of
List<-Foo>. So if you have a Foo f you can do:

for (Foo x : f.list()) {
x.frobFoo();
}

and if you have something you know explicitly to be a Bar b, you can
do:

for (Bar x : b.list()) {
x.frobBar();
}

To achieve this, perhaps all that is needed is for the interface to be
designed more flexibly?

i.e.

interface Foo {
public List<? extends Foo> list();
}

?



Regards,
Edwin
 
O

Oliver Wong

I've read a number of articles which highlight the reason why you can't
type cast generics in Java. The classic example is casting a
List<Integer> to List<Number> or vice versa. This can't be done because
you can add a Float to a List<Number> but not to a List<Integer>. I
understand this. But why does the analysis stop here? Sure, this is a
problem, but why was it never solved?

I never perceived this as a "problem". In fact, I think it's a GOOD
thing that you cannot cast a List said:
It seems to me that the problem lies in the mixing up of input and
output types. A List<Integer> has methods which input Integers and
methods which output Integers. You can't cast it to a List<Number>
because, while it output Numbers, it cannot input them.

So why don't we deliberately distinguish input and output types in our
definitions? Suppose we have a new notation T<+I, -O> which defines I
as an input type and O as an output type. Input types can only be used
for method parameters. Output types can only be used for return types,
eg:

interface List<+I, -O> {

public boolean add(I element);

public O get(int i);

public O set(int i, I element);

// etc

}

If you wanted to do this, you don't need new notation, I think. You
could write it as so:

interface List<I,O> {
public boolean add(I element);
public O get(int i);
public O set(int i, I element);
}

Althought if you're trying to implement the concept of a list which
automatically casts its content upon upon, you might want to change the
declaration to "interface List<I extends O,O>" to ensure that the cast is
actually legal.


[snip example explanation, because the following paragraph sums it up]
In general T<+I,-O> can be safely cast to T<+J,-P> if J can be cast to
I, and O
can be cast to P. This analysis seems simple and straightforward to me.
Am I missing something? Is there an reason why it was not implemented
this way?

<code>
Number secretlyAnInteger = new Integer(2);
Integer explicitInteger = (Integer)secretlyAnInteger; //This is allowed.

List<+Number,-Number> myNumbers = new List<+Number,-Number>();
myNumbers.add(new Double(3.4));
List<+Number,-Integer> myNumbersGivingIntegers =
(List<+Number,-Integer>)myNumbers(); //DANGER!
</code>

The reason you the above rule doesn't work is because you can't just check
that whether I/J and O/P are type compatible, but you'd have to check
whether every element in the list is type compatible with the desired output
type. For lists with billions of elements, this can be quite a performance
hit.

- Oliver
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Similar Threads

generics puzzle 57
Generics 12
Generics ? 14
Dynamic Casting with Generics: Type Erasure Problems 5
Java type-casting -- Q3 50
Hairy generics question 29
Tree design with generics 2
question about casting and inheritance 8

Members online

Forum statistics

Threads
473,769
Messages
2,569,576
Members
45,054
Latest member
LucyCarper

Latest Threads

Top